import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { LoadingSpinner } from "@/components/ui/loading-spinner"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { useToast } from "@/hooks/use-toast"; import { superstructResolver } from "@hookform/resolvers/superstruct"; import { Check, Trash2, X } from "lucide-react"; import { useContext, useEffect, useId } from "react"; import { useForm } from "react-hook-form"; import { type Infer, number, object, pattern, size, string } from "superstruct"; import { create } from "zustand"; import { useAddWorkspacePort, useDeleteWorkspacePort } from "./api"; import { WorkspaceTableRowContext } from "./workspace-table"; interface PortInfoTabStore { isAddingPort: boolean; setIsAddingPort: (isAddingPort: boolean) => void; } const useStore = create()((set) => ({ isAddingPort: false, setIsAddingPort: (isAddingPort) => set({ isAddingPort }), })); function PortInfoTab() { return ( <> Subdomain Port
); } function PortInfoTableBody() { const workspace = useContext(WorkspaceTableRowContext); const ports = workspace.ports ?? []; return ( {ports.map(({ port, subdomain }) => ( {subdomain} {port} ))} ); } function AddPortButton() { const isAddingPort = useStore((state) => state.isAddingPort); const setIsAddingPort = useStore((state) => state.setIsAddingPort); if (isAddingPort) { return null; } return ( ); } const NewPortMappingForm = object({ subdomain: pattern(string(), /^[\w-]+$/), port: size(number(), 1, 65536), }); function NewPortMappingRow() { const { addWorkspacePort, status } = useAddWorkspacePort(); const workspace = useContext(WorkspaceTableRowContext); const isAddingPort = useStore((state) => state.isAddingPort); const setIsAddingPort = useStore((state) => state.setIsAddingPort); const formId = useId(); const form = useForm({ resolver: superstructResolver(NewPortMappingForm), disabled: status.type === "loading", defaultValues: { subdomain: "", port: 3000, }, }); if (!isAddingPort) { return null; } async function submitForm(values: Infer) { await addWorkspacePort(workspace.name, [ { subdomain: values.subdomain, port: values.port, }, ]); setIsAddingPort(false); } return (
( )} />
( field.onChange(value.currentTarget.valueAsNumber) } /> )} />
); } function DeletePortMappingButton({ subdomain }: { subdomain: string }) { const { deleteWorkspacePort, status } = useDeleteWorkspacePort(); const { toast } = useToast(); const workspace = useContext(WorkspaceTableRowContext); const isDeleting = status.type === "loading"; useEffect(() => { if (status.type === "error") { toast({ variant: "destructive", title: "Failed to delete port.", description: "Unexpected error.", }); } }, [status.type, toast]); async function _deleteWorkspacePort() { await deleteWorkspacePort(workspace.name, subdomain); } return ( ); } export { PortInfoTab };