diff --git a/components/dashboard/project/add-template.tsx b/components/dashboard/project/add-template.tsx index 87ae6d29..d8a52399 100644 --- a/components/dashboard/project/add-template.tsx +++ b/components/dashboard/project/add-template.tsx @@ -12,6 +12,13 @@ import { } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; import { Dialog, DialogContent, @@ -22,8 +29,23 @@ import { } from "@/components/ui/dialog"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { Code, Github, Globe, PuzzleIcon } from "lucide-react"; +import { ScrollArea } from "@radix-ui/react-scroll-area"; +import { + CheckIcon, + ChevronsUpDown, + Code, + Github, + Globe, + PuzzleIcon, + SearchIcon, +} from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { toast } from "sonner"; @@ -34,13 +56,23 @@ interface Props { export const AddTemplate = ({ projectId }: Props) => { const [query, setQuery] = useState(""); const { data } = api.compose.templates.useQuery(); + const [selectedTags, setSelectedTags] = useState([]); + const { data: tags, isLoading: isLoadingTags } = + api.compose.getTags.useQuery(); const utils = api.useUtils(); const { mutateAsync, isLoading, error, isError } = api.compose.deployTemplate.useMutation(); - const templates = data?.filter((t) => - t.name.toLowerCase().includes(query.toLowerCase()), - ); + const templates = + data?.filter((template) => { + const matchesTags = + selectedTags.length === 0 || + template.tags.some((tag) => selectedTags.includes(tag)); + const matchesQuery = + query === "" || + template.name.toLowerCase().includes(query.toLowerCase()); + return matchesTags && matchesQuery; + }) || []; return ( @@ -62,146 +94,220 @@ export const AddTemplate = ({ projectId }: Props) => { {isError && {error?.message}} - setQuery(e.target.value)} - value={query} - /> - -
-
- {templates?.map((template, index) => ( -
-
+ setQuery(e.target.value)} + className="w-full" + value={query} + /> + + + + + + + + {isLoadingTags && ( + + Loading Tags.... + + )} + No tags found. + + + {tags?.map((tag) => { + return ( + { + if (selectedTags.includes(tag)) { + setSelectedTags( + selectedTags.filter((t) => t !== tag), + ); + return; } - > - - - {template.links.website && ( - - - - )} - {template.links.docs && ( - - - - )} - - - -
-
- {template.tags.map((tag) => ( - - {tag} - - ))} -
-
- - - - - - - - - Are you absolutely sure? - - - This will deploy {template.name} template to - your project. - - - - Cancel - { - await mutateAsync({ - projectId, - id: template.id, - }) - .then(async () => { - toast.success( - `${template.name} template created succesfully`, - ); - - utils.project.one.invalidate({ - projectId, - }); - }) - .catch(() => { - toast.error( - `Error to delete ${template.name} template`, - ); - }); - }} - > - Confirm - - - - + setSelectedTags([...selectedTags, tag]); + }} + > + {tag} + + + ); + })} + + + + + +
+
+
+ {templates.length === 0 ? ( +
+ +
+ No templates found +
+
+ ) : ( +
+ {templates?.map((template, index) => ( +
+
+
+
+
-

- {template.description} -

+
+
+
+
+ + {template.name} + + {template.version} +
+ +
+ + + + {template.links.website && ( + + + + )} + {template.links.docs && ( + + + + )} + + + +
+
+ {template.tags.map((tag) => ( + + {tag} + + ))} +
+
+ + + + + + + + + Are you absolutely sure? + + + This will deploy {template.name} template to + your project. + + + + Cancel + { + await mutateAsync({ + projectId, + id: template.id, + }) + .then(async () => { + toast.success( + `${template.name} template created succesfully`, + ); + + utils.project.one.invalidate({ + projectId, + }); + }) + .catch(() => { + toast.error( + `Error to delete ${template.name} template`, + ); + }); + }} + > + Confirm + + + + +
+ +

+ {template.description} +

+
-
- ))} -
+ ))} + + )}
diff --git a/server/api/routers/compose.ts b/server/api/routers/compose.ts index 9bcb07d5..dba4c476 100644 --- a/server/api/routers/compose.ts +++ b/server/api/routers/compose.ts @@ -30,6 +30,7 @@ import { } from "@/templates/utils"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; +import _ from "lodash"; import { nanoid } from "nanoid"; import { findAdmin } from "../services/admin"; import { @@ -283,4 +284,10 @@ export const composeRouter = createTRPCRouter({ return templatesData; }), + + getTags: protectedProcedure.query(async ({ input }) => { + const allTags = templates.flatMap((template) => template.tags); + const uniqueTags = _.uniq(allTags); + return uniqueTags; + }), });