+
+
+ Available Templates
+
+
+ {(templatesCount && templatesCount) || 0}
+
+
+
{
/>
{searchQuery.length > 0 ? (
setSearchQuery("")}>
-
+
) : (
-
+
)}
@@ -72,10 +83,10 @@ const Search = () => {
}}
>
-
+
Grid
-
+
List
diff --git a/app/src/components/SelectedTags.tsx b/app/src/components/SelectedTags.tsx
index feaa888..f4f7ffd 100644
--- a/app/src/components/SelectedTags.tsx
+++ b/app/src/components/SelectedTags.tsx
@@ -1,5 +1,5 @@
import { XIcon } from "lucide-react";
-import { useStore } from "../store";
+import { useStore } from "@/store";
import { Badge } from "./ui/badge";
const SelectedTags = () => {
diff --git a/app/src/components/Tags.tsx b/app/src/components/Tags.tsx
index 7dc8886..8f92970 100644
--- a/app/src/components/Tags.tsx
+++ b/app/src/components/Tags.tsx
@@ -1,4 +1,4 @@
-import { useStore } from "../store";
+import { useStore } from "@/store";
import { Badge } from "@/components/ui/badge";
const Tags = () => {
diff --git a/app/src/components/TemplateDialog.tsx b/app/src/components/TemplateDialog.tsx
new file mode 100644
index 0000000..2682072
--- /dev/null
+++ b/app/src/components/TemplateDialog.tsx
@@ -0,0 +1,266 @@
+import React from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "./ui/dialog";
+import { Button } from "./ui/button";
+import { toast } from "sonner";
+import copy from "copy-to-clipboard";
+import { CodeEditor } from "./ui/code-editor";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
+import { Label } from "./ui/label";
+import { Clipboard } from "lucide-react";
+import { Input } from "./ui/input";
+
+interface Template {
+ id: string;
+ name: string;
+ description: string;
+ version: string;
+ logo?: string;
+ links: {
+ github?: string;
+ website?: string;
+ docs?: string;
+ };
+ tags: string[];
+}
+
+interface TemplateFiles {
+ dockerCompose: string | null;
+ config: string | null;
+}
+
+interface TemplateDialogProps {
+ selectedTemplate: Template | null;
+ templateFiles: TemplateFiles | null;
+ modalLoading: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+const TemplateDialog: React.FC
= ({
+ selectedTemplate,
+ templateFiles,
+ modalLoading,
+ onOpenChange,
+}) => {
+ const getBase64Config = () => {
+ if (!templateFiles?.dockerCompose && !templateFiles?.config) return "";
+
+ const configObj = {
+ compose: templateFiles.dockerCompose || "",
+ config: templateFiles.config || "",
+ };
+
+ return btoa(JSON.stringify(configObj, null, 2));
+ };
+
+ return (
+
+ );
+};
+
+export default TemplateDialog;
diff --git a/app/src/components/TemplateGrid.tsx b/app/src/components/TemplateGrid.tsx
index 9f56eaa..34c3eeb 100644
--- a/app/src/components/TemplateGrid.tsx
+++ b/app/src/components/TemplateGrid.tsx
@@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react";
-import { Input } from "./ui/input";
import {
Card,
CardHeader,
@@ -7,23 +6,10 @@ import {
CardContent,
CardFooter,
} from "./ui/card";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
-} from "./ui/dialog";
-import { Button } from "./ui/button";
-import { toast } from "sonner";
-import copy from "copy-to-clipboard";
-import { CodeEditor } from "./ui/code-editor";
-import { useStore } from "../store";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
-import { Label } from "./ui/label";
-import { Clipboard } from "lucide-react";
+import { useStore } from "@/store";
import { Skeleton } from "./ui/skeleton";
import { cn } from "@/lib/utils";
+import TemplateDialog from "./TemplateDialog";
interface Template {
id: string;
@@ -49,7 +35,13 @@ interface TemplateGridProps {
}
const TemplateGrid: React.FC = ({ view }) => {
- const { templates, setTemplates } = useStore();
+ const {
+ templates,
+ setTemplates,
+ setTemplatesCount,
+ filteredTemplates,
+ setFilteredTemplates,
+ } = useStore();
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const searchQuery = useStore((state) => state.searchQuery);
@@ -73,6 +65,8 @@ const TemplateGrid: React.FC = ({ view }) => {
}
const data = await response.json();
setTemplates(data);
+ setFilteredTemplates(data);
+ setTemplatesCount(data.length);
setLoading(false);
} catch (err) {
setError(err instanceof Error ? err.message : "An error occurred");
@@ -81,7 +75,7 @@ const TemplateGrid: React.FC = ({ view }) => {
};
fetchTemplates();
- }, [setTemplates]);
+ }, [setTemplates, setFilteredTemplates]);
const fetchTemplateFiles = async (templateId: string) => {
setModalLoading(true);
@@ -111,32 +105,25 @@ const TemplateGrid: React.FC = ({ view }) => {
fetchTemplateFiles(template.id);
};
- const filteredTemplates = templates.filter((template) => {
- // Filter by search query
- const matchesSearch = template.name
- .toLowerCase()
- .includes(searchQuery.toLowerCase());
+ useEffect(() => {
+ const filtered = templates.filter((template) => {
+ // Filter by search query
+ const matchesSearch = template.name
+ .toLowerCase()
+ .includes(searchQuery.toLowerCase());
- // Filter by selected tags
- const matchesTags =
- selectedTags.length === 0 ||
- selectedTags.every((tag) => template.tags.includes(tag));
+ // Filter by selected tags
+ const matchesTags =
+ selectedTags.length === 0 ||
+ selectedTags.every((tag) => template.tags.includes(tag));
- return matchesSearch && matchesTags;
- });
+ return matchesSearch && matchesTags;
+ });
- console.log(filteredTemplates);
-
- const getBase64Config = () => {
- if (!templateFiles?.dockerCompose && !templateFiles?.config) return "";
-
- const configObj = {
- compose: templateFiles.dockerCompose || "",
- config: templateFiles.config || "",
- };
-
- return btoa(JSON.stringify(configObj, null, 2));
- };
+ console.log("ffiltered tem", filtered.length);
+ setTemplatesCount(filtered.length);
+ setFilteredTemplates(filtered);
+ }, [searchQuery, selectedTags]);
if (loading) {
return (
@@ -192,21 +179,28 @@ const TemplateGrid: React.FC = ({ view }) => {
" cursor-pointer hover:shadow-lg transition-all duration-200 h-full max-h-[300px]",
{
"flex-col": view === "grid",
- "flex-row": view === "rows",
+ "flex-row gap-0": view === "rows",
}
)}
>
-
-
-
- {template.name}
-
+
+
+ {template.name}
{template.description}
@@ -255,197 +249,12 @@ const TemplateGrid: React.FC = ({ view }) => {