commit 37df3b33de497d0c01823a9c660de3665b28c865 Author: Builder.io Date: Tue Aug 5 20:03:34 2025 +0000 Initial commit diff --git a/.builder/rules/deploy-app.mdc b/.builder/rules/deploy-app.mdc new file mode 100644 index 00000000..1ebbb5ea --- /dev/null +++ b/.builder/rules/deploy-app.mdc @@ -0,0 +1,20 @@ +--- +description: Deploy the app to production using Netlify or Vercel to make the site live +globs: +alwaysApply: true +--- + +Deploying the app to production is powered by either Netlify or Vercel through MCP (Model Context Protocol) integrations: + +**For Netlify:** + +- If the Netlify tools are not available, tell the user to [Connect Netlify MCP](#open-mcp-popover). +- If the user asks to deploy the app, use the appropriate Netlify MCP tools to deploy the app. +- Note: Netlify builds the source code on their servers, so while it's good to check if `npm run build` works locally, it's not required. + +**For Vercel:** + +- If the Vercel tools are not available, tell the user to [Connect Vercel MCP](#open-mcp-popover). +- Vercel deployment should work automatically once connected - simply use the appropriate Vercel MCP tools. + +You can also suggest sharing the [Open Preview](#open-preview), but explain that it won't be a production-ready link. diff --git a/.builder/rules/organize-ui.mdc b/.builder/rules/organize-ui.mdc new file mode 100644 index 00000000..d23c0c5b --- /dev/null +++ b/.builder/rules/organize-ui.mdc @@ -0,0 +1,9 @@ +--- +description: Keep component and files small +globs: +alwaysApply: true +--- + +As development goes, break down complex UI into smaller components, avoiding very nested JSX trees. +Also, even within a single page (route), feel free to create auxiliary extra smaller files to make the page more maintenable. +Breaking big components into multiple files will help down the road when the user asks for changes or refactors. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..2f30b0a7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,62 @@ +node_modules +dist +.git +.gitignore +README.md +.env +.env.local +.env.development +.env.test +.env.production +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.DS_Store +.vscode +.idea +*.swp +*.swo +*~ +.cursor +coverage +.nyc_output +.eslintcache +*.log +*.lock +*.tmp +*.tmp.* +log.txt + +.DS_Store +node_modules +**/node_modules/** +build +data +.env +load-ids.txt + +server +tmp +types +.git +.gitignore +dist +service +tests +fixtures-pages +fixtures-apps + +# Netlify +.netlify +packages/ml-air/lib +packages/ml-air/bin +packages/ml-air/project +packages/ml-air/share +packages/ml-air/random_forest_classification/ +packages/ml-air/__pycache__/ +packages/ml-air/app/__pycache__/ +packages/vcp-common/native-bridge/build +packages/vcp-common/_tests_/dataset-ranking.csv +node_modules/ +Dockerfile +.gitignore \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 00000000..6658458c --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +# .env is better suited for public variables, ie, variables that should not commited +# For secret variables is better to use DevServerControl tool with set_env_variable: ["KEY", "SECRET"] + +# https://www.builder.io/c/docs/using-your-api-key +VITE_PUBLIC_BUILDER_KEY=__BUILDER_PUBLIC_KEY__ +PING_MESSAGE="ping pong" diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..adf7425e --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.config/ +!.env diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..045129fc --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..d3be6d22 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "tabWidth": 2, + "useTabs": false, + "trailingComma": "all" +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..73881cd5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,163 @@ +# Fusion Starter + +A production-ready full-stack React application template with integrated Express server, featuring React Router 6 SPA mode, TypeScript, Vitest, Zod and modern tooling. + +While the starter comes with a express server, only create endpoint when strictly neccesary, for example to encapsulate logic that must leave in the server, such as private keys handling, or certain DB operations, db... + +## Tech Stack + +- **Frontend**: React 18 + React Router 6 (spa) + TypeScript + Vite + TailwindCSS 3 +- **Backend**: Express server integrated with Vite dev server +- **Testing**: Vitest +- **UI**: Radix UI + TailwindCSS 3 + Lucide React icons + +## Project Structure + +``` +client/ # React SPA frontend +├── pages/ # Route components (Index.tsx = home) +├── components/ui/ # Pre-built UI component library +├── App.tsx # App entry point and with SPA routing setup +└── global.css # TailwindCSS 3 theming and global styles + +server/ # Express API backend +├── index.ts # Main server setup (express config + routes) +└── routes/ # API handlers + +shared/ # Types used by both client & server +└── api.ts # Example of how to share api interfaces +``` + +## Key Features + +## SPA Routing System + +The routing system is powered by React Router 6: + +- `client/pages/Index.tsx` represents the home page. +- Routes are defined in `client/App.tsx` using the `react-router-dom` import +- Route files are located in the `client/pages/` directory + +For example, routes can be defined with: + +```typescript +import { BrowserRouter, Routes, Route } from "react-router-dom"; + + + } /> + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} + } /> +; +``` + +### Styling System + +- **Primary**: TailwindCSS 3 utility classes +- **Theme and design tokens**: Configure in `client/global.css` +- **UI components**: Pre-built library in `client/components/ui/` +- **Utility**: `cn()` function combines `clsx` + `tailwind-merge` for conditional classes + +```typescript +// cn utility usage +className={cn( + "base-classes", + { "conditional-class": condition }, + props.className // User overrides +)} +``` + +### Express Server Integration + +- **Development**: Single port (8080) for both frontend/backend +- **Hot reload**: Both client and server code +- **API endpoints**: Prefixed with `/api/` + +#### Example API Routes +- `GET /api/ping` - Simple ping api +- `GET /api/demo` - Demo endpoint + +### Shared Types +Import consistent types in both client and server: +```typescript +import { DemoResponse } from '@shared/api'; +``` + +Path aliases: +- `@shared/*` - Shared folder +- `@/*` - Client folder + +## Development Commands + +```bash +npm run dev # Start dev server (client + server) +npm run build # Production build +npm run start # Start production server +npm run typecheck # TypeScript validation +npm test # Run Vitest tests +``` + +## Adding Features + +### Add new colors to the theme + +Open `client/global.css` and `tailwind.config.ts` and add new tailwind colors. + +### New API Route +1. **Optional**: Create a shared interface in `shared/api.ts`: +```typescript +export interface MyRouteResponse { + message: string; + // Add other response properties here +} +``` + +2. Create a new route handler in `server/routes/my-route.ts`: +```typescript +import { RequestHandler } from "express"; +import { MyRouteResponse } from "@shared/api"; // Optional: for type safety + +export const handleMyRoute: RequestHandler = (req, res) => { + const response: MyRouteResponse = { + message: 'Hello from my endpoint!' + }; + res.json(response); +}; +``` + +3. Register the route in `server/index.ts`: +```typescript +import { handleMyRoute } from "./routes/my-route"; + +// Add to the createServer function: +app.get("/api/my-endpoint", handleMyRoute); +``` + +4. Use in React components with type safety: +```typescript +import { MyRouteResponse } from '@shared/api'; // Optional: for type safety + +const response = await fetch('/api/my-endpoint'); +const data: MyRouteResponse = await response.json(); +``` + +### New Page Route +1. Create component in `client/pages/MyPage.tsx` +2. Add route in `client/App.tsx`: +```typescript +} /> +``` + +## Production Deployment + +- **Standard**: `npm run build` + `npm start` +- **Binary**: Self-contained executables (Linux, macOS, Windows) +- **Cloud Deployment**: Use either Netlify or Vercel via their MCP integrations for easy deployment. Both providers work well with this starter template. + +## Architecture Notes + +- Single-port development with Vite + Express integration +- TypeScript throughout (client, server, shared) +- Full hot reload for rapid development +- Production-ready with multiple deployment options +- Comprehensive UI component library included +- Type-safe API communication via shared interfaces diff --git a/client/App.tsx b/client/App.tsx new file mode 100644 index 00000000..9821ec84 --- /dev/null +++ b/client/App.tsx @@ -0,0 +1,30 @@ +import "./global.css"; + +import { Toaster } from "@/components/ui/toaster"; +import { createRoot } from "react-dom/client"; +import { Toaster as Sonner } from "@/components/ui/sonner"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import Index from "./pages/Index"; +import NotFound from "./pages/NotFound"; + +const queryClient = new QueryClient(); + +const App = () => ( + + + + + + + } /> + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} + } /> + + + + +); + +createRoot(document.getElementById("root")!).render(); diff --git a/client/components/ui/accordion.tsx b/client/components/ui/accordion.tsx new file mode 100644 index 00000000..83ff0179 --- /dev/null +++ b/client/components/ui/accordion.tsx @@ -0,0 +1,56 @@ +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDown } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Accordion = AccordionPrimitive.Root; + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AccordionItem.displayName = "AccordionItem"; + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className, + )} + {...props} + > + {children} + + + +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)); + +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/client/components/ui/alert-dialog.tsx b/client/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..23500146 --- /dev/null +++ b/client/components/ui/alert-dialog.tsx @@ -0,0 +1,139 @@ +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/client/components/ui/alert.tsx b/client/components/ui/alert.tsx new file mode 100644 index 00000000..13219e77 --- /dev/null +++ b/client/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/client/components/ui/aspect-ratio.tsx b/client/components/ui/aspect-ratio.tsx new file mode 100644 index 00000000..c9e6f4bf --- /dev/null +++ b/client/components/ui/aspect-ratio.tsx @@ -0,0 +1,5 @@ +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; + +const AspectRatio = AspectRatioPrimitive.Root; + +export { AspectRatio }; diff --git a/client/components/ui/avatar.tsx b/client/components/ui/avatar.tsx new file mode 100644 index 00000000..444b1dba --- /dev/null +++ b/client/components/ui/avatar.tsx @@ -0,0 +1,48 @@ +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "@/lib/utils"; + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/client/components/ui/badge.tsx b/client/components/ui/badge.tsx new file mode 100644 index 00000000..d3d5d604 --- /dev/null +++ b/client/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; diff --git a/client/components/ui/breadcrumb.tsx b/client/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..6934f83b --- /dev/null +++ b/client/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode; + } +>(({ ...props }, ref) =>