From a3b2a476c18bcf093b998d4b9d211fa66dd212e1 Mon Sep 17 00:00:00 2001 From: Bram Suurd <78373894+BramSuurdje@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:34:10 +0100 Subject: [PATCH] Add calendar and label components; enhance JSON generator with date selection and script path updates for installation methods --- frontend/package-lock.json | 88 +++++ frontend/package.json | 4 + frontend/src/app/json-editor/page.tsx | 395 ++++++++++++-------- frontend/src/components/ui/calendar.tsx | 66 ++++ frontend/src/components/ui/label.tsx | 26 ++ frontend/src/components/ui/multi-select.tsx | 387 +++++++++++++++++++ frontend/src/components/ui/popover.tsx | 31 ++ 7 files changed, 848 insertions(+), 149 deletions(-) create mode 100644 frontend/src/components/ui/calendar.tsx create mode 100644 frontend/src/components/ui/label.tsx create mode 100644 frontend/src/components/ui/multi-select.tsx create mode 100644 frontend/src/components/ui/popover.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ed6e0b48..851edf9b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,7 +13,9 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", @@ -24,6 +26,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "date-fns": "^4.1.0", "framer-motion": "^11.11.11", "fuse.js": "^7.0.0", "lucide-react": "^0.453.0", @@ -35,6 +38,7 @@ "prettier-plugin-organize-imports": "^4.1.0", "react": "19.0.0-rc-02c0e824-20241028", "react-code-blocks": "^0.1.6", + "react-day-picker": "8.10.1", "react-dom": "19.0.0-rc-02c0e824-20241028", "react-icons": "^5.1.0", "react-simple-typewriter": "^5.0.1", @@ -1371,6 +1375,29 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", @@ -1447,6 +1474,43 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", + "integrity": "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", @@ -3016,6 +3080,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -6116,6 +6190,20 @@ "react": ">=16" } }, + "node_modules/react-day-picker": { + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", + "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "date-fns": "^2.28.0 || ^3.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "19.0.0-rc-02c0e824-20241028", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0-rc-02c0e824-20241028.tgz", diff --git a/frontend/package.json b/frontend/package.json index 56807210..4d09c074 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,7 +23,9 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", @@ -34,6 +36,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "date-fns": "^4.1.0", "framer-motion": "^11.11.11", "fuse.js": "^7.0.0", "lucide-react": "^0.453.0", @@ -45,6 +48,7 @@ "prettier-plugin-organize-imports": "^4.1.0", "react": "19.0.0-rc-02c0e824-20241028", "react-code-blocks": "^0.1.6", + "react-day-picker": "8.10.1", "react-dom": "19.0.0-rc-02c0e824-20241028", "react-icons": "^5.1.0", "react-simple-typewriter": "^5.0.1", diff --git a/frontend/src/app/json-editor/page.tsx b/frontend/src/app/json-editor/page.tsx index 24b8f452..18aa5aa4 100644 --- a/frontend/src/app/json-editor/page.tsx +++ b/frontend/src/app/json-editor/page.tsx @@ -2,7 +2,9 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; +import { Calendar } from "@/components/ui/calendar"; import { Input } from "@/components/ui/input"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Select, SelectContent, @@ -14,9 +16,13 @@ import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { fetchCategories } from "@/lib/data"; import { Category } from "@/lib/types"; -import { PlusCircle, Trash2 } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { CalendarIcon, Check, Clipboard, PlusCircle, Trash2 } from "lucide-react"; import { useEffect, useState } from "react"; +import { toast } from "sonner"; import { z } from "zod"; +import { format } from "date-fns"; +import { Label } from "@/components/ui/label"; const scriptSchema = z.object({ name: z.string().min(1), @@ -96,13 +102,14 @@ export default function JSONGenerator() { }, ], }); + const [isCopied, setIsCopied] = useState(false) const [isValid, setIsValid] = useState(false); const [categories, setCategories] = useState([]); useEffect(() => { fetchCategories() - .then((data: Category[]) => { + .then((data) => { setCategories(data); }) .catch((error) => console.error("Error fetching categories:", error)); @@ -111,6 +118,18 @@ export default function JSONGenerator() { const updateScript = (key: keyof Script, value: Script[keyof Script]) => { setScript((prev) => { const updated = { ...prev, [key]: value }; + + // Update script paths for install methods if `type` or `slug` changed + if (key === "type" || key === "slug") { + updated.install_methods = updated.install_methods.map((method) => ({ + ...method, + script: + method.type === "alpine" + ? `/${updated.type}/alpine-${updated.slug}.sh` + : `/${updated.type}/${updated.slug}.sh`, + })); + } + const result = scriptSchema.safeParse(updated); setIsValid(result.success); return updated; @@ -118,23 +137,23 @@ export default function JSONGenerator() { }; const addInstallMethod = () => { - setScript((prev) => ({ - ...prev, - install_methods: [ - ...prev.install_methods, - { - type: "default", - script: "", - resources: { - cpu: null, - ram: null, - hdd: null, - os: null, - version: null, - }, + setScript((prev) => { + const method = { + type: "default" as "default", // Ensure type matches the union type + script: `/${prev.type}/${prev.slug}.sh`, // Default script path + resources: { + cpu: null, + ram: null, + hdd: null, + os: null, + version: null, }, - ], - })); + }; + return { + ...prev, + install_methods: [...prev.install_methods, method], + }; + }); }; const updateInstallMethod = ( @@ -143,12 +162,28 @@ export default function JSONGenerator() { value: Script["install_methods"][number][keyof Script["install_methods"][number]], ) => { setScript((prev) => { + const updatedMethods = prev.install_methods.map((method, i) => { + if (i === index) { + const updatedMethod = { ...method, [key]: value }; + + // Update script path if `type` of the install method changes + if (key === "type") { + updatedMethod.script = + value === "alpine" + ? `/${prev.type}/alpine-${prev.slug}.sh` + : `/${prev.type}/${prev.slug}.sh`; + } + + return updatedMethod; + } + return method; + }); + const updated = { ...prev, - install_methods: prev.install_methods.map((method, i) => - i === index ? { ...method, [key]: value } : method, - ), + install_methods: updatedMethods, }; + const result = scriptSchema.safeParse(updated); setIsValid(result.success); return updated; @@ -209,24 +244,33 @@ export default function JSONGenerator() { }; return ( -
+

JSON Generator

+
+ updateScript("name", e.target.value)} + /> + updateScript("slug", e.target.value)} + /> +
updateScript("name", e.target.value)} + placeholder="Logo URL" + value={script.logo || ""} + onChange={(e) => updateScript("logo", e.target.value || null)} /> - updateScript("slug", e.target.value)} +