import { MariadbIcon, MongodbIcon, MysqlIcon, PostgresqlIcon, RedisIcon, } from "@/components/icons/data-tools-icons"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, 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 { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Textarea } from "@/components/ui/textarea"; import { slugify } from "@/lib/slug"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { Database, AlertTriangle } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; type DbType = typeof mySchema._type.type; // TODO: Change to a real docker images const dockerImageDefaultPlaceholder: Record = { mongo: "mongo:6", mariadb: "mariadb:11", mysql: "mysql:8", postgres: "postgres:15", redis: "redis:7", }; const databasesUserDefaultPlaceholder: Record< Exclude, string > = { mongo: "mongo", mariadb: "mariadb", mysql: "mysql", postgres: "postgres", }; const baseDatabaseSchema = z.object({ name: z.string().min(1, "Name 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 '-'", }), databasePassword: z.string(), dockerImage: z.string(), description: z.string().nullable(), }); const mySchema = z.discriminatedUnion("type", [ z .object({ type: z.literal("postgres"), databaseName: z.string().min(1, "Database name required"), databaseUser: z.string().default("postgres"), }) .merge(baseDatabaseSchema), z .object({ type: z.literal("mongo"), databaseUser: z.string().default("mongo"), }) .merge(baseDatabaseSchema), z .object({ type: z.literal("redis"), }) .merge(baseDatabaseSchema), z .object({ type: z.literal("mysql"), databaseRootPassword: z.string().default(""), databaseUser: z.string().default("mysql"), databaseName: z.string().min(1, "Database name required"), }) .merge(baseDatabaseSchema), z .object({ type: z.literal("mariadb"), dockerImage: z.string().default("mariadb:4"), databaseRootPassword: z.string().default(""), databaseUser: z.string().default("mariadb"), databaseName: z.string().min(1, "Database name required"), }) .merge(baseDatabaseSchema), ]); const databasesMap = { postgres: { icon: , label: "PostgreSQL", }, mongo: { icon: , label: "MongoDB", }, mariadb: { icon: , label: "MariaDB", }, mysql: { icon: , label: "MySQL", }, redis: { icon: , label: "Redis", }, }; type AddDatabase = z.infer; interface Props { projectId: string; projectName?: string; } export const AddDatabase = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); const [visible, setVisible] = useState(false); const slug = slugify(projectName); const postgresMutation = api.postgres.create.useMutation(); const mongoMutation = api.mongo.create.useMutation(); const redisMutation = api.redis.create.useMutation(); const mariadbMutation = api.mariadb.create.useMutation(); const mysqlMutation = api.mysql.create.useMutation(); const form = useForm({ defaultValues: { type: "postgres", dockerImage: "", name: "", appName: `${slug}-`, databasePassword: "", description: "", databaseName: "", databaseUser: "", }, resolver: zodResolver(mySchema), }); const type = form.watch("type"); const activeMutation = { postgres: postgresMutation, mongo: mongoMutation, redis: redisMutation, mariadb: mariadbMutation, mysql: mysqlMutation, }; const onSubmit = async (data: AddDatabase) => { const defaultDockerImage = data.dockerImage || dockerImageDefaultPlaceholder[data.type]; let promise: Promise | null = null; const commonParams = { name: data.name, appName: data.appName, dockerImage: defaultDockerImage, projectId, description: data.description, }; if (data.type === "postgres") { promise = postgresMutation.mutateAsync({ ...commonParams, databasePassword: data.databasePassword, databaseName: data.databaseName, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], }); } else if (data.type === "mongo") { promise = mongoMutation.mutateAsync({ ...commonParams, databasePassword: data.databasePassword, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], }); } else if (data.type === "redis") { promise = redisMutation.mutateAsync({ ...commonParams, databasePassword: data.databasePassword, projectId, }); } else if (data.type === "mariadb") { promise = mariadbMutation.mutateAsync({ ...commonParams, databasePassword: data.databasePassword, databaseRootPassword: data.databaseRootPassword, databaseName: data.databaseName, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], }); } else if (data.type === "mysql") { promise = mysqlMutation.mutateAsync({ ...commonParams, databasePassword: data.databasePassword, databaseName: data.databaseName, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], databaseRootPassword: data.databaseRootPassword, }); } if (promise) { await promise .then(async () => { toast.success("Database Created"); form.reset({ type: "postgres", dockerImage: "", name: "", appName: `${projectName}-`, databasePassword: "", description: "", databaseName: "", databaseUser: "", }); setVisible(false); await utils.project.one.invalidate({ projectId, }); }) .catch(() => { toast.error("Error to create a database"); }); } }; return ( e.preventDefault()} > Database Databases
( Select a database {Object.entries(databasesMap).map(([key, value]) => (
))}
{activeMutation[field.value].isError && (
{activeMutation[field.value].error?.message}
)}
)} />
Fill the next fields.
( Name { const val = e.target.value?.trim() || ""; form.setValue("appName", `${slug}-${val}`); field.onChange(val); }} /> )} /> ( AppName )} /> ( Description