From d3a54172b5df4f9a68a94622146879baa92e3354 Mon Sep 17 00:00:00 2001 From: Jhon Date: Sat, 26 Jul 2025 13:53:28 -0300 Subject: [PATCH 1/3] feat(ux): add conditional server selection functionality to application forms --- .../dashboard/project/add-application.tsx | 468 +++---- .../dashboard/project/add-compose.tsx | 534 ++++---- .../dashboard/project/add-database.tsx | 1072 +++++++++-------- .../dashboard/project/add-template.tsx | 966 +++++++-------- .../dashboard/project/ai/step-one.tsx | 65 +- 5 files changed, 1562 insertions(+), 1543 deletions(-) diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx index 9ed31464a..bdf010dc5 100644 --- a/apps/dokploy/components/dashboard/project/add-application.tsx +++ b/apps/dokploy/components/dashboard/project/add-application.tsx @@ -1,256 +1,260 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Textarea } from "@/components/ui/textarea"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { slugify } from "@/lib/slug"; -import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { Folder, HelpCircle } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { slugify } from "@/lib/slug"; +import { api } from "@/utils/api"; const AddTemplateSchema = z.object({ - name: z.string().min(1, { - message: "Name is required", - }), - appName: z - .string() - .min(1, { - message: "App name is required", - }) - .regex(/^[a-z](?!.*--)([a-z0-9-]*[a-z])?$/, { - message: - "App name supports lowercase letters, numbers, '-' and can only start and end letters, and does not support continuous '-'", - }), - description: z.string().optional(), - serverId: z.string().optional(), + name: z.string().min(1, { + message: "Name is required", + }), + appName: z + .string() + .min(1, { + message: "App name is required", + }) + .regex(/^[a-z](?!.*--)([a-z0-9-]*[a-z])?$/, { + message: + "App name supports lowercase letters, numbers, '-' and can only start and end letters, and does not support continuous '-'", + }), + description: z.string().optional(), + serverId: z.string().optional(), }); type AddTemplate = z.infer; interface Props { - projectId: string; - projectName?: string; + projectId: string; + projectName?: string; } export const AddApplication = ({ projectId, projectName }: Props) => { - const utils = api.useUtils(); - const { data: isCloud } = api.settings.isCloud.useQuery(); - const [visible, setVisible] = useState(false); - const slug = slugify(projectName); - const { data: servers } = api.server.withSSHKey.useQuery(); + const utils = api.useUtils(); + const { data: isCloud } = api.settings.isCloud.useQuery(); + const [visible, setVisible] = useState(false); + const slug = slugify(projectName); + const { data: servers } = api.server.withSSHKey.useQuery(); - const { mutateAsync, isLoading, error, isError } = - api.application.create.useMutation(); + const hasServers = servers && servers.length > 0; - const form = useForm({ - defaultValues: { - name: "", - appName: `${slug}-`, - description: "", - }, - resolver: zodResolver(AddTemplateSchema), - }); + const { mutateAsync, isLoading, error, isError } = + api.application.create.useMutation(); - const onSubmit = async (data: AddTemplate) => { - await mutateAsync({ - name: data.name, - appName: data.appName, - description: data.description, - projectId, - serverId: data.serverId, - }) - .then(async () => { - toast.success("Service Created"); - form.reset(); - setVisible(false); - await utils.project.one.invalidate({ - projectId, - }); - }) - .catch(() => { - toast.error("Error creating the service"); - }); - }; + const form = useForm({ + defaultValues: { + name: "", + appName: `${slug}-`, + description: "", + }, + resolver: zodResolver(AddTemplateSchema), + }); - return ( - - - e.preventDefault()} - > - - Application - - - - - Create - - Assign a name and description to your application - - - {isError && {error?.message}} -
- - ( - - Name - - { - const val = e.target.value?.trim() || ""; - const serviceName = slugify(val); - form.setValue("appName", `${slug}-${serviceName}`); - field.onChange(val); - }} - /> - - - - )} - /> - ( - - - - - - Select a Server {!isCloud ? "(Optional)" : ""} - - - - - - If no server is selected, the application will be - deployed on the server where the user is logged in. - - - - + const onSubmit = async (data: AddTemplate) => { + await mutateAsync({ + name: data.name, + appName: data.appName, + description: data.description, + projectId, + serverId: data.serverId, + }) + .then(async () => { + toast.success("Service Created"); + form.reset(); + setVisible(false); + await utils.project.one.invalidate({ + projectId, + }); + }) + .catch(() => { + toast.error("Error creating the service"); + }); + }; - - - - )} - /> - ( - - App Name - - - - - - )} - /> - ( - - Description - -