diff --git a/.emergent/emergent.yml b/.emergent/emergent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..788fc27735d9b2a62ed0f02a00861e687ffaa1ee
--- /dev/null
+++ b/.emergent/emergent.yml
@@ -0,0 +1,3 @@
+{
+ "env_image_name": "fastapi_react_mongo_shadcn_base_image_cloud_arm:release-17042026-1"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..267ee1f97607da65c7dee6f00eb20716268673d8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,82 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# IDE and editors
+.idea/
+.vscode/
+
+# Dependencies
+node_modules/
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# Testing
+/coverage
+
+# Next.js
+/.next/
+/out/
+next-env.d.ts
+*.tsbuildinfo
+
+# Production builds
+/build
+dist/
+dist
+
+# Environment files (comprehensive coverage)
+
+*token.json*
+*credentials.json*
+
+# Logs and debug files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+dump.rdb
+
+# System files
+.DS_Store
+*.pem
+
+# Python
+__pycache__/
+*pyc*
+venv/
+.venv/
+
+# Development tools
+chainlit.md
+.chainlit
+.ipynb_checkpoints/
+.ac
+
+# Deployment
+.vercel
+
+# Data and databases
+agenthub/agents/youtube/db
+
+# Archive files and large assets
+**/*.zip
+**/*.tar.gz
+**/*.tar
+**/*.tgz
+*.pack
+*.deb
+*.dylib
+
+# Build caches
+.cache/
+
+memory/test_credentials.md
+
+# Mobile development
+android-sdk/
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3786c8adf4662171d66ebd14aca44ae1b071d91b
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# Here are your Instructions
diff --git a/backend/requirements.txt b/backend/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..42a9b3216e1dbb7d9d50853196221e979b9af546
--- /dev/null
+++ b/backend/requirements.txt
@@ -0,0 +1,27 @@
+fastapi==0.110.1
+uvicorn==0.25.0
+boto3>=1.34.129
+requests-oauthlib>=2.0.0
+cryptography>=42.0.8
+python-dotenv>=1.0.1
+pymongo==4.5.0
+pydantic>=2.6.4
+email-validator>=2.2.0
+pyjwt>=2.10.1
+bcrypt==4.1.3
+passlib>=1.7.4
+tzdata>=2024.2
+motor==3.3.1
+pytest>=8.0.0
+black>=24.1.1
+isort>=5.13.2
+flake8>=7.0.0
+mypy>=1.8.0
+python-jose>=3.3.0
+requests>=2.31.0
+pandas>=2.2.0
+numpy>=1.26.0
+python-multipart>=0.0.9
+jq>=1.6.0
+typer>=0.9.0
+emergentintegrations==0.1.0
diff --git a/backend/server.py b/backend/server.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ed97afb76c55f2e25be29af00eaa8736d914e91
--- /dev/null
+++ b/backend/server.py
@@ -0,0 +1,89 @@
+from fastapi import FastAPI, APIRouter
+from dotenv import load_dotenv
+from starlette.middleware.cors import CORSMiddleware
+from motor.motor_asyncio import AsyncIOMotorClient
+import os
+import logging
+from pathlib import Path
+from pydantic import BaseModel, Field, ConfigDict
+from typing import List
+import uuid
+from datetime import datetime, timezone
+
+
+ROOT_DIR = Path(__file__).parent
+load_dotenv(ROOT_DIR / '.env')
+
+# MongoDB connection
+mongo_url = os.environ['MONGO_URL']
+client = AsyncIOMotorClient(mongo_url)
+db = client[os.environ['DB_NAME']]
+
+# Create the main app without a prefix
+app = FastAPI()
+
+# Create a router with the /api prefix
+api_router = APIRouter(prefix="/api")
+
+
+# Define Models
+class StatusCheck(BaseModel):
+ model_config = ConfigDict(extra="ignore") # Ignore MongoDB's _id field
+
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
+ client_name: str
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
+
+class StatusCheckCreate(BaseModel):
+ client_name: str
+
+# Add your routes to the router instead of directly to app
+@api_router.get("/")
+async def root():
+ return {"message": "Hello World"}
+
+@api_router.post("/status", response_model=StatusCheck)
+async def create_status_check(input: StatusCheckCreate):
+ status_dict = input.model_dump()
+ status_obj = StatusCheck(**status_dict)
+
+ # Convert to dict and serialize datetime to ISO string for MongoDB
+ doc = status_obj.model_dump()
+ doc['timestamp'] = doc['timestamp'].isoformat()
+
+ _ = await db.status_checks.insert_one(doc)
+ return status_obj
+
+@api_router.get("/status", response_model=List[StatusCheck])
+async def get_status_checks():
+ # Exclude MongoDB's _id field from the query results
+ status_checks = await db.status_checks.find({}, {"_id": 0}).to_list(1000)
+
+ # Convert ISO string timestamps back to datetime objects
+ for check in status_checks:
+ if isinstance(check['timestamp'], str):
+ check['timestamp'] = datetime.fromisoformat(check['timestamp'])
+
+ return status_checks
+
+# Include the router in the main app
+app.include_router(api_router)
+
+app.add_middleware(
+ CORSMiddleware,
+ allow_credentials=True,
+ allow_origins=os.environ.get('CORS_ORIGINS', '*').split(','),
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+# Configure logging
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+)
+logger = logging.getLogger(__name__)
+
+@app.on_event("shutdown")
+async def shutdown_db_client():
+ client.close()
\ No newline at end of file
diff --git a/design_guidelines.json b/design_guidelines.json
new file mode 100644
index 0000000000000000000000000000000000000000..4790cb99a7bfcfcae840d53e3aa718708ab0eab2
--- /dev/null
+++ b/design_guidelines.json
@@ -0,0 +1,106 @@
+{
+ "project_name": "ForgeSight",
+ "domain": "Manufacturing / AI Quality Control / Developer Tool",
+ "theme": "Dark",
+ "archetype": "Archetype 4 (Swiss & High-Contrast) - Technical Brutalist variant",
+ "mood": "Precise, authoritative, industrial, hardware-optimized, slightly brutalist",
+ "colors": {
+ "background_base": "#0A0A0A",
+ "background_surface": "#141416",
+ "primary_accent": "#ED1C24",
+ "primary_hover": "#FF3B30",
+ "border_default": "rgba(255,255,255,0.1)",
+ "text_primary": "#FFFFFF",
+ "text_secondary": "#A1A1AA",
+ "status": {
+ "pass": "#10B981",
+ "warn": "#F59E0B",
+ "fail": "#ED1C24"
+ }
+ },
+ "typography": {
+ "headings": {
+ "family": "Chivo",
+ "weights": [
+ "300",
+ "900"
+ ],
+ "classes_h1": "text-4xl sm:text-5xl lg:text-6xl font-black tracking-tighter leading-none text-white",
+ "classes_h2": "text-2xl sm:text-3xl lg:text-4xl font-bold tracking-tight text-white",
+ "classes_h3": "text-xl sm:text-2xl font-semibold text-white"
+ },
+ "body": {
+ "family": "IBM Plex Sans",
+ "weights": [
+ "400",
+ "500"
+ ],
+ "classes": "text-base leading-relaxed tracking-normal text-zinc-300"
+ },
+ "mono": {
+ "family": "JetBrains Mono",
+ "weights": [
+ "400",
+ "700"
+ ],
+ "classes": "text-sm tracking-tight font-mono text-zinc-300"
+ },
+ "labels": {
+ "classes": "text-xs font-mono uppercase tracking-[0.2em] text-zinc-400"
+ }
+ },
+ "layout_and_spacing": {
+ "philosophy": "Industrial-HMI meets Linear. Dense information with purposeful whitespace and strict grid alignments.",
+ "bento_grid": {
+ "dashboard_mode": "Control Room Grid (Tight gaps: gap-4 or gap-6, col-span logic for metrics vs feeds)",
+ "marketing_mode": "Tetris Grid (Asymmetric, wide gaps: gap-8, col-span-full for hero elements)"
+ },
+ "spacing_scale": "Tailwind standard (p-4, p-6, p-8). Cards must use internal p-6. Footers use py-24."
+ },
+ "surfaces": {
+ "dashboards": {
+ "style": "Grid Borders (Technical Look)",
+ "rules": "Expose the skeleton. border-collapse layouts, 1px subtle borders (border-white/10), flat backgrounds, no soft shadows."
+ },
+ "agent_console": {
+ "style": "Retro-Futurism / Terminal HMI",
+ "rules": "Deep black #000000 background, neon red borders for active states, monospace fonts, CSS repeating-linear-gradient for subtle scanlines."
+ },
+ "navigation": {
+ "style": "Solid / Linear",
+ "rules": "Solid #0A0A0A background with hard 1px bottom border (border-white/10). Pill-style route switcher."
+ }
+ },
+ "components_strategy": {
+ "html_tailwind": "Use for Marketing/Hero sections (custom pure HTML/Tailwind) for maximum asymmetry.",
+ "shadcn_ui": "Use heavily customized Shadcn for App/Dashboard interactions (Tabs, Dropdowns, Tables, Cards). Override soft corners to max rounded-sm (2px) or rounded-none.",
+ "buttons": "Flat, angular. Primary CTA uses AMD Red background, white text. No rounded-full (use rounded-none or rounded-sm). Hover state must transition background to lighter red (#FF3B30).",
+ "animations": "Minimalist micro-interactions. Blinking cursor for agent streaming. Smooth fade-ins for bento cards."
+ },
+ "media_and_images": {
+ "hero_inspection": {
+ "url": "https://static.prod-images.emergentagent.com/jobs/d5829a2e-bc03-4880-adcd-73acc809a3bd/images/184a8bf32b150669152ea3aa72546730d8caad845b1b8eb0233eeb35e4255eeb.png",
+ "description": "Macro shot of industrial inspection. Use as background for the main hero section with a bg-black/60 overlay for text contrast."
+ },
+ "blueprint_architecture": {
+ "url": "https://static.prod-images.emergentagent.com/jobs/d5829a2e-bc03-4880-adcd-73acc809a3bd/images/7251062dc0e36ea4218374b05cc959bc4e6c55a2cf4789a8a2cbc38db6392916.png",
+ "description": "Abstract 3D AMD MI300X chip architecture. Use in the 'Deployment Blueprint' section."
+ },
+ "factory_feed": {
+ "url": "https://images.unsplash.com/photo-1720036237334-9263cd28c3d4?crop=entropy&cs=srgb&fm=jpg&ixid=M3w3NTY2Njl8MHwxfHNlYXJjaHwyfHxpbmR1c3RyaWFsJTIwbWFudWZhY3R1cmluZyUyMGFzc2VtYmx5JTIwbGluZSUyMGRhcmt8ZW58MHx8fHwxNzc3NjQ5MzQzfDA&ixlib=rb-4.1.0&q=85",
+ "description": "Background for Defect Feed empty states or generic manufacturing imagery."
+ }
+ },
+ "icons_and_testing": {
+ "icon_library": "lucide-react (Default, fine for technical interfaces)",
+ "testing": "All interactive elements MUST have data-testid attributes (e.g. data-testid='upload-inspection-button')."
+ },
+ "instructions_to_main_agent": [
+ "Build the app as a hybrid: Marketing Hero at the top, followed by the Dashboard Console below.",
+ "Do NOT use the color teal, purple, or generic SaaS blue anywhere.",
+ "Enforce the 'Grid Borders' surface look for the dashboard parts to make it feel like an industrial HMI.",
+ "For the Agent Console, implement a typing/streaming effect with JetBrains Mono to simulate live inference.",
+ "Use data-testid on every interactive UI piece.",
+ "In the Blueprint diagram section, use standard CSS grid to layer the visual elements over the blueprint_architecture image."
+ ]
+}
\ No newline at end of file
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..4d29575de80483b005c29bfcac5061cd2f45313e
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..58beeaccd87e230076cab531b8f418f40b6d1aeb
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,70 @@
+# Getting Started with Create React App
+
+This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
+
+## Available Scripts
+
+In the project directory, you can run:
+
+### `npm start`
+
+Runs the app in the development mode.\
+Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
+
+The page will reload when you make changes.\
+You may also see any lint errors in the console.
+
+### `npm test`
+
+Launches the test runner in the interactive watch mode.\
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+
+### `npm run build`
+
+Builds the app for production to the `build` folder.\
+It correctly bundles React in production mode and optimizes the build for the best performance.
+
+The build is minified and the filenames include the hashes.\
+Your app is ready to be deployed!
+
+See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
+
+### `npm run eject`
+
+**Note: this is a one-way operation. Once you `eject`, you can't go back!**
+
+If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+
+Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
+
+You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
+
+## Learn More
+
+You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
+
+To learn React, check out the [React documentation](https://reactjs.org/).
+
+### Code Splitting
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
+
+### Analyzing the Bundle Size
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
+
+### Making a Progressive Web App
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
+
+### Advanced Configuration
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
+
+### Deployment
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
+
+### `npm run build` fails to minify
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
diff --git a/frontend/components.json b/frontend/components.json
new file mode 100644
index 0000000000000000000000000000000000000000..ebf7e6edf3e341acbdb9b72a6d828d4837d504de
--- /dev/null
+++ b/frontend/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": false,
+ "tailwind": {
+ "config": "tailwind.config.js",
+ "css": "src/index.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
\ No newline at end of file
diff --git a/frontend/craco.config.js b/frontend/craco.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..ddbeea8880b5976f3ae5fd480e120aebd195e166
--- /dev/null
+++ b/frontend/craco.config.js
@@ -0,0 +1,100 @@
+// craco.config.js
+const path = require("path");
+require("dotenv").config();
+
+// Check if we're in development/preview mode (not production build)
+// Craco sets NODE_ENV=development for start, NODE_ENV=production for build
+const isDevServer = process.env.NODE_ENV !== "production";
+
+// Environment variable overrides
+const config = {
+ enableHealthCheck: process.env.ENABLE_HEALTH_CHECK === "true",
+};
+
+// Conditionally load health check modules only if enabled
+let WebpackHealthPlugin;
+let setupHealthEndpoints;
+let healthPluginInstance;
+
+if (config.enableHealthCheck) {
+ WebpackHealthPlugin = require("./plugins/health-check/webpack-health-plugin");
+ setupHealthEndpoints = require("./plugins/health-check/health-endpoints");
+ healthPluginInstance = new WebpackHealthPlugin();
+}
+
+let webpackConfig = {
+ eslint: {
+ configure: {
+ extends: ["plugin:react-hooks/recommended"],
+ rules: {
+ "react-hooks/rules-of-hooks": "error",
+ "react-hooks/exhaustive-deps": "warn",
+ },
+ },
+ },
+ webpack: {
+ alias: {
+ '@': path.resolve(__dirname, 'src'),
+ },
+ configure: (webpackConfig) => {
+
+ // Add ignored patterns to reduce watched directories
+ webpackConfig.watchOptions = {
+ ...webpackConfig.watchOptions,
+ ignored: [
+ '**/node_modules/**',
+ '**/.git/**',
+ '**/build/**',
+ '**/dist/**',
+ '**/coverage/**',
+ '**/public/**',
+ ],
+ };
+
+ // Add health check plugin to webpack if enabled
+ if (config.enableHealthCheck && healthPluginInstance) {
+ webpackConfig.plugins.push(healthPluginInstance);
+ }
+ return webpackConfig;
+ },
+ },
+};
+
+webpackConfig.devServer = (devServerConfig) => {
+ // Add health check endpoints if enabled
+ if (config.enableHealthCheck && setupHealthEndpoints && healthPluginInstance) {
+ const originalSetupMiddlewares = devServerConfig.setupMiddlewares;
+
+ devServerConfig.setupMiddlewares = (middlewares, devServer) => {
+ // Call original setup if exists
+ if (originalSetupMiddlewares) {
+ middlewares = originalSetupMiddlewares(middlewares, devServer);
+ }
+
+ // Setup health endpoints
+ setupHealthEndpoints(devServer, healthPluginInstance);
+
+ return middlewares;
+ };
+ }
+
+ return devServerConfig;
+};
+
+// Wrap with visual edits (automatically adds babel plugin, dev server, and overlay in dev mode)
+if (isDevServer) {
+ try {
+ const { withVisualEdits } = require("@emergentbase/visual-edits/craco");
+ webpackConfig = withVisualEdits(webpackConfig);
+ } catch (err) {
+ if (err.code === 'MODULE_NOT_FOUND' && err.message.includes('@emergentbase/visual-edits/craco')) {
+ console.warn(
+ "[visual-edits] @emergentbase/visual-edits not installed — visual editing disabled."
+ );
+ } else {
+ throw err;
+ }
+ }
+}
+
+module.exports = webpackConfig;
diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..822d8e42f9d704812ecedfbbad596044b650a257
--- /dev/null
+++ b/frontend/jsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "include": ["src"]
+}
\ No newline at end of file
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..e3ca27fdd965b2b00f7f6116610aaef74e1eebb4
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,91 @@
+{
+ "name": "frontend",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@hookform/resolvers": "^5.0.1",
+ "@radix-ui/react-accordion": "^1.2.8",
+ "@radix-ui/react-alert-dialog": "^1.1.11",
+ "@radix-ui/react-aspect-ratio": "^1.1.4",
+ "@radix-ui/react-avatar": "^1.1.7",
+ "@radix-ui/react-checkbox": "^1.2.3",
+ "@radix-ui/react-collapsible": "^1.1.8",
+ "@radix-ui/react-context-menu": "^2.2.12",
+ "@radix-ui/react-dialog": "^1.1.11",
+ "@radix-ui/react-dropdown-menu": "^2.1.12",
+ "@radix-ui/react-hover-card": "^1.1.11",
+ "@radix-ui/react-label": "^2.1.4",
+ "@radix-ui/react-menubar": "^1.1.12",
+ "@radix-ui/react-navigation-menu": "^1.2.10",
+ "@radix-ui/react-popover": "^1.1.11",
+ "@radix-ui/react-progress": "^1.1.4",
+ "@radix-ui/react-radio-group": "^1.3.4",
+ "@radix-ui/react-scroll-area": "^1.2.6",
+ "@radix-ui/react-select": "^2.2.2",
+ "@radix-ui/react-separator": "^1.1.4",
+ "@radix-ui/react-slider": "^1.3.2",
+ "@radix-ui/react-slot": "^1.2.0",
+ "@radix-ui/react-switch": "^1.2.2",
+ "@radix-ui/react-tabs": "^1.1.9",
+ "@radix-ui/react-toast": "^1.2.11",
+ "@radix-ui/react-toggle": "^1.1.6",
+ "@radix-ui/react-toggle-group": "^1.1.7",
+ "@radix-ui/react-tooltip": "^1.2.4",
+ "axios": "^1.8.4",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "^1.1.1",
+ "cra-template": "1.2.0",
+ "date-fns": "^4.1.0",
+ "embla-carousel-react": "^8.6.0",
+ "input-otp": "^1.4.2",
+ "lucide-react": "^0.507.0",
+ "next-themes": "^0.4.6",
+ "react": "^19.0.0",
+ "react-day-picker": "8.10.1",
+ "react-dom": "^19.0.0",
+ "react-hook-form": "^7.56.2",
+ "react-resizable-panels": "^3.0.1",
+ "react-router-dom": "^7.5.1",
+ "react-scripts": "5.0.1",
+ "recharts": "^3.6.0",
+ "sonner": "^2.0.3",
+ "tailwind-merge": "^3.2.0",
+ "tailwindcss-animate": "^1.0.7",
+ "vaul": "^1.1.2",
+ "zod": "^3.24.4"
+ },
+ "scripts": {
+ "start": "craco start",
+ "build": "craco build",
+ "test": "craco test"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
+ "@craco/craco": "^7.1.0",
+ "@emergentbase/visual-edits": "https://assets.emergent.sh/npm/emergentbase-visual-edits-1.0.8.tgz",
+ "@eslint/js": "9.23.0",
+ "autoprefixer": "^10.4.20",
+ "eslint": "9.23.0",
+ "eslint-plugin-import": "2.31.0",
+ "eslint-plugin-jsx-a11y": "6.10.2",
+ "eslint-plugin-react": "7.37.4",
+ "eslint-plugin-react-hooks": "5.2.0",
+ "globals": "15.15.0",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^3.4.17"
+ },
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
+}
diff --git a/frontend/plugins/health-check/health-endpoints.js b/frontend/plugins/health-check/health-endpoints.js
new file mode 100644
index 0000000000000000000000000000000000000000..5af66cfc6dabdc40d2264b0cecf00de470084e6c
--- /dev/null
+++ b/frontend/plugins/health-check/health-endpoints.js
@@ -0,0 +1,213 @@
+// health-endpoints.js
+// API endpoints for health checks and monitoring
+
+const os = require('os');
+
+const SERVER_START_TIME = Date.now();
+
+/**
+ * Setup health check endpoints on the dev server
+ * @param {Object} devServer - Webpack dev server instance
+ * @param {Object} healthPlugin - Instance of WebpackHealthPlugin
+ */
+function setupHealthEndpoints(devServer, healthPlugin) {
+ if (!devServer || !devServer.app) {
+ console.warn('[Health Check] Dev server not available, skipping health endpoints');
+ return;
+ }
+
+ if (!healthPlugin) {
+ console.warn('[Health Check] Health plugin not provided, skipping health endpoints');
+ return;
+ }
+
+ console.log('[Health Check] Setting up health endpoints...');
+
+ // ====================================================================
+ // GET /health - Detailed health status (JSON)
+ // ====================================================================
+ devServer.app.get("/health", (req, res) => {
+ const webpackStatus = healthPlugin.getStatus();
+ const uptime = Date.now() - SERVER_START_TIME;
+ const memUsage = process.memoryUsage();
+
+ res.json({
+ status: webpackStatus.isHealthy ? 'healthy' : 'unhealthy',
+ timestamp: new Date().toISOString(),
+ uptime: {
+ seconds: Math.floor(uptime / 1000),
+ formatted: formatDuration(uptime),
+ },
+ webpack: {
+ state: webpackStatus.state,
+ isHealthy: webpackStatus.isHealthy,
+ hasCompiled: webpackStatus.hasCompiled,
+ errors: webpackStatus.errorCount,
+ warnings: webpackStatus.warningCount,
+ lastCompileTime: webpackStatus.lastCompileTime
+ ? new Date(webpackStatus.lastCompileTime).toISOString()
+ : null,
+ lastSuccessTime: webpackStatus.lastSuccessTime
+ ? new Date(webpackStatus.lastSuccessTime).toISOString()
+ : null,
+ compileDuration: webpackStatus.compileDuration
+ ? `${webpackStatus.compileDuration}ms`
+ : null,
+ totalCompiles: webpackStatus.totalCompiles,
+ firstCompileTime: webpackStatus.firstCompileTime
+ ? new Date(webpackStatus.firstCompileTime).toISOString()
+ : null,
+ },
+ server: {
+ nodeVersion: process.version,
+ platform: os.platform(),
+ arch: os.arch(),
+ cpus: os.cpus().length,
+ memory: {
+ heapUsed: formatBytes(memUsage.heapUsed),
+ heapTotal: formatBytes(memUsage.heapTotal),
+ rss: formatBytes(memUsage.rss),
+ external: formatBytes(memUsage.external),
+ },
+ systemMemory: {
+ total: formatBytes(os.totalmem()),
+ free: formatBytes(os.freemem()),
+ used: formatBytes(os.totalmem() - os.freemem()),
+ },
+ },
+ environment: process.env.NODE_ENV || 'development',
+ });
+ });
+
+ // ====================================================================
+ // GET /health/simple - Simple text response (OK/COMPILING/ERROR)
+ // ====================================================================
+ devServer.app.get("/health/simple", (req, res) => {
+ const webpackStatus = healthPlugin.getSimpleStatus();
+
+ if (webpackStatus.state === 'success') {
+ res.status(200).send('OK');
+ } else if (webpackStatus.state === 'compiling') {
+ res.status(200).send('COMPILING');
+ } else if (webpackStatus.state === 'idle') {
+ res.status(200).send('IDLE');
+ } else {
+ res.status(503).send('ERROR');
+ }
+ });
+
+ // ====================================================================
+ // GET /health/ready - Readiness check (Kubernetes/load balancer)
+ // ====================================================================
+ devServer.app.get("/health/ready", (req, res) => {
+ const webpackStatus = healthPlugin.getSimpleStatus();
+
+ if (webpackStatus.state === 'success') {
+ res.status(200).json({
+ ready: true,
+ state: webpackStatus.state,
+ });
+ } else {
+ res.status(503).json({
+ ready: false,
+ state: webpackStatus.state,
+ reason: webpackStatus.state === 'compiling'
+ ? 'Compilation in progress'
+ : 'Compilation failed',
+ });
+ }
+ });
+
+ // ====================================================================
+ // GET /health/live - Liveness check (Kubernetes)
+ // ====================================================================
+ devServer.app.get("/health/live", (req, res) => {
+ res.status(200).json({
+ alive: true,
+ timestamp: new Date().toISOString(),
+ });
+ });
+
+ // ====================================================================
+ // GET /health/errors - Get current errors and warnings
+ // ====================================================================
+ devServer.app.get("/health/errors", (req, res) => {
+ const webpackStatus = healthPlugin.getStatus();
+
+ res.json({
+ errorCount: webpackStatus.errorCount,
+ warningCount: webpackStatus.warningCount,
+ errors: webpackStatus.errors,
+ warnings: webpackStatus.warnings,
+ state: webpackStatus.state,
+ });
+ });
+
+ // ====================================================================
+ // GET /health/stats - Compilation statistics
+ // ====================================================================
+ devServer.app.get("/health/stats", (req, res) => {
+ const webpackStatus = healthPlugin.getStatus();
+ const uptime = Date.now() - SERVER_START_TIME;
+
+ res.json({
+ totalCompiles: webpackStatus.totalCompiles,
+ averageCompileTime: webpackStatus.totalCompiles > 0
+ ? `${Math.round(uptime / webpackStatus.totalCompiles)}ms`
+ : null,
+ lastCompileDuration: webpackStatus.compileDuration
+ ? `${webpackStatus.compileDuration}ms`
+ : null,
+ firstCompileTime: webpackStatus.firstCompileTime
+ ? new Date(webpackStatus.firstCompileTime).toISOString()
+ : null,
+ serverUptime: formatDuration(uptime),
+ });
+ });
+
+ console.log('[Health Check] ✓ Health endpoints ready:');
+ console.log(' • GET /health - Detailed status');
+ console.log(' • GET /health/simple - Simple OK/ERROR');
+ console.log(' • GET /health/ready - Readiness check');
+ console.log(' • GET /health/live - Liveness check');
+ console.log(' • GET /health/errors - Error details');
+ console.log(' • GET /health/stats - Statistics');
+}
+
+// ====================================================================
+// Helper Functions
+// ====================================================================
+
+/**
+ * Format bytes to human-readable string
+ * @param {number} bytes
+ * @returns {string}
+ */
+function formatBytes(bytes) {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
+}
+
+/**
+ * Format duration to human-readable string
+ * @param {number} ms - Duration in milliseconds
+ * @returns {string}
+ */
+function formatDuration(ms) {
+ const seconds = Math.floor(ms / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+
+ if (hours > 0) {
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
+ } else if (minutes > 0) {
+ return `${minutes}m ${seconds % 60}s`;
+ } else {
+ return `${seconds}s`;
+ }
+}
+
+module.exports = setupHealthEndpoints;
diff --git a/frontend/plugins/health-check/webpack-health-plugin.js b/frontend/plugins/health-check/webpack-health-plugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..4efbce72ef278f9bb2db8e27bcde4b01c7c68776
--- /dev/null
+++ b/frontend/plugins/health-check/webpack-health-plugin.js
@@ -0,0 +1,120 @@
+// webpack-health-plugin.js
+// Webpack plugin that tracks compilation state and health metrics
+
+class WebpackHealthPlugin {
+ constructor() {
+ this.status = {
+ state: 'idle', // idle, compiling, success, failed
+ errors: [],
+ warnings: [],
+ lastCompileTime: null,
+ lastSuccessTime: null,
+ compileDuration: 0,
+ totalCompiles: 0,
+ firstCompileTime: null,
+ };
+ }
+
+ apply(compiler) {
+ const pluginName = 'WebpackHealthPlugin';
+
+ // Hook: Compilation started
+ compiler.hooks.compile.tap(pluginName, () => {
+ const now = Date.now();
+ this.status.state = 'compiling';
+ this.status.lastCompileTime = now;
+
+ if (!this.status.firstCompileTime) {
+ this.status.firstCompileTime = now;
+ }
+ });
+
+ // Hook: Compilation completed
+ compiler.hooks.done.tap(pluginName, (stats) => {
+ const info = stats.toJson({
+ all: false,
+ errors: true,
+ warnings: true,
+ });
+
+ this.status.totalCompiles++;
+ this.status.compileDuration = Date.now() - this.status.lastCompileTime;
+
+ if (stats.hasErrors()) {
+ this.status.state = 'failed';
+ this.status.errors = info.errors.map(err => ({
+ message: err.message || String(err),
+ stack: err.stack,
+ moduleName: err.moduleName,
+ loc: err.loc,
+ }));
+ } else {
+ this.status.state = 'success';
+ this.status.lastSuccessTime = Date.now();
+ this.status.errors = [];
+ }
+
+ if (stats.hasWarnings()) {
+ this.status.warnings = info.warnings.map(warn => ({
+ message: warn.message || String(warn),
+ moduleName: warn.moduleName,
+ loc: warn.loc,
+ }));
+ } else {
+ this.status.warnings = [];
+ }
+ });
+
+ // Hook: Compilation failed
+ compiler.hooks.failed.tap(pluginName, (error) => {
+ this.status.state = 'failed';
+ this.status.errors = [{
+ message: error.message,
+ stack: error.stack,
+ }];
+ this.status.compileDuration = Date.now() - this.status.lastCompileTime;
+ });
+
+ // Hook: Invalid (file changed, recompiling)
+ compiler.hooks.invalid.tap(pluginName, () => {
+ this.status.state = 'compiling';
+ });
+ }
+
+ getStatus() {
+ return {
+ ...this.status,
+ // Add computed fields
+ isHealthy: this.status.state === 'success',
+ errorCount: this.status.errors.length,
+ warningCount: this.status.warnings.length,
+ hasCompiled: this.status.totalCompiles > 0,
+ };
+ }
+
+ // Get simplified status for quick checks
+ getSimpleStatus() {
+ return {
+ state: this.status.state,
+ isHealthy: this.status.state === 'success',
+ errorCount: this.status.errors.length,
+ warningCount: this.status.warnings.length,
+ };
+ }
+
+ // Reset statistics (useful for testing)
+ reset() {
+ this.status = {
+ state: 'idle',
+ errors: [],
+ warnings: [],
+ lastCompileTime: null,
+ lastSuccessTime: null,
+ compileDuration: 0,
+ totalCompiles: 0,
+ firstCompileTime: null,
+ };
+ }
+}
+
+module.exports = WebpackHealthPlugin;
diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..33ad091d26d8a9dc95ebdf616e217d985ec215b8
--- /dev/null
+++ b/frontend/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/frontend/public/index.html b/frontend/public/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..427ea43b33806c327610bde0eccca5e4eed4e7b4
--- /dev/null
+++ b/frontend/public/index.html
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Emergent | Fullstack App
+
+
+
+
+
+
+
+
+
+
+ Made with Emergent
+
+
+
+
+
diff --git a/frontend/src/App.css b/frontend/src/App.css
new file mode 100644
index 0000000000000000000000000000000000000000..6bfdb4eae6eb72def093ae4b7de1a601088a7bd4
--- /dev/null
+++ b/frontend/src/App.css
@@ -0,0 +1,34 @@
+.App-logo {
+ height: 40vmin;
+ pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ }
+}
+
+.App-header {
+ background-color: #0f0f10;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: white;
+}
+
+.App-link {
+ color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/frontend/src/App.js b/frontend/src/App.js
new file mode 100644
index 0000000000000000000000000000000000000000..3ece752ec477029d40862188437fd1b671e4d118
--- /dev/null
+++ b/frontend/src/App.js
@@ -0,0 +1,54 @@
+import { useEffect } from "react";
+import "@/App.css";
+import { BrowserRouter, Routes, Route } from "react-router-dom";
+import axios from "axios";
+
+const BACKEND_URL = process.env.REACT_APP_BACKEND_URL;
+const API = `${BACKEND_URL}/api`;
+
+const Home = () => {
+ const helloWorldApi = async () => {
+ try {
+ const response = await axios.get(`${API}/`);
+ console.log(response.data.message);
+ } catch (e) {
+ console.error(e, `errored out requesting / api`);
+ }
+ };
+
+ useEffect(() => {
+ helloWorldApi();
+ }, []);
+
+ return (
+
+ );
+};
+
+function App() {
+ return (
+
+
+
+ }>
+ } />
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/frontend/src/components/ui/accordion.jsx b/frontend/src/components/ui/accordion.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1c4416a4a852c758ce4056b302fb6b2e9cf24118
--- /dev/null
+++ b/frontend/src/components/ui/accordion.jsx
@@ -0,0 +1,41 @@
+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(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}>
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef(({ className, children, ...props }, ref) => (
+
+ {children}
+
+))
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/frontend/src/components/ui/alert-dialog.jsx b/frontend/src/components/ui/alert-dialog.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a4174f3675cce4b9835ec9acb878b6d0578134e7
--- /dev/null
+++ b/frontend/src/components/ui/alert-dialog.jsx
@@ -0,0 +1,97 @@
+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(({ className, ...props }, ref) => (
+
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}) => (
+
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}) => (
+
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/frontend/src/components/ui/alert.jsx b/frontend/src/components/ui/alert.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..28597e84ffa7ba757fb992076bc5340fd9d567a0
--- /dev/null
+++ b/frontend/src/components/ui/alert.jsx
@@ -0,0 +1,47 @@
+import * as React from "react"
+import { cva } from "class-variance-authority";
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
+ {
+ 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(({ className, variant, ...props }, ref) => (
+
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/frontend/src/components/ui/aspect-ratio.jsx b/frontend/src/components/ui/aspect-ratio.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c4abbf37f217c715a0eaade7f45ac78600df419f
--- /dev/null
+++ b/frontend/src/components/ui/aspect-ratio.jsx
@@ -0,0 +1,5 @@
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+
+const AspectRatio = AspectRatioPrimitive.Root
+
+export { AspectRatio }
diff --git a/frontend/src/components/ui/avatar.jsx b/frontend/src/components/ui/avatar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9a2f853246a39519572367f45336371dc24d80ea
--- /dev/null
+++ b/frontend/src/components/ui/avatar.jsx
@@ -0,0 +1,33 @@
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/frontend/src/components/ui/badge.jsx b/frontend/src/components/ui/badge.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a687ebad024768896157580c5884e8c6c0c0f502
--- /dev/null
+++ b/frontend/src/components/ui/badge.jsx
@@ -0,0 +1,34 @@
+import * as React from "react"
+import { cva } from "class-variance-authority";
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-md 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 shadow hover:bg-primary/80",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ destructive:
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
+ outline: "text-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Badge({
+ className,
+ variant,
+ ...props
+}) {
+ return ();
+}
+
+export { Badge, badgeVariants }
diff --git a/frontend/src/components/ui/breadcrumb.jsx b/frontend/src/components/ui/breadcrumb.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..2588f36ddf5e0a0d5367d44033d43fd17db3a66b
--- /dev/null
+++ b/frontend/src/components/ui/breadcrumb.jsx
@@ -0,0 +1,92 @@
+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(
+ ({ ...props }, ref) =>
+)
+Breadcrumb.displayName = "Breadcrumb"
+
+const BreadcrumbList = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+BreadcrumbList.displayName = "BreadcrumbList"
+
+const BreadcrumbItem = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+BreadcrumbItem.displayName = "BreadcrumbItem"
+
+const BreadcrumbLink = React.forwardRef(({ asChild, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+
+ );
+})
+BreadcrumbLink.displayName = "BreadcrumbLink"
+
+const BreadcrumbPage = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+BreadcrumbPage.displayName = "BreadcrumbPage"
+
+const BreadcrumbSeparator = ({
+ children,
+ className,
+ ...props
+}) => (
+ svg]:w-3.5 [&>svg]:h-3.5", className)}
+ {...props}>
+ {children ?? }
+
+)
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+
+const BreadcrumbEllipsis = ({
+ className,
+ ...props
+}) => (
+
+
+ More
+
+)
+BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/frontend/src/components/ui/button.jsx b/frontend/src/components/ui/button.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..91db9be3834a9ff9cc847a0faa00ab10b9f84df6
--- /dev/null
+++ b/frontend/src/components/ui/button.jsx
@@ -0,0 +1,48 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva } from "class-variance-authority";
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ outline:
+ "border border-input shadow-sm hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2",
+ sm: "h-8 rounded-md px-3 text-xs",
+ lg: "h-10 rounded-md px-8",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ );
+})
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/frontend/src/components/ui/calendar.jsx b/frontend/src/components/ui/calendar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a3eac376da3a69b0fce316f5130f0b82b01f45ec
--- /dev/null
+++ b/frontend/src/components/ui/calendar.jsx
@@ -0,0 +1,71 @@
+import * as React from "react"
+import { ChevronLeft, ChevronRight } from "lucide-react"
+import { DayPicker } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}) {
+ return (
+ .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
+ : "[&:has([aria-selected])]:rounded-md"
+ ),
+ day: cn(
+ buttonVariants({ variant: "ghost" }),
+ "h-8 w-8 p-0 font-normal aria-selected:opacity-100"
+ ),
+ day_range_start: "day-range-start",
+ day_range_end: "day-range-end",
+ day_selected:
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+ day_today: "bg-accent text-accent-foreground",
+ day_outside:
+ "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
+ day_disabled: "text-muted-foreground opacity-50",
+ day_range_middle:
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
+ day_hidden: "invisible",
+ ...classNames,
+ }}
+ components={{
+ IconLeft: ({ className, ...props }) => (
+
+ ),
+ IconRight: ({ className, ...props }) => (
+
+ ),
+ }}
+ {...props} />
+ );
+}
+Calendar.displayName = "Calendar"
+
+export { Calendar }
diff --git a/frontend/src/components/ui/card.jsx b/frontend/src/components/ui/card.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..2985cca80f03eae3175a3ee94936204141e38b6f
--- /dev/null
+++ b/frontend/src/components/ui/card.jsx
@@ -0,0 +1,50 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/frontend/src/components/ui/carousel.jsx b/frontend/src/components/ui/carousel.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e4a94a868c661f274cebdbd56b2d0628341cfcf8
--- /dev/null
+++ b/frontend/src/components/ui/carousel.jsx
@@ -0,0 +1,193 @@
+import * as React from "react"
+import useEmblaCarousel from "embla-carousel-react";
+import { ArrowLeft, ArrowRight } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+const CarouselContext = React.createContext(null)
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext)
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ")
+ }
+
+ return context
+}
+
+const Carousel = React.forwardRef((
+ {
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+ },
+ ref
+) => {
+ const [carouselRef, api] = useEmblaCarousel({
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ }, plugins)
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+ const onSelect = React.useCallback((api) => {
+ if (!api) {
+ return
+ }
+
+ setCanScrollPrev(api.canScrollPrev())
+ setCanScrollNext(api.canScrollNext())
+ }, [])
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev()
+ }, [api])
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext()
+ }, [api])
+
+ const handleKeyDown = React.useCallback((event) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault()
+ scrollPrev()
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault()
+ scrollNext()
+ }
+ }, [scrollPrev, scrollNext])
+
+ React.useEffect(() => {
+ if (!api || !setApi) {
+ return
+ }
+
+ setApi(api)
+ }, [api, setApi])
+
+ React.useEffect(() => {
+ if (!api) {
+ return
+ }
+
+ onSelect(api)
+ api.on("reInit", onSelect)
+ api.on("select", onSelect)
+
+ return () => {
+ api?.off("select", onSelect)
+ };
+ }, [api, onSelect])
+
+ return (
+
+
+ {children}
+
+
+ );
+})
+Carousel.displayName = "Carousel"
+
+const CarouselContent = React.forwardRef(({ className, ...props }, ref) => {
+ const { carouselRef, orientation } = useCarousel()
+
+ return (
+
+ );
+})
+CarouselContent.displayName = "CarouselContent"
+
+const CarouselItem = React.forwardRef(({ className, ...props }, ref) => {
+ const { orientation } = useCarousel()
+
+ return (
+
+ );
+})
+CarouselItem.displayName = "CarouselItem"
+
+const CarouselPrevious = React.forwardRef(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+ return (
+
+ );
+})
+CarouselPrevious.displayName = "CarouselPrevious"
+
+const CarouselNext = React.forwardRef(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+ return (
+
+ );
+})
+CarouselNext.displayName = "CarouselNext"
+
+export { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext };
diff --git a/frontend/src/components/ui/checkbox.jsx b/frontend/src/components/ui/checkbox.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5e0c96bf7bcb9878da9239772b3ff361a5e8a1c0
--- /dev/null
+++ b/frontend/src/components/ui/checkbox.jsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Checkbox = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/frontend/src/components/ui/collapsible.jsx b/frontend/src/components/ui/collapsible.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a23e7a281287e18b1c332498491b6bcc8d8e2b70
--- /dev/null
+++ b/frontend/src/components/ui/collapsible.jsx
@@ -0,0 +1,9 @@
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+const Collapsible = CollapsiblePrimitive.Root
+
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
+
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/frontend/src/components/ui/command.jsx b/frontend/src/components/ui/command.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e0eac7787ae4fa8179744784d91b42916b533c3b
--- /dev/null
+++ b/frontend/src/components/ui/command.jsx
@@ -0,0 +1,116 @@
+import * as React from "react"
+import { Command as CommandPrimitive } from "cmdk"
+import { Search } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+
+const Command = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+Command.displayName = CommandPrimitive.displayName
+
+const CommandDialog = ({
+ children,
+ ...props
+}) => {
+ return (
+
+ );
+}
+
+const CommandInput = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+
+CommandList.displayName = CommandPrimitive.List.displayName
+
+const CommandEmpty = React.forwardRef((props, ref) => (
+
+))
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
+
+const CommandGroup = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
+
+const CommandSeparator = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
+
+const CommandItem = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
+
+const CommandShortcut = ({
+ className,
+ ...props
+}) => {
+ return (
+
+ );
+}
+CommandShortcut.displayName = "CommandShortcut"
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/frontend/src/components/ui/context-menu.jsx b/frontend/src/components/ui/context-menu.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b450396a3c3eac489ac0698b8613d18ac72e1dd0
--- /dev/null
+++ b/frontend/src/components/ui/context-menu.jsx
@@ -0,0 +1,156 @@
+import * as React from "react"
+import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const ContextMenu = ContextMenuPrimitive.Root
+
+const ContextMenuTrigger = ContextMenuPrimitive.Trigger
+
+const ContextMenuGroup = ContextMenuPrimitive.Group
+
+const ContextMenuPortal = ContextMenuPrimitive.Portal
+
+const ContextMenuSub = ContextMenuPrimitive.Sub
+
+const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
+
+const ContextMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
+
+const ContextMenuSubContent = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
+
+const ContextMenuContent = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+))
+ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
+
+const ContextMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (
+
+))
+ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
+
+const ContextMenuCheckboxItem = React.forwardRef(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+ContextMenuCheckboxItem.displayName =
+ ContextMenuPrimitive.CheckboxItem.displayName
+
+const ContextMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
+
+const ContextMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => (
+
+))
+ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
+
+const ContextMenuSeparator = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
+
+const ContextMenuShortcut = ({
+ className,
+ ...props
+}) => {
+ return (
+
+ );
+}
+ContextMenuShortcut.displayName = "ContextMenuShortcut"
+
+export {
+ ContextMenu,
+ ContextMenuTrigger,
+ ContextMenuContent,
+ ContextMenuItem,
+ ContextMenuCheckboxItem,
+ ContextMenuRadioItem,
+ ContextMenuLabel,
+ ContextMenuSeparator,
+ ContextMenuShortcut,
+ ContextMenuGroup,
+ ContextMenuPortal,
+ ContextMenuSub,
+ ContextMenuSubContent,
+ ContextMenuSubTrigger,
+ ContextMenuRadioGroup,
+}
diff --git a/frontend/src/components/ui/dialog.jsx b/frontend/src/components/ui/dialog.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b41b8e1a061f95bbc925802378c7da7b84055489
--- /dev/null
+++ b/frontend/src/components/ui/dialog.jsx
@@ -0,0 +1,94 @@
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}) => (
+
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}) => (
+
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogTrigger,
+ DialogClose,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/frontend/src/components/ui/drawer.jsx b/frontend/src/components/ui/drawer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1aa82681f576d60f17848ff6f8fc233d0059dc4a
--- /dev/null
+++ b/frontend/src/components/ui/drawer.jsx
@@ -0,0 +1,90 @@
+import * as React from "react"
+import { Drawer as DrawerPrimitive } from "vaul"
+
+import { cn } from "@/lib/utils"
+
+const Drawer = ({
+ shouldScaleBackground = true,
+ ...props
+}) => (
+
+)
+Drawer.displayName = "Drawer"
+
+const DrawerTrigger = DrawerPrimitive.Trigger
+
+const DrawerPortal = DrawerPrimitive.Portal
+
+const DrawerClose = DrawerPrimitive.Close
+
+const DrawerOverlay = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
+
+const DrawerContent = React.forwardRef(({ className, children, ...props }, ref) => (
+
+
+
+
+ {children}
+
+
+))
+DrawerContent.displayName = "DrawerContent"
+
+const DrawerHeader = ({
+ className,
+ ...props
+}) => (
+
+)
+DrawerHeader.displayName = "DrawerHeader"
+
+const DrawerFooter = ({
+ className,
+ ...props
+}) => (
+
+)
+DrawerFooter.displayName = "DrawerFooter"
+
+const DrawerTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DrawerTitle.displayName = DrawerPrimitive.Title.displayName
+
+const DrawerDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DrawerDescription.displayName = DrawerPrimitive.Description.displayName
+
+export {
+ Drawer,
+ DrawerPortal,
+ DrawerOverlay,
+ DrawerTrigger,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerFooter,
+ DrawerTitle,
+ DrawerDescription,
+}
diff --git a/frontend/src/components/ui/dropdown-menu.jsx b/frontend/src/components/ui/dropdown-menu.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..91d98a3fefa41a17fc3e00fac729e02b488ed86e
--- /dev/null
+++ b/frontend/src/components/ui/dropdown-menu.jsx
@@ -0,0 +1,156 @@
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (
+ svg]:size-4 [&>svg]:shrink-0",
+ inset && "pl-8",
+ className
+ )}
+ {...props} />
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}) => {
+ return (
+
+ );
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/frontend/src/components/ui/form.jsx b/frontend/src/components/ui/form.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..bd161604211cee808f35baa8e087965a08da1282
--- /dev/null
+++ b/frontend/src/components/ui/form.jsx
@@ -0,0 +1,133 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { Controller, FormProvider, useFormContext } from "react-hook-form";
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+
+const Form = FormProvider
+
+const FormFieldContext = React.createContext({})
+
+const FormField = (
+ {
+ ...props
+ }
+) => {
+ return (
+
+
+
+ );
+}
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext)
+ const itemContext = React.useContext(FormItemContext)
+ const { getFieldState, formState } = useFormContext()
+
+ const fieldState = getFieldState(fieldContext.name, formState)
+
+ if (!fieldContext) {
+ throw new Error("useFormField should be used within ")
+ }
+
+ const { id } = itemContext
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ }
+}
+
+const FormItemContext = React.createContext({})
+
+const FormItem = React.forwardRef(({ className, ...props }, ref) => {
+ const id = React.useId()
+
+ return (
+
+
+
+ );
+})
+FormItem.displayName = "FormItem"
+
+const FormLabel = React.forwardRef(({ className, ...props }, ref) => {
+ const { error, formItemId } = useFormField()
+
+ return (
+
+ );
+})
+FormLabel.displayName = "FormLabel"
+
+const FormControl = React.forwardRef(({ ...props }, ref) => {
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+ return (
+
+ );
+})
+FormControl.displayName = "FormControl"
+
+const FormDescription = React.forwardRef(({ className, ...props }, ref) => {
+ const { formDescriptionId } = useFormField()
+
+ return (
+
+ );
+})
+FormDescription.displayName = "FormDescription"
+
+const FormMessage = React.forwardRef(({ className, children, ...props }, ref) => {
+ const { error, formMessageId } = useFormField()
+ const body = error ? String(error?.message ?? "") : children
+
+ if (!body) {
+ return null
+ }
+
+ return (
+
+ {body}
+
+ );
+})
+FormMessage.displayName = "FormMessage"
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+}
diff --git a/frontend/src/components/ui/hover-card.jsx b/frontend/src/components/ui/hover-card.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..85d12ed5a2bed4024bba24be7ae565c273b808dc
--- /dev/null
+++ b/frontend/src/components/ui/hover-card.jsx
@@ -0,0 +1,23 @@
+import * as React from "react"
+import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
+
+import { cn } from "@/lib/utils"
+
+const HoverCard = HoverCardPrimitive.Root
+
+const HoverCardTrigger = HoverCardPrimitive.Trigger
+
+const HoverCardContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+
+))
+HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
+
+export { HoverCard, HoverCardTrigger, HoverCardContent }
diff --git a/frontend/src/components/ui/input-otp.jsx b/frontend/src/components/ui/input-otp.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..d6e989965d6770383be088afaffa1014e26f5e8a
--- /dev/null
+++ b/frontend/src/components/ui/input-otp.jsx
@@ -0,0 +1,53 @@
+import * as React from "react"
+import { OTPInput, OTPInputContext } from "input-otp"
+import { Minus } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const InputOTP = React.forwardRef(({ className, containerClassName, ...props }, ref) => (
+
+))
+InputOTP.displayName = "InputOTP"
+
+const InputOTPGroup = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+InputOTPGroup.displayName = "InputOTPGroup"
+
+const InputOTPSlot = React.forwardRef(({ index, className, ...props }, ref) => {
+ const inputOTPContext = React.useContext(OTPInputContext)
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
+
+ return (
+
+ {char}
+ {hasFakeCaret && (
+
+ )}
+
+ );
+})
+InputOTPSlot.displayName = "InputOTPSlot"
+
+const InputOTPSeparator = React.forwardRef(({ ...props }, ref) => (
+
+
+
+))
+InputOTPSeparator.displayName = "InputOTPSeparator"
+
+export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
diff --git a/frontend/src/components/ui/input.jsx b/frontend/src/components/ui/input.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..764b48f35a932da47fcd9ed32efb95cdfd679010
--- /dev/null
+++ b/frontend/src/components/ui/input.jsx
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Input = React.forwardRef(({ className, type, ...props }, ref) => {
+ return (
+
+ );
+})
+Input.displayName = "Input"
+
+export { Input }
diff --git a/frontend/src/components/ui/label.jsx b/frontend/src/components/ui/label.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a1f40999c1466ed84fdd2ad1a8944781f516c9ba
--- /dev/null
+++ b/frontend/src/components/ui/label.jsx
@@ -0,0 +1,16 @@
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva } from "class-variance-authority";
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/frontend/src/components/ui/menubar.jsx b/frontend/src/components/ui/menubar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..361c54dc8701a9926064269ad5e6ea36d4349991
--- /dev/null
+++ b/frontend/src/components/ui/menubar.jsx
@@ -0,0 +1,198 @@
+import * as React from "react"
+import * as MenubarPrimitive from "@radix-ui/react-menubar"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function MenubarMenu({
+ ...props
+}) {
+ return ;
+}
+
+function MenubarGroup({
+ ...props
+}) {
+ return ;
+}
+
+function MenubarPortal({
+ ...props
+}) {
+ return ;
+}
+
+function MenubarRadioGroup({
+ ...props
+}) {
+ return ;
+}
+
+function MenubarSub({
+ ...props
+}) {
+ return ;
+}
+
+const Menubar = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+Menubar.displayName = MenubarPrimitive.Root.displayName
+
+const MenubarTrigger = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
+
+const MenubarSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
+
+const MenubarSubContent = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
+
+const MenubarContent = React.forwardRef((
+ { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
+ ref
+) => (
+
+
+
+))
+MenubarContent.displayName = MenubarPrimitive.Content.displayName
+
+const MenubarItem = React.forwardRef(({ className, inset, ...props }, ref) => (
+
+))
+MenubarItem.displayName = MenubarPrimitive.Item.displayName
+
+const MenubarCheckboxItem = React.forwardRef(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
+
+const MenubarRadioItem = React.forwardRef(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
+
+const MenubarLabel = React.forwardRef(({ className, inset, ...props }, ref) => (
+
+))
+MenubarLabel.displayName = MenubarPrimitive.Label.displayName
+
+const MenubarSeparator = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
+
+const MenubarShortcut = ({
+ className,
+ ...props
+}) => {
+ return (
+
+ );
+}
+MenubarShortcut.displayname = "MenubarShortcut"
+
+export {
+ Menubar,
+ MenubarMenu,
+ MenubarTrigger,
+ MenubarContent,
+ MenubarItem,
+ MenubarSeparator,
+ MenubarLabel,
+ MenubarCheckboxItem,
+ MenubarRadioGroup,
+ MenubarRadioItem,
+ MenubarPortal,
+ MenubarSubContent,
+ MenubarSubTrigger,
+ MenubarGroup,
+ MenubarSub,
+ MenubarShortcut,
+}
diff --git a/frontend/src/components/ui/navigation-menu.jsx b/frontend/src/components/ui/navigation-menu.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3b62c0a345dfb7b033e83d737966db4d7645a4de
--- /dev/null
+++ b/frontend/src/components/ui/navigation-menu.jsx
@@ -0,0 +1,104 @@
+import * as React from "react"
+import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
+import { cva } from "class-variance-authority"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const NavigationMenu = React.forwardRef(({ className, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
+
+const NavigationMenuList = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
+
+const NavigationMenuItem = NavigationMenuPrimitive.Item
+
+const navigationMenuTriggerStyle = cva(
+ "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent"
+)
+
+const NavigationMenuTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
+
+ {children}{" "}
+
+
+))
+NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
+
+const NavigationMenuContent = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
+
+const NavigationMenuLink = NavigationMenuPrimitive.Link
+
+const NavigationMenuViewport = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+))
+NavigationMenuViewport.displayName =
+ NavigationMenuPrimitive.Viewport.displayName
+
+const NavigationMenuIndicator = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+))
+NavigationMenuIndicator.displayName =
+ NavigationMenuPrimitive.Indicator.displayName
+
+export {
+ navigationMenuTriggerStyle,
+ NavigationMenu,
+ NavigationMenuList,
+ NavigationMenuItem,
+ NavigationMenuContent,
+ NavigationMenuTrigger,
+ NavigationMenuLink,
+ NavigationMenuIndicator,
+ NavigationMenuViewport,
+}
diff --git a/frontend/src/components/ui/pagination.jsx b/frontend/src/components/ui/pagination.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..193407e6d2bf990665776a932a865b764df2a3a7
--- /dev/null
+++ b/frontend/src/components/ui/pagination.jsx
@@ -0,0 +1,100 @@
+import * as React from "react"
+import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button";
+
+const Pagination = ({
+ className,
+ ...props
+}) => (
+
+)
+Pagination.displayName = "Pagination"
+
+const PaginationContent = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+PaginationContent.displayName = "PaginationContent"
+
+const PaginationItem = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+PaginationItem.displayName = "PaginationItem"
+
+const PaginationLink = ({
+ className,
+ isActive,
+ size = "icon",
+ ...props
+}) => (
+
+)
+PaginationLink.displayName = "PaginationLink"
+
+const PaginationPrevious = ({
+ className,
+ ...props
+}) => (
+
+
+ Previous
+
+)
+PaginationPrevious.displayName = "PaginationPrevious"
+
+const PaginationNext = ({
+ className,
+ ...props
+}) => (
+
+ Next
+
+
+)
+PaginationNext.displayName = "PaginationNext"
+
+const PaginationEllipsis = ({
+ className,
+ ...props
+}) => (
+
+
+ More pages
+
+)
+PaginationEllipsis.displayName = "PaginationEllipsis"
+
+export {
+ Pagination,
+ PaginationContent,
+ PaginationLink,
+ PaginationItem,
+ PaginationPrevious,
+ PaginationNext,
+ PaginationEllipsis,
+}
diff --git a/frontend/src/components/ui/popover.jsx b/frontend/src/components/ui/popover.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..de4a2716064d6c9f8813ee9649c5576aa6b158f2
--- /dev/null
+++ b/frontend/src/components/ui/popover.jsx
@@ -0,0 +1,27 @@
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverAnchor = PopoverPrimitive.Anchor
+
+const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
diff --git a/frontend/src/components/ui/progress.jsx b/frontend/src/components/ui/progress.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6bcb2e1e9e4f33e609d83e453a6bdb1b07e6a2b8
--- /dev/null
+++ b/frontend/src/components/ui/progress.jsx
@@ -0,0 +1,21 @@
+import * as React from "react"
+import * as ProgressPrimitive from "@radix-ui/react-progress"
+
+import { cn } from "@/lib/utils"
+
+const Progress = React.forwardRef(({ className, value, ...props }, ref) => (
+
+
+
+))
+Progress.displayName = ProgressPrimitive.Root.displayName
+
+export { Progress }
diff --git a/frontend/src/components/ui/radio-group.jsx b/frontend/src/components/ui/radio-group.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..4d8a29ed4244866c424ee45ac458dcd0988139f0
--- /dev/null
+++ b/frontend/src/components/ui/radio-group.jsx
@@ -0,0 +1,29 @@
+import * as React from "react"
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
+import { Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const RadioGroup = React.forwardRef(({ className, ...props }, ref) => {
+ return ();
+})
+RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
+
+const RadioGroupItem = React.forwardRef(({ className, ...props }, ref) => {
+ return (
+
+
+
+
+
+ );
+})
+RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
+
+export { RadioGroup, RadioGroupItem }
diff --git a/frontend/src/components/ui/resizable.jsx b/frontend/src/components/ui/resizable.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c961f53242f2c0082d94325d83247cc332c88492
--- /dev/null
+++ b/frontend/src/components/ui/resizable.jsx
@@ -0,0 +1,40 @@
+import { GripVertical } from "lucide-react"
+import * as ResizablePrimitive from "react-resizable-panels"
+
+import { cn } from "@/lib/utils"
+
+const ResizablePanelGroup = ({
+ className,
+ ...props
+}) => (
+
+)
+
+const ResizablePanel = ResizablePrimitive.Panel
+
+const ResizableHandle = ({
+ withHandle,
+ className,
+ ...props
+}) => (
+ div]:rotate-90",
+ className
+ )}
+ {...props}>
+ {withHandle && (
+
+
+
+ )}
+
+)
+
+export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
diff --git a/frontend/src/components/ui/scroll-area.jsx b/frontend/src/components/ui/scroll-area.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..dc3d3840ca9a57627ec2edcc938111a877c67761
--- /dev/null
+++ b/frontend/src/components/ui/scroll-area.jsx
@@ -0,0 +1,38 @@
+import * as React from "react"
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "@/lib/utils"
+
+const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
diff --git a/frontend/src/components/ui/select.jsx b/frontend/src/components/ui/select.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b1367bf2397e6b8a021de7fa9966066d1f12138c
--- /dev/null
+++ b/frontend/src/components/ui/select.jsx
@@ -0,0 +1,119 @@
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { Check, ChevronDown, ChevronUp } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Select = SelectPrimitive.Root
+
+const SelectGroup = SelectPrimitive.Group
+
+const SelectValue = SelectPrimitive.Value
+
+const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
+ span]:line-clamp-1",
+ className
+ )}
+ {...props}>
+ {children}
+
+
+
+
+))
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+
+const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+))
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+
+const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+))
+SelectScrollDownButton.displayName =
+ SelectPrimitive.ScrollDownButton.displayName
+
+const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => (
+
+
+
+
+ {children}
+
+
+
+
+))
+SelectContent.displayName = SelectPrimitive.Content.displayName
+
+const SelectLabel = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+SelectLabel.displayName = SelectPrimitive.Label.displayName
+
+const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+SelectItem.displayName = SelectPrimitive.Item.displayName
+
+const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+
+export {
+ Select,
+ SelectGroup,
+ SelectValue,
+ SelectTrigger,
+ SelectContent,
+ SelectLabel,
+ SelectItem,
+ SelectSeparator,
+ SelectScrollUpButton,
+ SelectScrollDownButton,
+}
diff --git a/frontend/src/components/ui/separator.jsx b/frontend/src/components/ui/separator.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c40b88839ea28b3755355e6a9841301315abf908
--- /dev/null
+++ b/frontend/src/components/ui/separator.jsx
@@ -0,0 +1,23 @@
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+const Separator = React.forwardRef((
+ { className, orientation = "horizontal", decorative = true, ...props },
+ ref
+) => (
+
+))
+Separator.displayName = SeparatorPrimitive.Root.displayName
+
+export { Separator }
diff --git a/frontend/src/components/ui/sheet.jsx b/frontend/src/components/ui/sheet.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6741fcb0d2550ce3b01c4816753316987790d2a9
--- /dev/null
+++ b/frontend/src/components/ui/sheet.jsx
@@ -0,0 +1,108 @@
+import * as React from "react"
+import * as SheetPrimitive from "@radix-ui/react-dialog"
+import { cva } from "class-variance-authority";
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Sheet = SheetPrimitive.Root
+
+const SheetTrigger = SheetPrimitive.Trigger
+
+const SheetClose = SheetPrimitive.Close
+
+const SheetPortal = SheetPrimitive.Portal
+
+const SheetOverlay = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
+ {
+ variants: {
+ side: {
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
+ bottom:
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+ right:
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+ },
+ },
+ defaultVariants: {
+ side: "right",
+ },
+ }
+)
+
+const SheetContent = React.forwardRef(({ side = "right", className, children, ...props }, ref) => (
+
+
+
+
+
+ Close
+
+ {children}
+
+
+))
+SheetContent.displayName = SheetPrimitive.Content.displayName
+
+const SheetHeader = ({
+ className,
+ ...props
+}) => (
+
+)
+SheetHeader.displayName = "SheetHeader"
+
+const SheetFooter = ({
+ className,
+ ...props
+}) => (
+
+)
+SheetFooter.displayName = "SheetFooter"
+
+const SheetTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+SheetTitle.displayName = SheetPrimitive.Title.displayName
+
+const SheetDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+SheetDescription.displayName = SheetPrimitive.Description.displayName
+
+export {
+ Sheet,
+ SheetPortal,
+ SheetOverlay,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/frontend/src/components/ui/skeleton.jsx b/frontend/src/components/ui/skeleton.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..12138e566b4542484df231624764f46717ce6e5b
--- /dev/null
+++ b/frontend/src/components/ui/skeleton.jsx
@@ -0,0 +1,14 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({
+ className,
+ ...props
+}) {
+ return (
+
+ );
+}
+
+export { Skeleton }
diff --git a/frontend/src/components/ui/slider.jsx b/frontend/src/components/ui/slider.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6062ce5e4b36a90b148d55eb0cb080ac3f47330c
--- /dev/null
+++ b/frontend/src/components/ui/slider.jsx
@@ -0,0 +1,21 @@
+import * as React from "react"
+import * as SliderPrimitive from "@radix-ui/react-slider"
+
+import { cn } from "@/lib/utils"
+
+const Slider = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+
+
+
+))
+Slider.displayName = SliderPrimitive.Root.displayName
+
+export { Slider }
diff --git a/frontend/src/components/ui/sonner.jsx b/frontend/src/components/ui/sonner.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c5e2b57932c4ca087542b2170f77293751f94041
--- /dev/null
+++ b/frontend/src/components/ui/sonner.jsx
@@ -0,0 +1,28 @@
+import { useTheme } from "next-themes"
+import { Toaster as Sonner, toast } from "sonner"
+
+const Toaster = ({
+ ...props
+}) => {
+ const { theme = "system" } = useTheme()
+
+ return (
+
+ );
+}
+
+export { Toaster, toast }
diff --git a/frontend/src/components/ui/switch.jsx b/frontend/src/components/ui/switch.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5ef3287b76983539c22e5d2d3c05d6b69347beec
--- /dev/null
+++ b/frontend/src/components/ui/switch.jsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+import * as SwitchPrimitives from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+const Switch = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+))
+Switch.displayName = SwitchPrimitives.Root.displayName
+
+export { Switch }
diff --git a/frontend/src/components/ui/table.jsx b/frontend/src/components/ui/table.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..07443a1a5a091046a7bed640882106fbc2df47cf
--- /dev/null
+++ b/frontend/src/components/ui/table.jsx
@@ -0,0 +1,86 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Table = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+Table.displayName = "Table"
+
+const TableHeader = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+TableHeader.displayName = "TableHeader"
+
+const TableBody = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+TableBody.displayName = "TableBody"
+
+const TableFooter = React.forwardRef(({ className, ...props }, ref) => (
+ tr]:last:border-b-0", className)}
+ {...props} />
+))
+TableFooter.displayName = "TableFooter"
+
+const TableRow = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+TableRow.displayName = "TableRow"
+
+const TableHead = React.forwardRef(({ className, ...props }, ref) => (
+ [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props} />
+))
+TableHead.displayName = "TableHead"
+
+const TableCell = React.forwardRef(({ className, ...props }, ref) => (
+ | [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props} />
+))
+TableCell.displayName = "TableCell"
+
+const TableCaption = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+TableCaption.displayName = "TableCaption"
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/frontend/src/components/ui/tabs.jsx b/frontend/src/components/ui/tabs.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b674eb93009a3f5da27631ea28a99604fc6321cd
--- /dev/null
+++ b/frontend/src/components/ui/tabs.jsx
@@ -0,0 +1,41 @@
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "@/lib/utils"
+
+const Tabs = TabsPrimitive.Root
+
+const TabsList = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+TabsList.displayName = TabsPrimitive.List.displayName
+
+const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
+
+const TabsContent = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+TabsContent.displayName = TabsPrimitive.Content.displayName
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/frontend/src/components/ui/textarea.jsx b/frontend/src/components/ui/textarea.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9a3276c2b780ae17dd21cf914be3d02a823a191e
--- /dev/null
+++ b/frontend/src/components/ui/textarea.jsx
@@ -0,0 +1,18 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Textarea = React.forwardRef(({ className, ...props }, ref) => {
+ return (
+
+ );
+})
+Textarea.displayName = "Textarea"
+
+export { Textarea }
diff --git a/frontend/src/components/ui/toast.jsx b/frontend/src/components/ui/toast.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f8936f1ba83299acf89e66300f575016f5cc9d07
--- /dev/null
+++ b/frontend/src/components/ui/toast.jsx
@@ -0,0 +1,85 @@
+import * as React from "react"
+import * as ToastPrimitives from "@radix-ui/react-toast"
+import { cva } from "class-variance-authority";
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const ToastProvider = ToastPrimitives.Provider
+
+const ToastViewport = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+
+const toastVariants = cva(
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+ {
+ variants: {
+ variant: {
+ default: "border bg-background text-foreground",
+ destructive:
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Toast = React.forwardRef(({ className, variant, ...props }, ref) => {
+ return (
+
+ );
+})
+Toast.displayName = ToastPrimitives.Root.displayName
+
+const ToastAction = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+ToastAction.displayName = ToastPrimitives.Action.displayName
+
+const ToastClose = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+))
+ToastClose.displayName = ToastPrimitives.Close.displayName
+
+const ToastTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+ToastTitle.displayName = ToastPrimitives.Title.displayName
+
+const ToastDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+ToastDescription.displayName = ToastPrimitives.Description.displayName
+
+export { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction };
diff --git a/frontend/src/components/ui/toaster.jsx b/frontend/src/components/ui/toaster.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..892a53d2daa1dec36d96bca848b3bfb63b40f93e
--- /dev/null
+++ b/frontend/src/components/ui/toaster.jsx
@@ -0,0 +1,33 @@
+import { useToast } from "@/hooks/use-toast"
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from "@/components/ui/toast"
+
+export function Toaster() {
+ const { toasts } = useToast()
+
+ return (
+
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ return (
+
+
+ {title && {title}}
+ {description && (
+ {description}
+ )}
+
+ {action}
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/frontend/src/components/ui/toggle-group.jsx b/frontend/src/components/ui/toggle-group.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..618c02c84d41cdb0738a43ef9b4fe30b688f3e91
--- /dev/null
+++ b/frontend/src/components/ui/toggle-group.jsx
@@ -0,0 +1,43 @@
+import * as React from "react"
+import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
+
+import { cn } from "@/lib/utils"
+import { toggleVariants } from "@/components/ui/toggle"
+
+const ToggleGroupContext = React.createContext({
+ size: "default",
+ variant: "default",
+})
+
+const ToggleGroup = React.forwardRef(({ className, variant, size, children, ...props }, ref) => (
+
+
+ {children}
+
+
+))
+
+ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
+
+const ToggleGroupItem = React.forwardRef(({ className, children, variant, size, ...props }, ref) => {
+ const context = React.useContext(ToggleGroupContext)
+
+ return (
+
+ {children}
+
+ );
+})
+
+ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
+
+export { ToggleGroup, ToggleGroupItem }
diff --git a/frontend/src/components/ui/toggle.jsx b/frontend/src/components/ui/toggle.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3908703436f451162f6f44fb5ad0e82b4d7b0f88
--- /dev/null
+++ b/frontend/src/components/ui/toggle.jsx
@@ -0,0 +1,40 @@
+"use client"
+
+import * as React from "react"
+import * as TogglePrimitive from "@radix-ui/react-toggle"
+import { cva } from "class-variance-authority";
+
+import { cn } from "@/lib/utils"
+
+const toggleVariants = cva(
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ outline:
+ "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
+ },
+ size: {
+ default: "h-9 px-2 min-w-9",
+ sm: "h-8 px-1.5 min-w-8",
+ lg: "h-10 px-2.5 min-w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+const Toggle = React.forwardRef(({ className, variant, size, ...props }, ref) => (
+
+))
+
+Toggle.displayName = TogglePrimitive.Root.displayName
+
+export { Toggle, toggleVariants }
diff --git a/frontend/src/components/ui/tooltip.jsx b/frontend/src/components/ui/tooltip.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5bf88282d9c928f266543db5980577c6c589e38f
--- /dev/null
+++ b/frontend/src/components/ui/tooltip.jsx
@@ -0,0 +1,26 @@
+import * as React from "react"
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "@/lib/utils"
+
+const TooltipProvider = TooltipPrimitive.Provider
+
+const Tooltip = TooltipPrimitive.Root
+
+const TooltipTrigger = TooltipPrimitive.Trigger
+
+const TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+TooltipContent.displayName = TooltipPrimitive.Content.displayName
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/frontend/src/hooks/use-toast.js b/frontend/src/hooks/use-toast.js
new file mode 100644
index 0000000000000000000000000000000000000000..03accc0a354f14ce303aef1143ebb68284586145
--- /dev/null
+++ b/frontend/src/hooks/use-toast.js
@@ -0,0 +1,155 @@
+"use client";
+// Inspired by react-hot-toast library
+import * as React from "react"
+
+const TOAST_LIMIT = 1
+const TOAST_REMOVE_DELAY = 1000000
+
+const actionTypes = {
+ ADD_TOAST: "ADD_TOAST",
+ UPDATE_TOAST: "UPDATE_TOAST",
+ DISMISS_TOAST: "DISMISS_TOAST",
+ REMOVE_TOAST: "REMOVE_TOAST"
+}
+
+let count = 0
+
+function genId() {
+ count = (count + 1) % Number.MAX_SAFE_INTEGER
+ return count.toString();
+}
+
+const toastTimeouts = new Map()
+
+const addToRemoveQueue = (toastId) => {
+ if (toastTimeouts.has(toastId)) {
+ return
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId)
+ dispatch({
+ type: "REMOVE_TOAST",
+ toastId: toastId,
+ })
+ }, TOAST_REMOVE_DELAY)
+
+ toastTimeouts.set(toastId, timeout)
+}
+
+export const reducer = (state, action) => {
+ switch (action.type) {
+ case "ADD_TOAST":
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ };
+
+ case "UPDATE_TOAST":
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t),
+ };
+
+ case "DISMISS_TOAST": {
+ const { toastId } = action
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId)
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id)
+ })
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t),
+ };
+ }
+ case "REMOVE_TOAST":
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ }
+ }
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ };
+ }
+}
+
+const listeners = []
+
+let memoryState = { toasts: [] }
+
+function dispatch(action) {
+ memoryState = reducer(memoryState, action)
+ listeners.forEach((listener) => {
+ listener(memoryState)
+ })
+}
+
+function toast({
+ ...props
+}) {
+ const id = genId()
+
+ const update = (props) =>
+ dispatch({
+ type: "UPDATE_TOAST",
+ toast: { ...props, id },
+ })
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
+
+ dispatch({
+ type: "ADD_TOAST",
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open) => {
+ if (!open) dismiss()
+ },
+ },
+ })
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ }
+}
+
+function useToast() {
+ const [state, setState] = React.useState(memoryState)
+
+ React.useEffect(() => {
+ listeners.push(setState)
+ return () => {
+ const index = listeners.indexOf(setState)
+ if (index > -1) {
+ listeners.splice(index, 1)
+ }
+ };
+ }, [state])
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId) => dispatch({ type: "DISMISS_TOAST", toastId }),
+ };
+}
+
+export { useToast, toast }
diff --git a/frontend/src/index.css b/frontend/src/index.css
new file mode 100644
index 0000000000000000000000000000000000000000..e839749567dcf07b934d79810b1c3eddb7ae0cf8
--- /dev/null
+++ b/frontend/src/index.css
@@ -0,0 +1,115 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+body {
+ margin: 0;
+ font-family:
+ -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family:
+ source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
+}
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+ }
+ .dark {
+ --background: 0 0% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 0 0% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 0 0% 14.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
+
+@layer base {
+ [data-debug-wrapper="true"] {
+ display: contents !important;
+ }
+
+ [data-debug-wrapper="true"] > * {
+ margin-left: inherit;
+ margin-right: inherit;
+ margin-top: inherit;
+ margin-bottom: inherit;
+ padding-left: inherit;
+ padding-right: inherit;
+ padding-top: inherit;
+ padding-bottom: inherit;
+ column-gap: inherit;
+ row-gap: inherit;
+ gap: inherit;
+ border-left-width: inherit;
+ border-right-width: inherit;
+ border-top-width: inherit;
+ border-bottom-width: inherit;
+ border-left-style: inherit;
+ border-right-style: inherit;
+ border-top-style: inherit;
+ border-bottom-style: inherit;
+ border-left-color: inherit;
+ border-right-color: inherit;
+ border-top-color: inherit;
+ border-bottom-color: inherit;
+ }
+}
diff --git a/frontend/src/index.js b/frontend/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..a27d249b0a404b3aff14e82960163353d4b80732
--- /dev/null
+++ b/frontend/src/index.js
@@ -0,0 +1,11 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import "@/index.css";
+import App from "@/App";
+
+const root = ReactDOM.createRoot(document.getElementById("root"));
+root.render(
+
+
+ ,
+);
diff --git a/frontend/src/lib/utils.js b/frontend/src/lib/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..b20bf017182a344cc7cd55ce40eb2e19752a42df
--- /dev/null
+++ b/frontend/src/lib/utils.js
@@ -0,0 +1,6 @@
+import { clsx } from "clsx";
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs) {
+ return twMerge(clsx(inputs));
+}
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..747a58639de8a3a8579b7b2627ad20001ee19210
--- /dev/null
+++ b/frontend/tailwind.config.js
@@ -0,0 +1,82 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ darkMode: ["class"],
+ content: [
+ "./src/**/*.{js,jsx,ts,tsx}",
+ "./public/index.html"
+ ],
+ theme: {
+ extend: {
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
+ },
+ colors: {
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))'
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))'
+ },
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))'
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))'
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))'
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))'
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))'
+ },
+ border: 'hsl(var(--border))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ chart: {
+ '1': 'hsl(var(--chart-1))',
+ '2': 'hsl(var(--chart-2))',
+ '3': 'hsl(var(--chart-3))',
+ '4': 'hsl(var(--chart-4))',
+ '5': 'hsl(var(--chart-5))'
+ }
+ },
+ keyframes: {
+ 'accordion-down': {
+ from: {
+ height: '0'
+ },
+ to: {
+ height: 'var(--radix-accordion-content-height)'
+ }
+ },
+ 'accordion-up': {
+ from: {
+ height: 'var(--radix-accordion-content-height)'
+ },
+ to: {
+ height: '0'
+ }
+ }
+ },
+ animation: {
+ 'accordion-down': 'accordion-down 0.2s ease-out',
+ 'accordion-up': 'accordion-up 0.2s ease-out'
+ }
+ }
+ },
+ plugins: [require("tailwindcss-animate")],
+};
\ No newline at end of file
diff --git a/image_testing.md b/image_testing.md
new file mode 100644
index 0000000000000000000000000000000000000000..b7fe780f411c55aa563bfcf922ac41d5122d6856
--- /dev/null
+++ b/image_testing.md
@@ -0,0 +1,16 @@
+## TEST AGENT PROMPT – IMAGE INTEGRATION RULES ##
+You are the Test Agent responsible for validating image integrations.
+Follow these rules exactly. Do not overcomplicate.
+
+*** Image Handling Rules ***
+- Always use base64-encoded images for all tests and requests.
+- Accepted formats: JPEG, PNG, WEBP only.
+- Do not use SVG, BMP, HEIC, or other formats.
+- Do not upload blank, solid-color, or uniform-variance images.
+- Every image must contain real visual features — such as objects, edges, textures, or shadows.
+- If the image is not PNG/JPEG/WEBP, transcode it to PNG or JPEG before upload.
+ ## Fix Example:
+ If you read a .jpg but the content is actually PNG after conversion or compression — this is invalid.
+ Always re-detect and update the MIME after transformations.
+- If the image is animated (e.g., GIF, APNG, WEBP animation), extract the first frame only.
+- Resize large images to reasonable bounds (avoid oversized payloads).
diff --git a/memory/.gitkeep b/memory/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test_reports/.gitkeep b/test_reports/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test_reports/pytest/.gitkeep b/test_reports/pytest/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test_result.md b/test_result.md
new file mode 100644
index 0000000000000000000000000000000000000000..187cba49b3b4a5f41ab1e67433e756a46d001c1e
--- /dev/null
+++ b/test_result.md
@@ -0,0 +1,103 @@
+#====================================================================================================
+# START - Testing Protocol - DO NOT EDIT OR REMOVE THIS SECTION
+#====================================================================================================
+
+# THIS SECTION CONTAINS CRITICAL TESTING INSTRUCTIONS FOR BOTH AGENTS
+# BOTH MAIN_AGENT AND TESTING_AGENT MUST PRESERVE THIS ENTIRE BLOCK
+
+# Communication Protocol:
+# If the `testing_agent` is available, main agent should delegate all testing tasks to it.
+#
+# You have access to a file called `test_result.md`. This file contains the complete testing state
+# and history, and is the primary means of communication between main and the testing agent.
+#
+# Main and testing agents must follow this exact format to maintain testing data.
+# The testing data must be entered in yaml format Below is the data structure:
+#
+## user_problem_statement: {problem_statement}
+## backend:
+## - task: "Task name"
+## implemented: true
+## working: true # or false or "NA"
+## file: "file_path.py"
+## stuck_count: 0
+## priority: "high" # or "medium" or "low"
+## needs_retesting: false
+## status_history:
+## -working: true # or false or "NA"
+## -agent: "main" # or "testing" or "user"
+## -comment: "Detailed comment about status"
+##
+## frontend:
+## - task: "Task name"
+## implemented: true
+## working: true # or false or "NA"
+## file: "file_path.js"
+## stuck_count: 0
+## priority: "high" # or "medium" or "low"
+## needs_retesting: false
+## status_history:
+## -working: true # or false or "NA"
+## -agent: "main" # or "testing" or "user"
+## -comment: "Detailed comment about status"
+##
+## metadata:
+## created_by: "main_agent"
+## version: "1.0"
+## test_sequence: 0
+## run_ui: false
+##
+## test_plan:
+## current_focus:
+## - "Task name 1"
+## - "Task name 2"
+## stuck_tasks:
+## - "Task name with persistent issues"
+## test_all: false
+## test_priority: "high_first" # or "sequential" or "stuck_first"
+##
+## agent_communication:
+## -agent: "main" # or "testing" or "user"
+## -message: "Communication message between agents"
+
+# Protocol Guidelines for Main agent
+#
+# 1. Update Test Result File Before Testing:
+# - Main agent must always update the `test_result.md` file before calling the testing agent
+# - Add implementation details to the status_history
+# - Set `needs_retesting` to true for tasks that need testing
+# - Update the `test_plan` section to guide testing priorities
+# - Add a message to `agent_communication` explaining what you've done
+#
+# 2. Incorporate User Feedback:
+# - When a user provides feedback that something is or isn't working, add this information to the relevant task's status_history
+# - Update the working status based on user feedback
+# - If a user reports an issue with a task that was marked as working, increment the stuck_count
+# - Whenever user reports issue in the app, if we have testing agent and task_result.md file so find the appropriate task for that and append in status_history of that task to contain the user concern and problem as well
+#
+# 3. Track Stuck Tasks:
+# - Monitor which tasks have high stuck_count values or where you are fixing same issue again and again, analyze that when you read task_result.md
+# - For persistent issues, use websearch tool to find solutions
+# - Pay special attention to tasks in the stuck_tasks list
+# - When you fix an issue with a stuck task, don't reset the stuck_count until the testing agent confirms it's working
+#
+# 4. Provide Context to Testing Agent:
+# - When calling the testing agent, provide clear instructions about:
+# - Which tasks need testing (reference the test_plan)
+# - Any authentication details or configuration needed
+# - Specific test scenarios to focus on
+# - Any known issues or edge cases to verify
+#
+# 5. Call the testing agent with specific instructions referring to test_result.md
+#
+# IMPORTANT: Main agent must ALWAYS update test_result.md BEFORE calling the testing agent, as it relies on this file to understand what to test next.
+
+#====================================================================================================
+# END - Testing Protocol - DO NOT EDIT OR REMOVE THIS SECTION
+#====================================================================================================
+
+
+
+#====================================================================================================
+# Testing Data - Main Agent and testing sub agent both should log testing data below this section
+#====================================================================================================
\ No newline at end of file
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|