TODOs: - [x] Do restart when checkout / restore if there is a DB - [x] List all branches (branch id, name, date) - [x] Allow checking out versions with no DB - [x] safeguard to never delete main branches - [x] create app hook for neon template - [x] weird UX with connector on configure panel - [x] tiny neon logo in connector - [x] deploy to vercel - [x] build forgot password page - [x] what about email setup - [x] lots of imgix errors - [x] edit file - db snapshot - [x] DYAD_DISABLE_DB_PUSH - [ ] update portal doc - [x] switch preview branch to be read-only endpoint - [x] disable supabase sys prompt if neon is enabled - [ ] https://payloadcms.com/docs/upload/storage-adapters - [x] need to use main branch... Phase 2? - [x] generate DB migrations
138 lines
4.0 KiB
TypeScript
138 lines
4.0 KiB
TypeScript
import React, { useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { useCreateApp } from "@/hooks/useCreateApp";
|
|
import { useCheckName } from "@/hooks/useCheckName";
|
|
import { useSetAtom } from "jotai";
|
|
import { selectedAppIdAtom } from "@/atoms/appAtoms";
|
|
import { NEON_TEMPLATE_IDS, Template } from "@/shared/templates";
|
|
|
|
import { useRouter } from "@tanstack/react-router";
|
|
|
|
import { Loader2 } from "lucide-react";
|
|
import { neonTemplateHook } from "@/client_logic/template_hook";
|
|
import { showError } from "@/lib/toast";
|
|
|
|
interface CreateAppDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
template: Template | undefined;
|
|
}
|
|
|
|
export function CreateAppDialog({
|
|
open,
|
|
onOpenChange,
|
|
template,
|
|
}: CreateAppDialogProps) {
|
|
const setSelectedAppId = useSetAtom(selectedAppIdAtom);
|
|
const [appName, setAppName] = useState("");
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const { createApp } = useCreateApp();
|
|
const { data: nameCheckResult } = useCheckName(appName);
|
|
const router = useRouter();
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (!appName.trim()) {
|
|
return;
|
|
}
|
|
|
|
if (nameCheckResult?.exists) {
|
|
return;
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
try {
|
|
const result = await createApp({ name: appName.trim() });
|
|
if (template && NEON_TEMPLATE_IDS.has(template.id)) {
|
|
await neonTemplateHook({
|
|
appId: result.app.id,
|
|
appName: result.app.name,
|
|
});
|
|
}
|
|
setSelectedAppId(result.app.id);
|
|
// Navigate to the new app's first chat
|
|
router.navigate({
|
|
to: "/chat",
|
|
search: { id: result.chatId },
|
|
});
|
|
setAppName("");
|
|
onOpenChange(false);
|
|
} catch (error) {
|
|
showError(error as any);
|
|
// Error is already handled by createApp hook or shown above
|
|
console.error("Error creating app:", error);
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const isNameValid = appName.trim().length > 0;
|
|
const nameExists = nameCheckResult?.exists;
|
|
const canSubmit = isNameValid && !nameExists && !isSubmitting;
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="sm:max-w-[425px]">
|
|
<DialogHeader>
|
|
<DialogTitle>Create New App</DialogTitle>
|
|
<DialogDescription>
|
|
{`Create a new app using the ${template?.title} template.`}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="grid gap-4 py-4">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="appName">App Name</Label>
|
|
<Input
|
|
id="appName"
|
|
value={appName}
|
|
onChange={(e) => setAppName(e.target.value)}
|
|
placeholder="Enter app name..."
|
|
className={nameExists ? "border-red-500" : ""}
|
|
disabled={isSubmitting}
|
|
/>
|
|
{nameExists && (
|
|
<p className="text-sm text-red-500">
|
|
An app with this name already exists
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => onOpenChange(false)}
|
|
disabled={isSubmitting}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
disabled={!canSubmit}
|
|
className="bg-indigo-600 hover:bg-indigo-700"
|
|
>
|
|
{isSubmitting && (
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
)}
|
|
{isSubmitting ? "Creating..." : "Create App"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|