Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/hooks/use-projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,18 @@ export function useDeleteProject() {
},
});
}

export function useImportProject() {
const utils = api.useUtils();
return api.project.importProject.useMutation({
onSuccess: (data) => {
invalidateProjectQueries(utils);
toast.success(
`Project imported successfully (${data.foldersCreated} folders, ${data.endpointsCreated} endpoints)`,
);
},
onError: (error) => {
toast.error(error.message || "Failed to import project");
},
});
}
86 changes: 86 additions & 0 deletions src/modules/workspace/ui/components/sidebar/dialogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,89 @@ export function CreateEndpointDialog({
</Dialog>
);
}

export function ImportProjectDialog({
open,
onOpenChange,
onImport,
isImporting,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
onImport: (data: unknown) => void;
isImporting: boolean;
}) {
const [jsonContent, setJsonContent] = useState("");
const [error, setError] = useState<string | null>(null);

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.addEventListener("load", (event) => {
const content = event.target?.result as string;
setJsonContent(content);
setError(null);
});
reader.readAsText(file);
};

const handleSubmit = () => {
try {
setError(null);
const parsed = JSON.parse(jsonContent);
onImport(parsed);
} catch {
setError("Invalid JSON format. Please select or paste a valid exported project JSON.");
}
};

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md sm:max-w-lg">
<DialogHeader>
<DialogTitle>Import Project</DialogTitle>
</DialogHeader>

<div className="space-y-4">
<div className="space-y-1.5">
<label className="text-xs text-muted-foreground">
Select exported JSON file
</label>
<Input
type="file"
accept=".json,application/json"
onChange={handleFileChange}
/>
</div>

<div className="space-y-1.5">
<label className="text-xs text-muted-foreground">
Or paste JSON content
</label>
<textarea
className="w-full h-40 p-2 text-xs font-mono rounded-md border bg-background resize-none focus:outline-none focus:ring-1 focus:ring-ring"
placeholder="Paste project export JSON here..."
value={jsonContent}
onChange={(e) => {
setJsonContent(e.target.value);
setError(null);
}}
/>
</div>

{error && <p className="text-xs font-medium text-destructive">{error}</p>}
</div>

<DialogFooter>
<Button variant="ghost" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button onClick={handleSubmit} disabled={!jsonContent.trim() || isImporting}>
{isImporting ? "Importing..." : "Import Project"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Loading
Loading