import { Copy, Loader2 } from "lucide-react"; import { useRouter } from "next/router"; import { useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; export type Services = { serverId?: string | null; name: string; type: | "mariadb" | "application" | "postgres" | "mysql" | "mongo" | "redis" | "compose"; description?: string | null; id: string; createdAt: string; status?: "idle" | "running" | "done" | "error"; }; interface DuplicateProjectProps { environmentId: string; services: Services[]; selectedServiceIds: string[]; } export const DuplicateProject = ({ environmentId, services, selectedServiceIds, }: DuplicateProjectProps) => { const [open, setOpen] = useState(false); const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [duplicateType, setDuplicateType] = useState("new-project"); // "new-project" or "existing-environment" const [selectedTargetProject, setSelectedTargetProject] = useState(""); const [selectedTargetEnvironment, setSelectedTargetEnvironment] = useState(""); const utils = api.useUtils(); const router = useRouter(); // Queries for project and environment selection const { data: allProjects } = api.project.all.useQuery(); const { data: selectedProjectEnvironments } = api.environment.byProjectId.useQuery( { projectId: selectedTargetProject }, { enabled: !!selectedTargetProject }, ); const selectedServices = services.filter((service) => selectedServiceIds.includes(service.id), ); const { mutateAsync: duplicateProject, isPending } = api.project.duplicate.useMutation({ onSuccess: async (newProject) => { await utils.project.all.invalidate(); // If duplicating to same project+environment, invalidate the environment query // to refresh the services list if (duplicateType === "existing-environment") { await utils.environment.one.invalidate({ environmentId: selectedTargetEnvironment, }); await utils.environment.byProjectId.invalidate({ projectId: selectedTargetProject, }); // If duplicating to the same environment we're currently viewing, // also invalidate the current environment to refresh the services list if (selectedTargetEnvironment === environmentId) { await utils.environment.one.invalidate({ environmentId }); // Also invalidate the project query to refresh the project data const projectId = router.query.projectId as string; if (projectId) { await utils.project.one.invalidate({ projectId }); } } } toast.success( duplicateType === "new-project" ? "Project duplicated successfully" : "Services duplicated successfully", ); setOpen(false); if (duplicateType === "new-project") { router.push( `/dashboard/project/${newProject?.projectId}/environment/${newProject?.environmentId}`, ); } }, onError: (error) => { toast.error(error.message); }, }); const handleDuplicate = async () => { if (duplicateType === "new-project" && !name) { toast.error("Project name is required"); return; } if (duplicateType === "existing-environment") { if (!selectedTargetProject) { toast.error("Please select a target project"); return; } if (!selectedTargetEnvironment) { toast.error("Please select a target environment"); return; } } // TODO: Update duplicate API to support targetProjectId and targetEnvironmentId await duplicateProject({ sourceEnvironmentId: selectedTargetEnvironment, name, description, includeServices: true, selectedServices: selectedServices.map((service) => ({ id: service.id, type: service.type, })), duplicateInSameProject: duplicateType === "existing-environment", }); }; return ( { setOpen(isOpen); if (!isOpen) { // Reset form when closing setName(""); setDescription(""); setDuplicateType("new-project"); setSelectedTargetProject(""); setSelectedTargetEnvironment(""); } }} > Duplicate Services Choose where to duplicate the selected services
{ setDuplicateType(value); // Reset selections when changing type if (value !== "existing-environment") { setSelectedTargetProject(""); setSelectedTargetEnvironment(""); } }} className="grid gap-2" >
{duplicateType === "new-project" && ( <>
setName(e.target.value)} placeholder="New project name" />
setDescription(e.target.value)} placeholder="Project description (optional)" />
)} {duplicateType === "existing-environment" && ( <> {allProjects?.filter((p) => p.projectId !== environmentId) .length === 0 ? (

No other projects available. Create a new project first.

) : ( <> {/* Step 1: Select Project */}
{/* Step 2: Select Environment (only show if project is selected) */} {selectedTargetProject && (
)} )} )}
{selectedServices.map((service) => (
{service.name} ({service.type})
))}
); };