import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { DialogFooter, DialogHeader } from "@/components/ui/dialog"; import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage, } from "@/components/ui/form"; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from "@/components/ui/select"; import { ToastAction } from "@/components/ui/toast"; import { DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { LoadingSpinner } from "@/components/ui/loading-spinner"; import { useToast } from "@/hooks/use-toast"; import { useTemplateImages } from "@/templates/api"; import { superstructResolver } from "@hookform/resolvers/superstruct"; import { useRef, useEffect } from "react"; import { useForm } from "react-hook-form"; import { nonempty, object, pattern, string, type Infer } from "superstruct"; import { useCreateWorkspace, useWorkspaceRuntimes } from "./api"; import type { TemplateImage } from "@/templates/types"; import type { WorkspaceRuntime } from "./types"; import { API_ERROR_WORKSPACE_EXISTS, isApiErrorResponse } from "@/api"; interface NewWorkspaceDialogProps { onCreateSuccess: () => void; } const NewWorkspaceFormSchema = object({ workspaceName: pattern(string(), /^[\w-]+$/), image: nonempty(string()), runtime: nonempty(string()), }); function NewWorkspaceDialog({ onCreateSuccess }: NewWorkspaceDialogProps) { const { data: templateImages, isLoading: isLoadingImages, error, } = useTemplateImages(); const { data: runtimes, isLoading: isLoadingRuntimes } = useWorkspaceRuntimes(); const isLoading = isLoadingImages || isLoadingRuntimes; function content() { if (error) { console.log(error); return (

An error occurred when fetching available options.

); } if (isLoading) { return (
); } if (!templateImages || !runtimes) { return null; } if (templateImages.length === 0) { return ( <>

No images found. Create and build a template, and the resulting image will show up here.

What are images? An image is used to bootstrap a workspace, including the operating system, the environment, and packages, as specified by a template. ); } return ( ); } return ( New workspace {content()} ); } function NewWorkspaceForm({ templateImages, runtimes, onCreateSuccess, }: { templateImages: TemplateImage[]; runtimes: WorkspaceRuntime[]; onCreateSuccess: () => void; }) { const form = useForm({ resolver: superstructResolver(NewWorkspaceFormSchema), defaultValues: { workspaceName: "", // image is in the form "imageTag imageId" (space as separator) // this is to prevent two image tags pointing to the same image id image: "", runtime: "", }, }); const { createWorkspace, status } = useCreateWorkspace(); const { toast } = useToast(); const formRef = useRef(null); useEffect(() => { switch (status.type) { case "error": if (isApiErrorResponse(status.error)) { let toastTitle = ""; switch (status.error.code) { case API_ERROR_WORKSPACE_EXISTS: toastTitle = "Workspace already exists."; break; default: toastTitle = "Failed to create the workspace."; break; } toast({ variant: "destructive", title: toastTitle, description: status.error.error, }); } else { toast({ variant: "destructive", title: "Failed to create the workspace.", description: "Unknown error", action: ( { formRef.current?.requestSubmit(); }} altText="Try again" > Try again ), }); } break; case "ok": onCreateSuccess(); break; default: break; } }, [status, toast, onCreateSuccess]); async function onSubmit(values: Infer) { await createWorkspace({ workspaceName: values.workspaceName, imageId: values.image.split(" ")[1], runtime: values.runtime, }); } return (
( Workspace name Must only contain alphanumeric characters and "-". )} /> ( Image for this workspace )} /> ( Docker runtime )} /> ); } export { NewWorkspaceDialog };