From 55a09ffd20ccbb47ad17d31ceb59119e162c277b Mon Sep 17 00:00:00 2001 From: vibeasite-bot Date: Sat, 22 Aug 2026 06:22:02 +0100 Subject: [PATCH] bake canonical build harness (deterministic /ship for forks) [photography] --- .gitignore | 22 + Dockerfile | 15 + index.html | 13 + nginx.conf | 17 + package.json | 73 +++ postcss.config.js | 6 + src/components/DataForm.tsx | 195 ++++++++ src/components/DataTable.tsx | 214 +++++++++ src/components/KpiCard.tsx | 124 +++++ src/components/ScrollHeader.tsx | 125 +++++ src/components/ThemeToggle.tsx | 19 + src/components/ui/accordion.tsx | 72 +-- src/components/ui/alert-dialog.tsx | 104 +++++ src/components/ui/alert.tsx | 43 ++ src/components/ui/aspect-ratio.tsx | 5 + src/components/ui/avatar.tsx | 38 ++ src/components/ui/badge.tsx | 50 +- src/components/ui/breadcrumb.tsx | 90 ++++ src/components/ui/button.tsx | 84 ++-- src/components/ui/calendar.tsx | 54 +++ src/components/ui/card.tsx | 44 +- src/components/ui/carousel.tsx | 224 +++++++++ src/components/ui/chart.tsx | 303 ++++++++++++ src/components/ui/checkbox.tsx | 26 ++ src/components/ui/collapsible.tsx | 9 + src/components/ui/command.tsx | 132 ++++++ src/components/ui/context-menu.tsx | 178 +++++++ src/components/ui/dialog.tsx | 95 ++++ src/components/ui/drawer.tsx | 87 ++++ src/components/ui/dropdown-menu.tsx | 179 ++++++++ src/components/ui/form.tsx | 129 ++++++ src/components/ui/hover-card.tsx | 27 ++ src/components/ui/input-otp.tsx | 61 +++ src/components/ui/input.tsx | 29 +- src/components/ui/label.tsx | 17 +- src/components/ui/menubar.tsx | 207 +++++++++ src/components/ui/navigation-menu.tsx | 120 +++++ src/components/ui/pagination.tsx | 81 ++++ src/components/ui/popover.tsx | 29 ++ src/components/ui/progress.tsx | 23 + src/components/ui/radio-group.tsx | 36 ++ src/components/ui/resizable.tsx | 37 ++ src/components/ui/scroll-area.tsx | 38 ++ src/components/ui/select.tsx | 191 +++++--- src/components/ui/separator.tsx | 20 + src/components/ui/sheet.tsx | 139 ++++-- src/components/ui/sidebar.tsx | 637 ++++++++++++++++++++++++++ src/components/ui/skeleton.tsx | 7 + src/components/ui/slider.tsx | 23 + src/components/ui/sonner.tsx | 27 ++ src/components/ui/switch.tsx | 27 ++ src/components/ui/table.tsx | 72 +++ src/components/ui/tabs.tsx | 85 ++-- src/components/ui/textarea.tsx | 19 +- src/components/ui/toast.tsx | 111 +++++ src/components/ui/toaster.tsx | 24 + src/components/ui/toggle-group.tsx | 49 ++ src/components/ui/toggle.tsx | 37 ++ src/components/ui/tooltip.tsx | 28 ++ src/components/ui/use-toast.ts | 3 + src/contexts/AuthContext.tsx | 234 ++++++++++ src/hooks/use-anydb.ts | 292 ++++++++++++ src/hooks/use-mobile.tsx | 19 + src/hooks/use-oddsockets.ts | 161 +++++++ src/hooks/use-toast.ts | 186 ++++++++ src/hooks/use-tygarun.ts | 330 +++++++++++++ src/index.css | 114 +++++ src/lib/anydb.ts | 21 + src/lib/oddsockets.ts | 121 +++++ src/lib/safe-icons.ts | 89 ++++ src/lib/tygarun.ts | 115 +++++ src/lib/utils.ts | 9 +- src/main.tsx | 15 + tailwind.config.ts | 93 ++++ tsconfig.json | 37 ++ tsconfig.node.json | 12 + vite.config.ts | 16 + 77 files changed, 6553 insertions(+), 284 deletions(-) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 index.html create mode 100644 nginx.conf create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 src/components/DataForm.tsx create mode 100644 src/components/DataTable.tsx create mode 100644 src/components/KpiCard.tsx create mode 100644 src/components/ScrollHeader.tsx create mode 100644 src/components/ThemeToggle.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/aspect-ratio.tsx create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/breadcrumb.tsx create mode 100644 src/components/ui/calendar.tsx create mode 100644 src/components/ui/carousel.tsx create mode 100644 src/components/ui/chart.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/collapsible.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/context-menu.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/drawer.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/form.tsx create mode 100644 src/components/ui/hover-card.tsx create mode 100644 src/components/ui/input-otp.tsx create mode 100644 src/components/ui/menubar.tsx create mode 100644 src/components/ui/navigation-menu.tsx create mode 100644 src/components/ui/pagination.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/radio-group.tsx create mode 100644 src/components/ui/resizable.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/sidebar.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/slider.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/toast.tsx create mode 100644 src/components/ui/toaster.tsx create mode 100644 src/components/ui/toggle-group.tsx create mode 100644 src/components/ui/toggle.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/components/ui/use-toast.ts create mode 100644 src/contexts/AuthContext.tsx create mode 100644 src/hooks/use-anydb.ts create mode 100644 src/hooks/use-mobile.tsx create mode 100644 src/hooks/use-oddsockets.ts create mode 100644 src/hooks/use-toast.ts create mode 100644 src/hooks/use-tygarun.ts create mode 100644 src/index.css create mode 100644 src/lib/anydb.ts create mode 100644 src/lib/oddsockets.ts create mode 100644 src/lib/safe-icons.ts create mode 100644 src/lib/tygarun.ts create mode 100644 src/main.tsx create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ed88a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Logs +logs +*.log +npm-debug.log* + +# Dependencies +node_modules/ + +# Build +dist/ +dist-ssr/ + +# Editor +.vscode/ +.idea/ + +# OS +.DS_Store + +# Env +.env +.env.local \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2f59e25 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile:1 +# ── Build stage: compile the Vite/React app ── +FROM node:20-alpine AS build +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm install --no-audit --no-fund +COPY . . +RUN npm run build + +# ── Serve stage: static nginx serving dist/ with SPA fallback ── +FROM nginx:alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/index.html b/index.html new file mode 100644 index 0000000..79d495e --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Uphotography — vibeasite template + + +
+ + + \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..ee72d86 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,17 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # SPA fallback — client-side routing (react-router BrowserRouter) + location / { + try_files $uri $uri/ /index.html; + } + + # Long-cache hashed assets + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9817d78 --- /dev/null +++ b/package.json @@ -0,0 +1,73 @@ +{ + "name": "template-photography", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@hookform/resolvers": "^3.10.0", + "@radix-ui/react-accordion": "^1.2.11", + "@radix-ui/react-alert-dialog": "^1.1.14", + "@radix-ui/react-aspect-ratio": "^1.1.7", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-collapsible": "^1.1.11", + "@radix-ui/react-context-menu": "^2.2.15", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-hover-card": "^1.1.14", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-menubar": "^1.1.15", + "@radix-ui/react-navigation-menu": "^1.2.13", + "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-radio-group": "^1.3.7", + "@radix-ui/react-scroll-area": "^1.2.9", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slider": "^1.3.5", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", + "@radix-ui/react-toast": "^1.2.14", + "@radix-ui/react-toggle": "^1.1.9", + "@radix-ui/react-toggle-group": "^1.1.10", + "@radix-ui/react-tooltip": "^1.2.7", + "@tanstack/react-query": "^5.83.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^3.6.0", + "embla-carousel-react": "^8.6.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.462.0", + "next-themes": "^0.3.0", + "react": "^18.3.1", + "react-day-picker": "^8.10.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.61.1", + "react-resizable-panels": "^2.1.9", + "react-router-dom": "^6.30.1", + "recharts": "^2.15.4", + "sonner": "^1.7.4", + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7", + "vaul": "^0.9.9", + "zod": "^3.25.76" + }, + "devDependencies": { + "@types/node": "^22.16.5", + "@types/react": "^18.3.23", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react-swc": "^3.11.0", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.3", + "vite": "^5.4.19" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..fbe14a4 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; \ No newline at end of file diff --git a/src/components/DataForm.tsx b/src/components/DataForm.tsx new file mode 100644 index 0000000..de1002e --- /dev/null +++ b/src/components/DataForm.tsx @@ -0,0 +1,195 @@ +// --------------------------------------------------------------------------- +// DataForm — pre-built form component wired to AnyDB insertOne +// --------------------------------------------------------------------------- +// PRE-INSTALLED. CodeWriterAgent just uses: +// import { DataForm } from "@/components/DataForm" +// +// --------------------------------------------------------------------------- + +import { useState } from "react"; +import { useMutation } from "@/hooks/use-anydb"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { useToast } from "@/hooks/use-toast"; +import { Loader2, CheckCircle } from "lucide-react"; + +interface FormField { + name: string; + label: string; + type?: "text" | "email" | "tel" | "number" | "date" | "time" | "url" | "textarea" | "select"; + placeholder?: string; + required?: boolean; + options?: { value: string; label: string }[]; // For select type + rows?: number; // For textarea + min?: number; + max?: number; + defaultValue?: string | number; + className?: string; + gridCol?: number; // 1 or 2 — for grid layout +} + +interface DataFormProps { + collection: string; + fields: FormField[]; + submitLabel?: string; + successMessage?: string; + onSuccess?: (data: any) => void; + className?: string; + layout?: "stack" | "grid"; // stack = vertical, grid = 2-col +} + +export function DataForm({ + collection, + fields, + submitLabel = "Submit", + successMessage = "Saved successfully!", + onSuccess, + className = "", + layout = "stack", +}: DataFormProps) { + const { toast } = useToast(); + const { mutate, loading } = useMutation(collection); + const [submitted, setSubmitted] = useState(false); + + // Build initial form state from fields + const initialState: Record = {}; + fields.forEach(f => { initialState[f.name] = f.defaultValue || ""; }); + const [formData, setFormData] = useState(initialState); + + const handleChange = (name: string, value: any) => { + setFormData(prev => ({ ...prev, [name]: value })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Basic validation + for (const field of fields) { + if (field.required && !formData[field.name]) { + toast({ + title: "Required field", + description: `Please fill in ${field.label}`, + variant: "destructive", + }); + return; + } + if (field.type === "email" && formData[field.name] && !formData[field.name].includes("@")) { + toast({ + title: "Invalid email", + description: "Please enter a valid email address", + variant: "destructive", + }); + return; + } + } + + try { + const result = await mutate(formData); + setSubmitted(true); + toast({ title: "Success", description: successMessage }); + setFormData(initialState); + onSuccess?.(result); + + // Reset submitted state after 3 seconds + setTimeout(() => setSubmitted(false), 3000); + } catch (err) { + toast({ + title: "Error", + description: "Something went wrong. Please try again.", + variant: "destructive", + }); + } + }; + + const renderField = (field: FormField) => { + const id = `form-${collection}-${field.name}`; + + return ( +
+ + + {field.type === "textarea" ? ( +