emergent-agent-e1 commited on
Commit
7a7199f
·
1 Parent(s): eec94db

auto-commit for 41db1e46-1e00-4de7-81a3-658988d8d311

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .emergent/emergent.yml +3 -0
  2. .gitignore +82 -0
  3. README.md +1 -0
  4. backend/requirements.txt +27 -0
  5. backend/server.py +89 -0
  6. design_guidelines.json +106 -0
  7. frontend/.gitignore +23 -0
  8. frontend/README.md +70 -0
  9. frontend/components.json +21 -0
  10. frontend/craco.config.js +100 -0
  11. frontend/jsconfig.json +9 -0
  12. frontend/package.json +91 -0
  13. frontend/plugins/health-check/health-endpoints.js +213 -0
  14. frontend/plugins/health-check/webpack-health-plugin.js +120 -0
  15. frontend/postcss.config.js +6 -0
  16. frontend/public/index.html +158 -0
  17. frontend/src/App.css +34 -0
  18. frontend/src/App.js +54 -0
  19. frontend/src/components/ui/accordion.jsx +41 -0
  20. frontend/src/components/ui/alert-dialog.jsx +97 -0
  21. frontend/src/components/ui/alert.jsx +47 -0
  22. frontend/src/components/ui/aspect-ratio.jsx +5 -0
  23. frontend/src/components/ui/avatar.jsx +33 -0
  24. frontend/src/components/ui/badge.jsx +34 -0
  25. frontend/src/components/ui/breadcrumb.jsx +92 -0
  26. frontend/src/components/ui/button.jsx +48 -0
  27. frontend/src/components/ui/calendar.jsx +71 -0
  28. frontend/src/components/ui/card.jsx +50 -0
  29. frontend/src/components/ui/carousel.jsx +193 -0
  30. frontend/src/components/ui/checkbox.jsx +22 -0
  31. frontend/src/components/ui/collapsible.jsx +9 -0
  32. frontend/src/components/ui/command.jsx +116 -0
  33. frontend/src/components/ui/context-menu.jsx +156 -0
  34. frontend/src/components/ui/dialog.jsx +94 -0
  35. frontend/src/components/ui/drawer.jsx +90 -0
  36. frontend/src/components/ui/dropdown-menu.jsx +156 -0
  37. frontend/src/components/ui/form.jsx +133 -0
  38. frontend/src/components/ui/hover-card.jsx +23 -0
  39. frontend/src/components/ui/input-otp.jsx +53 -0
  40. frontend/src/components/ui/input.jsx +19 -0
  41. frontend/src/components/ui/label.jsx +16 -0
  42. frontend/src/components/ui/menubar.jsx +198 -0
  43. frontend/src/components/ui/navigation-menu.jsx +104 -0
  44. frontend/src/components/ui/pagination.jsx +100 -0
  45. frontend/src/components/ui/popover.jsx +27 -0
  46. frontend/src/components/ui/progress.jsx +21 -0
  47. frontend/src/components/ui/radio-group.jsx +29 -0
  48. frontend/src/components/ui/resizable.jsx +40 -0
  49. frontend/src/components/ui/scroll-area.jsx +38 -0
  50. frontend/src/components/ui/select.jsx +119 -0
.emergent/emergent.yml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "env_image_name": "fastapi_react_mongo_shadcn_base_image_cloud_arm:release-17042026-1"
3
+ }
.gitignore ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # IDE and editors
4
+ .idea/
5
+ .vscode/
6
+
7
+ # Dependencies
8
+ node_modules/
9
+ /node_modules
10
+ /.pnp
11
+ .pnp.js
12
+ .yarn/install-state.gz
13
+ .yarn/*
14
+ !.yarn/patches
15
+ !.yarn/plugins
16
+ !.yarn/releases
17
+ !.yarn/versions
18
+
19
+ # Testing
20
+ /coverage
21
+
22
+ # Next.js
23
+ /.next/
24
+ /out/
25
+ next-env.d.ts
26
+ *.tsbuildinfo
27
+
28
+ # Production builds
29
+ /build
30
+ dist/
31
+ dist
32
+
33
+ # Environment files (comprehensive coverage)
34
+
35
+ *token.json*
36
+ *credentials.json*
37
+
38
+ # Logs and debug files
39
+ npm-debug.log*
40
+ yarn-debug.log*
41
+ yarn-error.log*
42
+ .pnpm-debug.log*
43
+ dump.rdb
44
+
45
+ # System files
46
+ .DS_Store
47
+ *.pem
48
+
49
+ # Python
50
+ __pycache__/
51
+ *pyc*
52
+ venv/
53
+ .venv/
54
+
55
+ # Development tools
56
+ chainlit.md
57
+ .chainlit
58
+ .ipynb_checkpoints/
59
+ .ac
60
+
61
+ # Deployment
62
+ .vercel
63
+
64
+ # Data and databases
65
+ agenthub/agents/youtube/db
66
+
67
+ # Archive files and large assets
68
+ **/*.zip
69
+ **/*.tar.gz
70
+ **/*.tar
71
+ **/*.tgz
72
+ *.pack
73
+ *.deb
74
+ *.dylib
75
+
76
+ # Build caches
77
+ .cache/
78
+
79
+ memory/test_credentials.md
80
+
81
+ # Mobile development
82
+ android-sdk/
README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Here are your Instructions
backend/requirements.txt ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.110.1
2
+ uvicorn==0.25.0
3
+ boto3>=1.34.129
4
+ requests-oauthlib>=2.0.0
5
+ cryptography>=42.0.8
6
+ python-dotenv>=1.0.1
7
+ pymongo==4.5.0
8
+ pydantic>=2.6.4
9
+ email-validator>=2.2.0
10
+ pyjwt>=2.10.1
11
+ bcrypt==4.1.3
12
+ passlib>=1.7.4
13
+ tzdata>=2024.2
14
+ motor==3.3.1
15
+ pytest>=8.0.0
16
+ black>=24.1.1
17
+ isort>=5.13.2
18
+ flake8>=7.0.0
19
+ mypy>=1.8.0
20
+ python-jose>=3.3.0
21
+ requests>=2.31.0
22
+ pandas>=2.2.0
23
+ numpy>=1.26.0
24
+ python-multipart>=0.0.9
25
+ jq>=1.6.0
26
+ typer>=0.9.0
27
+ emergentintegrations==0.1.0
backend/server.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, APIRouter
2
+ from dotenv import load_dotenv
3
+ from starlette.middleware.cors import CORSMiddleware
4
+ from motor.motor_asyncio import AsyncIOMotorClient
5
+ import os
6
+ import logging
7
+ from pathlib import Path
8
+ from pydantic import BaseModel, Field, ConfigDict
9
+ from typing import List
10
+ import uuid
11
+ from datetime import datetime, timezone
12
+
13
+
14
+ ROOT_DIR = Path(__file__).parent
15
+ load_dotenv(ROOT_DIR / '.env')
16
+
17
+ # MongoDB connection
18
+ mongo_url = os.environ['MONGO_URL']
19
+ client = AsyncIOMotorClient(mongo_url)
20
+ db = client[os.environ['DB_NAME']]
21
+
22
+ # Create the main app without a prefix
23
+ app = FastAPI()
24
+
25
+ # Create a router with the /api prefix
26
+ api_router = APIRouter(prefix="/api")
27
+
28
+
29
+ # Define Models
30
+ class StatusCheck(BaseModel):
31
+ model_config = ConfigDict(extra="ignore") # Ignore MongoDB's _id field
32
+
33
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
34
+ client_name: str
35
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
36
+
37
+ class StatusCheckCreate(BaseModel):
38
+ client_name: str
39
+
40
+ # Add your routes to the router instead of directly to app
41
+ @api_router.get("/")
42
+ async def root():
43
+ return {"message": "Hello World"}
44
+
45
+ @api_router.post("/status", response_model=StatusCheck)
46
+ async def create_status_check(input: StatusCheckCreate):
47
+ status_dict = input.model_dump()
48
+ status_obj = StatusCheck(**status_dict)
49
+
50
+ # Convert to dict and serialize datetime to ISO string for MongoDB
51
+ doc = status_obj.model_dump()
52
+ doc['timestamp'] = doc['timestamp'].isoformat()
53
+
54
+ _ = await db.status_checks.insert_one(doc)
55
+ return status_obj
56
+
57
+ @api_router.get("/status", response_model=List[StatusCheck])
58
+ async def get_status_checks():
59
+ # Exclude MongoDB's _id field from the query results
60
+ status_checks = await db.status_checks.find({}, {"_id": 0}).to_list(1000)
61
+
62
+ # Convert ISO string timestamps back to datetime objects
63
+ for check in status_checks:
64
+ if isinstance(check['timestamp'], str):
65
+ check['timestamp'] = datetime.fromisoformat(check['timestamp'])
66
+
67
+ return status_checks
68
+
69
+ # Include the router in the main app
70
+ app.include_router(api_router)
71
+
72
+ app.add_middleware(
73
+ CORSMiddleware,
74
+ allow_credentials=True,
75
+ allow_origins=os.environ.get('CORS_ORIGINS', '*').split(','),
76
+ allow_methods=["*"],
77
+ allow_headers=["*"],
78
+ )
79
+
80
+ # Configure logging
81
+ logging.basicConfig(
82
+ level=logging.INFO,
83
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
84
+ )
85
+ logger = logging.getLogger(__name__)
86
+
87
+ @app.on_event("shutdown")
88
+ async def shutdown_db_client():
89
+ client.close()
design_guidelines.json ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "project_name": "ForgeSight",
3
+ "domain": "Manufacturing / AI Quality Control / Developer Tool",
4
+ "theme": "Dark",
5
+ "archetype": "Archetype 4 (Swiss & High-Contrast) - Technical Brutalist variant",
6
+ "mood": "Precise, authoritative, industrial, hardware-optimized, slightly brutalist",
7
+ "colors": {
8
+ "background_base": "#0A0A0A",
9
+ "background_surface": "#141416",
10
+ "primary_accent": "#ED1C24",
11
+ "primary_hover": "#FF3B30",
12
+ "border_default": "rgba(255,255,255,0.1)",
13
+ "text_primary": "#FFFFFF",
14
+ "text_secondary": "#A1A1AA",
15
+ "status": {
16
+ "pass": "#10B981",
17
+ "warn": "#F59E0B",
18
+ "fail": "#ED1C24"
19
+ }
20
+ },
21
+ "typography": {
22
+ "headings": {
23
+ "family": "Chivo",
24
+ "weights": [
25
+ "300",
26
+ "900"
27
+ ],
28
+ "classes_h1": "text-4xl sm:text-5xl lg:text-6xl font-black tracking-tighter leading-none text-white",
29
+ "classes_h2": "text-2xl sm:text-3xl lg:text-4xl font-bold tracking-tight text-white",
30
+ "classes_h3": "text-xl sm:text-2xl font-semibold text-white"
31
+ },
32
+ "body": {
33
+ "family": "IBM Plex Sans",
34
+ "weights": [
35
+ "400",
36
+ "500"
37
+ ],
38
+ "classes": "text-base leading-relaxed tracking-normal text-zinc-300"
39
+ },
40
+ "mono": {
41
+ "family": "JetBrains Mono",
42
+ "weights": [
43
+ "400",
44
+ "700"
45
+ ],
46
+ "classes": "text-sm tracking-tight font-mono text-zinc-300"
47
+ },
48
+ "labels": {
49
+ "classes": "text-xs font-mono uppercase tracking-[0.2em] text-zinc-400"
50
+ }
51
+ },
52
+ "layout_and_spacing": {
53
+ "philosophy": "Industrial-HMI meets Linear. Dense information with purposeful whitespace and strict grid alignments.",
54
+ "bento_grid": {
55
+ "dashboard_mode": "Control Room Grid (Tight gaps: gap-4 or gap-6, col-span logic for metrics vs feeds)",
56
+ "marketing_mode": "Tetris Grid (Asymmetric, wide gaps: gap-8, col-span-full for hero elements)"
57
+ },
58
+ "spacing_scale": "Tailwind standard (p-4, p-6, p-8). Cards must use internal p-6. Footers use py-24."
59
+ },
60
+ "surfaces": {
61
+ "dashboards": {
62
+ "style": "Grid Borders (Technical Look)",
63
+ "rules": "Expose the skeleton. border-collapse layouts, 1px subtle borders (border-white/10), flat backgrounds, no soft shadows."
64
+ },
65
+ "agent_console": {
66
+ "style": "Retro-Futurism / Terminal HMI",
67
+ "rules": "Deep black #000000 background, neon red borders for active states, monospace fonts, CSS repeating-linear-gradient for subtle scanlines."
68
+ },
69
+ "navigation": {
70
+ "style": "Solid / Linear",
71
+ "rules": "Solid #0A0A0A background with hard 1px bottom border (border-white/10). Pill-style route switcher."
72
+ }
73
+ },
74
+ "components_strategy": {
75
+ "html_tailwind": "Use for Marketing/Hero sections (custom pure HTML/Tailwind) for maximum asymmetry.",
76
+ "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.",
77
+ "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).",
78
+ "animations": "Minimalist micro-interactions. Blinking cursor for agent streaming. Smooth fade-ins for bento cards."
79
+ },
80
+ "media_and_images": {
81
+ "hero_inspection": {
82
+ "url": "https://static.prod-images.emergentagent.com/jobs/d5829a2e-bc03-4880-adcd-73acc809a3bd/images/184a8bf32b150669152ea3aa72546730d8caad845b1b8eb0233eeb35e4255eeb.png",
83
+ "description": "Macro shot of industrial inspection. Use as background for the main hero section with a bg-black/60 overlay for text contrast."
84
+ },
85
+ "blueprint_architecture": {
86
+ "url": "https://static.prod-images.emergentagent.com/jobs/d5829a2e-bc03-4880-adcd-73acc809a3bd/images/7251062dc0e36ea4218374b05cc959bc4e6c55a2cf4789a8a2cbc38db6392916.png",
87
+ "description": "Abstract 3D AMD MI300X chip architecture. Use in the 'Deployment Blueprint' section."
88
+ },
89
+ "factory_feed": {
90
+ "url": "https://images.unsplash.com/photo-1720036237334-9263cd28c3d4?crop=entropy&cs=srgb&fm=jpg&ixid=M3w3NTY2Njl8MHwxfHNlYXJjaHwyfHxpbmR1c3RyaWFsJTIwbWFudWZhY3R1cmluZyUyMGFzc2VtYmx5JTIwbGluZSUyMGRhcmt8ZW58MHx8fHwxNzc3NjQ5MzQzfDA&ixlib=rb-4.1.0&q=85",
91
+ "description": "Background for Defect Feed empty states or generic manufacturing imagery."
92
+ }
93
+ },
94
+ "icons_and_testing": {
95
+ "icon_library": "lucide-react (Default, fine for technical interfaces)",
96
+ "testing": "All interactive elements MUST have data-testid attributes (e.g. data-testid='upload-inspection-button')."
97
+ },
98
+ "instructions_to_main_agent": [
99
+ "Build the app as a hybrid: Marketing Hero at the top, followed by the Dashboard Console below.",
100
+ "Do NOT use the color teal, purple, or generic SaaS blue anywhere.",
101
+ "Enforce the 'Grid Borders' surface look for the dashboard parts to make it feel like an industrial HMI.",
102
+ "For the Agent Console, implement a typing/streaming effect with JetBrains Mono to simulate live inference.",
103
+ "Use data-testid on every interactive UI piece.",
104
+ "In the Blueprint diagram section, use standard CSS grid to layer the visual elements over the blueprint_architecture image."
105
+ ]
106
+ }
frontend/.gitignore ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ /coverage
10
+
11
+ # production
12
+ /build
13
+
14
+ # misc
15
+ .DS_Store
16
+ .env.local
17
+ .env.development.local
18
+ .env.test.local
19
+ .env.production.local
20
+
21
+ npm-debug.log*
22
+ yarn-debug.log*
23
+ yarn-error.log*
frontend/README.md ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Getting Started with Create React App
2
+
3
+ This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4
+
5
+ ## Available Scripts
6
+
7
+ In the project directory, you can run:
8
+
9
+ ### `npm start`
10
+
11
+ Runs the app in the development mode.\
12
+ Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13
+
14
+ The page will reload when you make changes.\
15
+ You may also see any lint errors in the console.
16
+
17
+ ### `npm test`
18
+
19
+ Launches the test runner in the interactive watch mode.\
20
+ See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21
+
22
+ ### `npm run build`
23
+
24
+ Builds the app for production to the `build` folder.\
25
+ It correctly bundles React in production mode and optimizes the build for the best performance.
26
+
27
+ The build is minified and the filenames include the hashes.\
28
+ Your app is ready to be deployed!
29
+
30
+ See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31
+
32
+ ### `npm run eject`
33
+
34
+ **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35
+
36
+ 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.
37
+
38
+ 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.
39
+
40
+ 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.
41
+
42
+ ## Learn More
43
+
44
+ You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45
+
46
+ To learn React, check out the [React documentation](https://reactjs.org/).
47
+
48
+ ### Code Splitting
49
+
50
+ 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)
51
+
52
+ ### Analyzing the Bundle Size
53
+
54
+ 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)
55
+
56
+ ### Making a Progressive Web App
57
+
58
+ 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)
59
+
60
+ ### Advanced Configuration
61
+
62
+ 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)
63
+
64
+ ### Deployment
65
+
66
+ This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67
+
68
+ ### `npm run build` fails to minify
69
+
70
+ 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)
frontend/components.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": false,
6
+ "tailwind": {
7
+ "config": "tailwind.config.js",
8
+ "css": "src/index.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
frontend/craco.config.js ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // craco.config.js
2
+ const path = require("path");
3
+ require("dotenv").config();
4
+
5
+ // Check if we're in development/preview mode (not production build)
6
+ // Craco sets NODE_ENV=development for start, NODE_ENV=production for build
7
+ const isDevServer = process.env.NODE_ENV !== "production";
8
+
9
+ // Environment variable overrides
10
+ const config = {
11
+ enableHealthCheck: process.env.ENABLE_HEALTH_CHECK === "true",
12
+ };
13
+
14
+ // Conditionally load health check modules only if enabled
15
+ let WebpackHealthPlugin;
16
+ let setupHealthEndpoints;
17
+ let healthPluginInstance;
18
+
19
+ if (config.enableHealthCheck) {
20
+ WebpackHealthPlugin = require("./plugins/health-check/webpack-health-plugin");
21
+ setupHealthEndpoints = require("./plugins/health-check/health-endpoints");
22
+ healthPluginInstance = new WebpackHealthPlugin();
23
+ }
24
+
25
+ let webpackConfig = {
26
+ eslint: {
27
+ configure: {
28
+ extends: ["plugin:react-hooks/recommended"],
29
+ rules: {
30
+ "react-hooks/rules-of-hooks": "error",
31
+ "react-hooks/exhaustive-deps": "warn",
32
+ },
33
+ },
34
+ },
35
+ webpack: {
36
+ alias: {
37
+ '@': path.resolve(__dirname, 'src'),
38
+ },
39
+ configure: (webpackConfig) => {
40
+
41
+ // Add ignored patterns to reduce watched directories
42
+ webpackConfig.watchOptions = {
43
+ ...webpackConfig.watchOptions,
44
+ ignored: [
45
+ '**/node_modules/**',
46
+ '**/.git/**',
47
+ '**/build/**',
48
+ '**/dist/**',
49
+ '**/coverage/**',
50
+ '**/public/**',
51
+ ],
52
+ };
53
+
54
+ // Add health check plugin to webpack if enabled
55
+ if (config.enableHealthCheck && healthPluginInstance) {
56
+ webpackConfig.plugins.push(healthPluginInstance);
57
+ }
58
+ return webpackConfig;
59
+ },
60
+ },
61
+ };
62
+
63
+ webpackConfig.devServer = (devServerConfig) => {
64
+ // Add health check endpoints if enabled
65
+ if (config.enableHealthCheck && setupHealthEndpoints && healthPluginInstance) {
66
+ const originalSetupMiddlewares = devServerConfig.setupMiddlewares;
67
+
68
+ devServerConfig.setupMiddlewares = (middlewares, devServer) => {
69
+ // Call original setup if exists
70
+ if (originalSetupMiddlewares) {
71
+ middlewares = originalSetupMiddlewares(middlewares, devServer);
72
+ }
73
+
74
+ // Setup health endpoints
75
+ setupHealthEndpoints(devServer, healthPluginInstance);
76
+
77
+ return middlewares;
78
+ };
79
+ }
80
+
81
+ return devServerConfig;
82
+ };
83
+
84
+ // Wrap with visual edits (automatically adds babel plugin, dev server, and overlay in dev mode)
85
+ if (isDevServer) {
86
+ try {
87
+ const { withVisualEdits } = require("@emergentbase/visual-edits/craco");
88
+ webpackConfig = withVisualEdits(webpackConfig);
89
+ } catch (err) {
90
+ if (err.code === 'MODULE_NOT_FOUND' && err.message.includes('@emergentbase/visual-edits/craco')) {
91
+ console.warn(
92
+ "[visual-edits] @emergentbase/visual-edits not installed — visual editing disabled."
93
+ );
94
+ } else {
95
+ throw err;
96
+ }
97
+ }
98
+ }
99
+
100
+ module.exports = webpackConfig;
frontend/jsconfig.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "paths": {
5
+ "@/*": ["src/*"]
6
+ }
7
+ },
8
+ "include": ["src"]
9
+ }
frontend/package.json ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "@hookform/resolvers": "^5.0.1",
7
+ "@radix-ui/react-accordion": "^1.2.8",
8
+ "@radix-ui/react-alert-dialog": "^1.1.11",
9
+ "@radix-ui/react-aspect-ratio": "^1.1.4",
10
+ "@radix-ui/react-avatar": "^1.1.7",
11
+ "@radix-ui/react-checkbox": "^1.2.3",
12
+ "@radix-ui/react-collapsible": "^1.1.8",
13
+ "@radix-ui/react-context-menu": "^2.2.12",
14
+ "@radix-ui/react-dialog": "^1.1.11",
15
+ "@radix-ui/react-dropdown-menu": "^2.1.12",
16
+ "@radix-ui/react-hover-card": "^1.1.11",
17
+ "@radix-ui/react-label": "^2.1.4",
18
+ "@radix-ui/react-menubar": "^1.1.12",
19
+ "@radix-ui/react-navigation-menu": "^1.2.10",
20
+ "@radix-ui/react-popover": "^1.1.11",
21
+ "@radix-ui/react-progress": "^1.1.4",
22
+ "@radix-ui/react-radio-group": "^1.3.4",
23
+ "@radix-ui/react-scroll-area": "^1.2.6",
24
+ "@radix-ui/react-select": "^2.2.2",
25
+ "@radix-ui/react-separator": "^1.1.4",
26
+ "@radix-ui/react-slider": "^1.3.2",
27
+ "@radix-ui/react-slot": "^1.2.0",
28
+ "@radix-ui/react-switch": "^1.2.2",
29
+ "@radix-ui/react-tabs": "^1.1.9",
30
+ "@radix-ui/react-toast": "^1.2.11",
31
+ "@radix-ui/react-toggle": "^1.1.6",
32
+ "@radix-ui/react-toggle-group": "^1.1.7",
33
+ "@radix-ui/react-tooltip": "^1.2.4",
34
+ "axios": "^1.8.4",
35
+ "class-variance-authority": "^0.7.1",
36
+ "clsx": "^2.1.1",
37
+ "cmdk": "^1.1.1",
38
+ "cra-template": "1.2.0",
39
+ "date-fns": "^4.1.0",
40
+ "embla-carousel-react": "^8.6.0",
41
+ "input-otp": "^1.4.2",
42
+ "lucide-react": "^0.507.0",
43
+ "next-themes": "^0.4.6",
44
+ "react": "^19.0.0",
45
+ "react-day-picker": "8.10.1",
46
+ "react-dom": "^19.0.0",
47
+ "react-hook-form": "^7.56.2",
48
+ "react-resizable-panels": "^3.0.1",
49
+ "react-router-dom": "^7.5.1",
50
+ "react-scripts": "5.0.1",
51
+ "recharts": "^3.6.0",
52
+ "sonner": "^2.0.3",
53
+ "tailwind-merge": "^3.2.0",
54
+ "tailwindcss-animate": "^1.0.7",
55
+ "vaul": "^1.1.2",
56
+ "zod": "^3.24.4"
57
+ },
58
+ "scripts": {
59
+ "start": "craco start",
60
+ "build": "craco build",
61
+ "test": "craco test"
62
+ },
63
+ "browserslist": {
64
+ "production": [
65
+ ">0.2%",
66
+ "not dead",
67
+ "not op_mini all"
68
+ ],
69
+ "development": [
70
+ "last 1 chrome version",
71
+ "last 1 firefox version",
72
+ "last 1 safari version"
73
+ ]
74
+ },
75
+ "devDependencies": {
76
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
77
+ "@craco/craco": "^7.1.0",
78
+ "@emergentbase/visual-edits": "https://assets.emergent.sh/npm/emergentbase-visual-edits-1.0.8.tgz",
79
+ "@eslint/js": "9.23.0",
80
+ "autoprefixer": "^10.4.20",
81
+ "eslint": "9.23.0",
82
+ "eslint-plugin-import": "2.31.0",
83
+ "eslint-plugin-jsx-a11y": "6.10.2",
84
+ "eslint-plugin-react": "7.37.4",
85
+ "eslint-plugin-react-hooks": "5.2.0",
86
+ "globals": "15.15.0",
87
+ "postcss": "^8.4.49",
88
+ "tailwindcss": "^3.4.17"
89
+ },
90
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
91
+ }
frontend/plugins/health-check/health-endpoints.js ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // health-endpoints.js
2
+ // API endpoints for health checks and monitoring
3
+
4
+ const os = require('os');
5
+
6
+ const SERVER_START_TIME = Date.now();
7
+
8
+ /**
9
+ * Setup health check endpoints on the dev server
10
+ * @param {Object} devServer - Webpack dev server instance
11
+ * @param {Object} healthPlugin - Instance of WebpackHealthPlugin
12
+ */
13
+ function setupHealthEndpoints(devServer, healthPlugin) {
14
+ if (!devServer || !devServer.app) {
15
+ console.warn('[Health Check] Dev server not available, skipping health endpoints');
16
+ return;
17
+ }
18
+
19
+ if (!healthPlugin) {
20
+ console.warn('[Health Check] Health plugin not provided, skipping health endpoints');
21
+ return;
22
+ }
23
+
24
+ console.log('[Health Check] Setting up health endpoints...');
25
+
26
+ // ====================================================================
27
+ // GET /health - Detailed health status (JSON)
28
+ // ====================================================================
29
+ devServer.app.get("/health", (req, res) => {
30
+ const webpackStatus = healthPlugin.getStatus();
31
+ const uptime = Date.now() - SERVER_START_TIME;
32
+ const memUsage = process.memoryUsage();
33
+
34
+ res.json({
35
+ status: webpackStatus.isHealthy ? 'healthy' : 'unhealthy',
36
+ timestamp: new Date().toISOString(),
37
+ uptime: {
38
+ seconds: Math.floor(uptime / 1000),
39
+ formatted: formatDuration(uptime),
40
+ },
41
+ webpack: {
42
+ state: webpackStatus.state,
43
+ isHealthy: webpackStatus.isHealthy,
44
+ hasCompiled: webpackStatus.hasCompiled,
45
+ errors: webpackStatus.errorCount,
46
+ warnings: webpackStatus.warningCount,
47
+ lastCompileTime: webpackStatus.lastCompileTime
48
+ ? new Date(webpackStatus.lastCompileTime).toISOString()
49
+ : null,
50
+ lastSuccessTime: webpackStatus.lastSuccessTime
51
+ ? new Date(webpackStatus.lastSuccessTime).toISOString()
52
+ : null,
53
+ compileDuration: webpackStatus.compileDuration
54
+ ? `${webpackStatus.compileDuration}ms`
55
+ : null,
56
+ totalCompiles: webpackStatus.totalCompiles,
57
+ firstCompileTime: webpackStatus.firstCompileTime
58
+ ? new Date(webpackStatus.firstCompileTime).toISOString()
59
+ : null,
60
+ },
61
+ server: {
62
+ nodeVersion: process.version,
63
+ platform: os.platform(),
64
+ arch: os.arch(),
65
+ cpus: os.cpus().length,
66
+ memory: {
67
+ heapUsed: formatBytes(memUsage.heapUsed),
68
+ heapTotal: formatBytes(memUsage.heapTotal),
69
+ rss: formatBytes(memUsage.rss),
70
+ external: formatBytes(memUsage.external),
71
+ },
72
+ systemMemory: {
73
+ total: formatBytes(os.totalmem()),
74
+ free: formatBytes(os.freemem()),
75
+ used: formatBytes(os.totalmem() - os.freemem()),
76
+ },
77
+ },
78
+ environment: process.env.NODE_ENV || 'development',
79
+ });
80
+ });
81
+
82
+ // ====================================================================
83
+ // GET /health/simple - Simple text response (OK/COMPILING/ERROR)
84
+ // ====================================================================
85
+ devServer.app.get("/health/simple", (req, res) => {
86
+ const webpackStatus = healthPlugin.getSimpleStatus();
87
+
88
+ if (webpackStatus.state === 'success') {
89
+ res.status(200).send('OK');
90
+ } else if (webpackStatus.state === 'compiling') {
91
+ res.status(200).send('COMPILING');
92
+ } else if (webpackStatus.state === 'idle') {
93
+ res.status(200).send('IDLE');
94
+ } else {
95
+ res.status(503).send('ERROR');
96
+ }
97
+ });
98
+
99
+ // ====================================================================
100
+ // GET /health/ready - Readiness check (Kubernetes/load balancer)
101
+ // ====================================================================
102
+ devServer.app.get("/health/ready", (req, res) => {
103
+ const webpackStatus = healthPlugin.getSimpleStatus();
104
+
105
+ if (webpackStatus.state === 'success') {
106
+ res.status(200).json({
107
+ ready: true,
108
+ state: webpackStatus.state,
109
+ });
110
+ } else {
111
+ res.status(503).json({
112
+ ready: false,
113
+ state: webpackStatus.state,
114
+ reason: webpackStatus.state === 'compiling'
115
+ ? 'Compilation in progress'
116
+ : 'Compilation failed',
117
+ });
118
+ }
119
+ });
120
+
121
+ // ====================================================================
122
+ // GET /health/live - Liveness check (Kubernetes)
123
+ // ====================================================================
124
+ devServer.app.get("/health/live", (req, res) => {
125
+ res.status(200).json({
126
+ alive: true,
127
+ timestamp: new Date().toISOString(),
128
+ });
129
+ });
130
+
131
+ // ====================================================================
132
+ // GET /health/errors - Get current errors and warnings
133
+ // ====================================================================
134
+ devServer.app.get("/health/errors", (req, res) => {
135
+ const webpackStatus = healthPlugin.getStatus();
136
+
137
+ res.json({
138
+ errorCount: webpackStatus.errorCount,
139
+ warningCount: webpackStatus.warningCount,
140
+ errors: webpackStatus.errors,
141
+ warnings: webpackStatus.warnings,
142
+ state: webpackStatus.state,
143
+ });
144
+ });
145
+
146
+ // ====================================================================
147
+ // GET /health/stats - Compilation statistics
148
+ // ====================================================================
149
+ devServer.app.get("/health/stats", (req, res) => {
150
+ const webpackStatus = healthPlugin.getStatus();
151
+ const uptime = Date.now() - SERVER_START_TIME;
152
+
153
+ res.json({
154
+ totalCompiles: webpackStatus.totalCompiles,
155
+ averageCompileTime: webpackStatus.totalCompiles > 0
156
+ ? `${Math.round(uptime / webpackStatus.totalCompiles)}ms`
157
+ : null,
158
+ lastCompileDuration: webpackStatus.compileDuration
159
+ ? `${webpackStatus.compileDuration}ms`
160
+ : null,
161
+ firstCompileTime: webpackStatus.firstCompileTime
162
+ ? new Date(webpackStatus.firstCompileTime).toISOString()
163
+ : null,
164
+ serverUptime: formatDuration(uptime),
165
+ });
166
+ });
167
+
168
+ console.log('[Health Check] ✓ Health endpoints ready:');
169
+ console.log(' • GET /health - Detailed status');
170
+ console.log(' • GET /health/simple - Simple OK/ERROR');
171
+ console.log(' • GET /health/ready - Readiness check');
172
+ console.log(' • GET /health/live - Liveness check');
173
+ console.log(' • GET /health/errors - Error details');
174
+ console.log(' • GET /health/stats - Statistics');
175
+ }
176
+
177
+ // ====================================================================
178
+ // Helper Functions
179
+ // ====================================================================
180
+
181
+ /**
182
+ * Format bytes to human-readable string
183
+ * @param {number} bytes
184
+ * @returns {string}
185
+ */
186
+ function formatBytes(bytes) {
187
+ if (bytes === 0) return '0 B';
188
+ const k = 1024;
189
+ const sizes = ['B', 'KB', 'MB', 'GB'];
190
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
191
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
192
+ }
193
+
194
+ /**
195
+ * Format duration to human-readable string
196
+ * @param {number} ms - Duration in milliseconds
197
+ * @returns {string}
198
+ */
199
+ function formatDuration(ms) {
200
+ const seconds = Math.floor(ms / 1000);
201
+ const minutes = Math.floor(seconds / 60);
202
+ const hours = Math.floor(minutes / 60);
203
+
204
+ if (hours > 0) {
205
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
206
+ } else if (minutes > 0) {
207
+ return `${minutes}m ${seconds % 60}s`;
208
+ } else {
209
+ return `${seconds}s`;
210
+ }
211
+ }
212
+
213
+ module.exports = setupHealthEndpoints;
frontend/plugins/health-check/webpack-health-plugin.js ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // webpack-health-plugin.js
2
+ // Webpack plugin that tracks compilation state and health metrics
3
+
4
+ class WebpackHealthPlugin {
5
+ constructor() {
6
+ this.status = {
7
+ state: 'idle', // idle, compiling, success, failed
8
+ errors: [],
9
+ warnings: [],
10
+ lastCompileTime: null,
11
+ lastSuccessTime: null,
12
+ compileDuration: 0,
13
+ totalCompiles: 0,
14
+ firstCompileTime: null,
15
+ };
16
+ }
17
+
18
+ apply(compiler) {
19
+ const pluginName = 'WebpackHealthPlugin';
20
+
21
+ // Hook: Compilation started
22
+ compiler.hooks.compile.tap(pluginName, () => {
23
+ const now = Date.now();
24
+ this.status.state = 'compiling';
25
+ this.status.lastCompileTime = now;
26
+
27
+ if (!this.status.firstCompileTime) {
28
+ this.status.firstCompileTime = now;
29
+ }
30
+ });
31
+
32
+ // Hook: Compilation completed
33
+ compiler.hooks.done.tap(pluginName, (stats) => {
34
+ const info = stats.toJson({
35
+ all: false,
36
+ errors: true,
37
+ warnings: true,
38
+ });
39
+
40
+ this.status.totalCompiles++;
41
+ this.status.compileDuration = Date.now() - this.status.lastCompileTime;
42
+
43
+ if (stats.hasErrors()) {
44
+ this.status.state = 'failed';
45
+ this.status.errors = info.errors.map(err => ({
46
+ message: err.message || String(err),
47
+ stack: err.stack,
48
+ moduleName: err.moduleName,
49
+ loc: err.loc,
50
+ }));
51
+ } else {
52
+ this.status.state = 'success';
53
+ this.status.lastSuccessTime = Date.now();
54
+ this.status.errors = [];
55
+ }
56
+
57
+ if (stats.hasWarnings()) {
58
+ this.status.warnings = info.warnings.map(warn => ({
59
+ message: warn.message || String(warn),
60
+ moduleName: warn.moduleName,
61
+ loc: warn.loc,
62
+ }));
63
+ } else {
64
+ this.status.warnings = [];
65
+ }
66
+ });
67
+
68
+ // Hook: Compilation failed
69
+ compiler.hooks.failed.tap(pluginName, (error) => {
70
+ this.status.state = 'failed';
71
+ this.status.errors = [{
72
+ message: error.message,
73
+ stack: error.stack,
74
+ }];
75
+ this.status.compileDuration = Date.now() - this.status.lastCompileTime;
76
+ });
77
+
78
+ // Hook: Invalid (file changed, recompiling)
79
+ compiler.hooks.invalid.tap(pluginName, () => {
80
+ this.status.state = 'compiling';
81
+ });
82
+ }
83
+
84
+ getStatus() {
85
+ return {
86
+ ...this.status,
87
+ // Add computed fields
88
+ isHealthy: this.status.state === 'success',
89
+ errorCount: this.status.errors.length,
90
+ warningCount: this.status.warnings.length,
91
+ hasCompiled: this.status.totalCompiles > 0,
92
+ };
93
+ }
94
+
95
+ // Get simplified status for quick checks
96
+ getSimpleStatus() {
97
+ return {
98
+ state: this.status.state,
99
+ isHealthy: this.status.state === 'success',
100
+ errorCount: this.status.errors.length,
101
+ warningCount: this.status.warnings.length,
102
+ };
103
+ }
104
+
105
+ // Reset statistics (useful for testing)
106
+ reset() {
107
+ this.status = {
108
+ state: 'idle',
109
+ errors: [],
110
+ warnings: [],
111
+ lastCompileTime: null,
112
+ lastSuccessTime: null,
113
+ compileDuration: 0,
114
+ totalCompiles: 0,
115
+ firstCompileTime: null,
116
+ };
117
+ }
118
+ }
119
+
120
+ module.exports = WebpackHealthPlugin;
frontend/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
frontend/public/index.html ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="theme-color" content="#000000" />
7
+ <meta name="description" content="A product of emergent.sh" />
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@600&display=swap" rel="stylesheet" />
11
+ <!--
12
+ manifest.json provides metadata used when your web app is installed on a
13
+ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
14
+ -->
15
+ <!--
16
+ Notice the use of %PUBLIC_URL% in the tags above.
17
+ It will be replaced with the URL of the `public` folder during the build.
18
+ Only files inside the `public` folder can be referenced from the HTML.
19
+
20
+ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
21
+ work correctly both with client-side routing and a non-root public URL.
22
+ Learn how to configure a non-root public URL by running `npm run build`.
23
+ -->
24
+ <title>Emergent | Fullstack App</title>
25
+ <script>window.addEventListener("error",function(e){if(e.error instanceof DOMException&&e.error.name==="DataCloneError"&&e.message&&e.message.includes("PerformanceServerTiming")){e.stopImmediatePropagation();e.preventDefault()}},true);</script>
26
+ <script src="https://assets.emergent.sh/scripts/emergent-main.js"></script>
27
+ </head>
28
+ <body>
29
+ <noscript>You need to enable JavaScript to run this app.</noscript>
30
+ <div id="root"></div>
31
+ <!--
32
+ This HTML file is a template.
33
+ If you open it directly in the browser, you will see an empty page.
34
+
35
+ You can add webfonts, meta tags, or analytics to this file.
36
+ The build step will place the bundled scripts into the <body> tag.
37
+
38
+ To begin the development, run `npm start` or `yarn start`.
39
+ To create a production bundle, use `npm run build` or `yarn build`.
40
+ -->
41
+ <a
42
+ id="emergent-badge"
43
+ target="_blank"
44
+ href="https://app.emergent.sh/?utm_source=emergent-badge"
45
+ style="
46
+ display: inline-flex !important;
47
+ box-sizing: border-box;
48
+ width: 178px;
49
+ height: 40px;
50
+ padding: 8px 12px 8px 12px;
51
+ align-items: center !important;
52
+ gap: 8px;
53
+ border-radius: 50px !important;
54
+ background: #000 !important;
55
+ position: fixed !important;
56
+ bottom: 16px;
57
+ right: 16px;
58
+ text-decoration: none;
59
+ font-family: -apple-system, BlinkMacSystemFont,
60
+ &quot;Segoe UI&quot;, Roboto, Oxygen, Ubuntu, Cantarell,
61
+ &quot;Open Sans&quot;, &quot;Helvetica Neue&quot;,
62
+ sans-serif !important;
63
+ font-size: 12px !important;
64
+ z-index: 9999 !important;
65
+ "
66
+ >
67
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
68
+ <path d="M15.5702 8.13142C15.7729 8.0412 16.0007 8.18878 15.9892 8.4103C15.8374 11.3192 14.0965 14.0405 11.2531 15.3065C8.40964 16.5725 5.2224 16.0453 2.95912 14.2117C2.78676 14.072 2.82955 13.804 3.03219 13.7137L4.95677 12.8568C5.04866 12.8159 5.15446 12.823 5.24204 12.8725C6.73377 13.7153 8.59176 13.8649 10.2772 13.1145C11.9626 12.3641 13.0947 10.8833 13.4665 9.21075C13.4883 9.11256 13.5539 9.02918 13.6457 8.98827L15.5702 8.13142Z" fill="white"/>
69
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M15.3066 4.74698L15.5067 5.19653C15.5759 5.35178 15.5061 5.53366 15.3508 5.60278L1.29992 11.8586C1.14467 11.9278 0.962794 11.8579 0.893675 11.7027L0.701732 11.2716L0.693457 11.2531C-1.10317 7.21778 0.711626 2.49007 4.74692 0.693443C8.78221 -1.10318 13.51 0.711693 15.3066 4.74698ZM2.82356 8.55367C2.63552 8.63739 2.41991 8.51617 2.40853 8.31065C2.28373 6.05724 3.53858 3.85787 5.72286 2.88536C7.90715 1.91286 10.3813 2.45199 11.9724 4.05256C12.1175 4.19854 12.0633 4.43988 11.8753 4.5236L2.82356 8.55367Z" fill="white"/>
70
+ </svg>
71
+ <p
72
+ style="
73
+ color: #FFF !important;
74
+ font-family: 'Inter', sans-serif !important;
75
+ font-size: 13px !important;
76
+ font-style: normal !important;
77
+ font-weight: 600 !important;
78
+ line-height: 20px !important;
79
+ margin: 0 !important;
80
+ white-space: nowrap !important;
81
+ "
82
+ >
83
+ Made with Emergent
84
+ </p>
85
+ </a>
86
+ <script>
87
+ !(function (t, e) {
88
+ var o, n, p, r;
89
+ e.__SV ||
90
+ ((window.posthog = e),
91
+ (e._i = []),
92
+ (e.init = function (i, s, a) {
93
+ function g(t, e) {
94
+ var o = e.split(".");
95
+ 2 == o.length && ((t = t[o[0]]), (e = o[1])),
96
+ (t[e] = function () {
97
+ t.push(
98
+ [e].concat(
99
+ Array.prototype.slice.call(
100
+ arguments,
101
+ 0,
102
+ ),
103
+ ),
104
+ );
105
+ });
106
+ }
107
+ ((p = t.createElement("script")).type =
108
+ "text/javascript"),
109
+ (p.crossOrigin = "anonymous"),
110
+ (p.async = !0),
111
+ (p.src =
112
+ s.api_host.replace(
113
+ ".i.posthog.com",
114
+ "-assets.i.posthog.com",
115
+ ) + "/static/array.js"),
116
+ (r =
117
+ t.getElementsByTagName(
118
+ "script",
119
+ )[0]).parentNode.insertBefore(p, r);
120
+ var u = e;
121
+ for (
122
+ void 0 !== a ? (u = e[a] = []) : (a = "posthog"),
123
+ u.people = u.people || [],
124
+ u.toString = function (t) {
125
+ var e = "posthog";
126
+ return (
127
+ "posthog" !== a && (e += "." + a),
128
+ t || (e += " (stub)"),
129
+ e
130
+ );
131
+ },
132
+ u.people.toString = function () {
133
+ return u.toString(1) + ".people (stub)";
134
+ },
135
+ o =
136
+ "init me ws ys ps bs capture je Di ks register register_once register_for_session unregister unregister_for_session Ps getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey canRenderSurveyAsync identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty Es $s createPersonProfile Is opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing Ss debug xs getPageViewId captureTraceFeedback captureTraceMetric".split(
137
+ " ",
138
+ ),
139
+ n = 0;
140
+ n < o.length;
141
+ n++
142
+ )
143
+ g(u, o[n]);
144
+ e._i.push([i, s, a]);
145
+ }),
146
+ (e.__SV = 1));
147
+ })(document, window.posthog || []);
148
+ posthog.init("phc_xAvL2Iq4tFmANRE7kzbKwaSqp1HJjN7x48s3vr0CMjs", {
149
+ api_host: "https://us.i.posthog.com",
150
+ person_profiles: "identified_only", // or 'always' to create profiles for anonymous users as well,
151
+ session_recording: {
152
+ recordCrossOriginIframes: true,
153
+ capturePerformance: false,
154
+ },
155
+ });
156
+ </script>
157
+ </body>
158
+ </html>
frontend/src/App.css ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .App-logo {
2
+ height: 40vmin;
3
+ pointer-events: none;
4
+ }
5
+
6
+ @media (prefers-reduced-motion: no-preference) {
7
+ .App-logo {
8
+ animation: App-logo-spin infinite 20s linear;
9
+ }
10
+ }
11
+
12
+ .App-header {
13
+ background-color: #0f0f10;
14
+ min-height: 100vh;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+ justify-content: center;
19
+ font-size: calc(10px + 2vmin);
20
+ color: white;
21
+ }
22
+
23
+ .App-link {
24
+ color: #61dafb;
25
+ }
26
+
27
+ @keyframes App-logo-spin {
28
+ from {
29
+ transform: rotate(0deg);
30
+ }
31
+ to {
32
+ transform: rotate(360deg);
33
+ }
34
+ }
frontend/src/App.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect } from "react";
2
+ import "@/App.css";
3
+ import { BrowserRouter, Routes, Route } from "react-router-dom";
4
+ import axios from "axios";
5
+
6
+ const BACKEND_URL = process.env.REACT_APP_BACKEND_URL;
7
+ const API = `${BACKEND_URL}/api`;
8
+
9
+ const Home = () => {
10
+ const helloWorldApi = async () => {
11
+ try {
12
+ const response = await axios.get(`${API}/`);
13
+ console.log(response.data.message);
14
+ } catch (e) {
15
+ console.error(e, `errored out requesting / api`);
16
+ }
17
+ };
18
+
19
+ useEffect(() => {
20
+ helloWorldApi();
21
+ }, []);
22
+
23
+ return (
24
+ <div>
25
+ <header className="App-header">
26
+ <a
27
+ className="App-link"
28
+ href="https://emergent.sh"
29
+ target="_blank"
30
+ rel="noopener noreferrer"
31
+ >
32
+ <img src="https://avatars.githubusercontent.com/in/1201222?s=120&u=2686cf91179bbafbc7a71bfbc43004cf9ae1acea&v=4" />
33
+ </a>
34
+ <p className="mt-5">Building something incredible ~!</p>
35
+ </header>
36
+ </div>
37
+ );
38
+ };
39
+
40
+ function App() {
41
+ return (
42
+ <div className="App">
43
+ <BrowserRouter>
44
+ <Routes>
45
+ <Route path="/" element={<Home />}>
46
+ <Route index element={<Home />} />
47
+ </Route>
48
+ </Routes>
49
+ </BrowserRouter>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ export default App;
frontend/src/components/ui/accordion.jsx ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
3
+ import { ChevronDown } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Accordion = AccordionPrimitive.Root
8
+
9
+ const AccordionItem = React.forwardRef(({ className, ...props }, ref) => (
10
+ <AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
11
+ ))
12
+ AccordionItem.displayName = "AccordionItem"
13
+
14
+ const AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
15
+ <AccordionPrimitive.Header className="flex">
16
+ <AccordionPrimitive.Trigger
17
+ ref={ref}
18
+ className={cn(
19
+ "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
20
+ className
21
+ )}
22
+ {...props}>
23
+ {children}
24
+ <ChevronDown
25
+ className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
26
+ </AccordionPrimitive.Trigger>
27
+ </AccordionPrimitive.Header>
28
+ ))
29
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
30
+
31
+ const AccordionContent = React.forwardRef(({ className, children, ...props }, ref) => (
32
+ <AccordionPrimitive.Content
33
+ ref={ref}
34
+ className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
35
+ {...props}>
36
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
37
+ </AccordionPrimitive.Content>
38
+ ))
39
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
40
+
41
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
frontend/src/components/ui/alert-dialog.jsx ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
3
+
4
+ import { cn } from "@/lib/utils"
5
+ import { buttonVariants } from "@/components/ui/button"
6
+
7
+ const AlertDialog = AlertDialogPrimitive.Root
8
+
9
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
10
+
11
+ const AlertDialogPortal = AlertDialogPrimitive.Portal
12
+
13
+ const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
14
+ <AlertDialogPrimitive.Overlay
15
+ className={cn(
16
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
17
+ className
18
+ )}
19
+ {...props}
20
+ ref={ref} />
21
+ ))
22
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
23
+
24
+ const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (
25
+ <AlertDialogPortal>
26
+ <AlertDialogOverlay />
27
+ <AlertDialogPrimitive.Content
28
+ ref={ref}
29
+ className={cn(
30
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
31
+ className
32
+ )}
33
+ {...props} />
34
+ </AlertDialogPortal>
35
+ ))
36
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
37
+
38
+ const AlertDialogHeader = ({
39
+ className,
40
+ ...props
41
+ }) => (
42
+ <div
43
+ className={cn("flex flex-col space-y-2 text-center sm:text-left", className)}
44
+ {...props} />
45
+ )
46
+ AlertDialogHeader.displayName = "AlertDialogHeader"
47
+
48
+ const AlertDialogFooter = ({
49
+ className,
50
+ ...props
51
+ }) => (
52
+ <div
53
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
54
+ {...props} />
55
+ )
56
+ AlertDialogFooter.displayName = "AlertDialogFooter"
57
+
58
+ const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (
59
+ <AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
60
+ ))
61
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
62
+
63
+ const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (
64
+ <AlertDialogPrimitive.Description
65
+ ref={ref}
66
+ className={cn("text-sm text-muted-foreground", className)}
67
+ {...props} />
68
+ ))
69
+ AlertDialogDescription.displayName =
70
+ AlertDialogPrimitive.Description.displayName
71
+
72
+ const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (
73
+ <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
74
+ ))
75
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
76
+
77
+ const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (
78
+ <AlertDialogPrimitive.Cancel
79
+ ref={ref}
80
+ className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
81
+ {...props} />
82
+ ))
83
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
84
+
85
+ export {
86
+ AlertDialog,
87
+ AlertDialogPortal,
88
+ AlertDialogOverlay,
89
+ AlertDialogTrigger,
90
+ AlertDialogContent,
91
+ AlertDialogHeader,
92
+ AlertDialogFooter,
93
+ AlertDialogTitle,
94
+ AlertDialogDescription,
95
+ AlertDialogAction,
96
+ AlertDialogCancel,
97
+ }
frontend/src/components/ui/alert.jsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva } from "class-variance-authority";
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "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",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-background text-foreground",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ const Alert = React.forwardRef(({ className, variant, ...props }, ref) => (
23
+ <div
24
+ ref={ref}
25
+ role="alert"
26
+ className={cn(alertVariants({ variant }), className)}
27
+ {...props} />
28
+ ))
29
+ Alert.displayName = "Alert"
30
+
31
+ const AlertTitle = React.forwardRef(({ className, ...props }, ref) => (
32
+ <h5
33
+ ref={ref}
34
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
35
+ {...props} />
36
+ ))
37
+ AlertTitle.displayName = "AlertTitle"
38
+
39
+ const AlertDescription = React.forwardRef(({ className, ...props }, ref) => (
40
+ <div
41
+ ref={ref}
42
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
43
+ {...props} />
44
+ ))
45
+ AlertDescription.displayName = "AlertDescription"
46
+
47
+ export { Alert, AlertTitle, AlertDescription }
frontend/src/components/ui/aspect-ratio.jsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2
+
3
+ const AspectRatio = AspectRatioPrimitive.Root
4
+
5
+ export { AspectRatio }
frontend/src/components/ui/avatar.jsx ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Avatar = React.forwardRef(({ className, ...props }, ref) => (
7
+ <AvatarPrimitive.Root
8
+ ref={ref}
9
+ className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
10
+ {...props} />
11
+ ))
12
+ Avatar.displayName = AvatarPrimitive.Root.displayName
13
+
14
+ const AvatarImage = React.forwardRef(({ className, ...props }, ref) => (
15
+ <AvatarPrimitive.Image
16
+ ref={ref}
17
+ className={cn("aspect-square h-full w-full", className)}
18
+ {...props} />
19
+ ))
20
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
21
+
22
+ const AvatarFallback = React.forwardRef(({ className, ...props }, ref) => (
23
+ <AvatarPrimitive.Fallback
24
+ ref={ref}
25
+ className={cn(
26
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
27
+ className
28
+ )}
29
+ {...props} />
30
+ ))
31
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
32
+
33
+ export { Avatar, AvatarImage, AvatarFallback }
frontend/src/components/ui/badge.jsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva } from "class-variance-authority";
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "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",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ function Badge({
27
+ className,
28
+ variant,
29
+ ...props
30
+ }) {
31
+ return (<div className={cn(badgeVariants({ variant }), className)} {...props} />);
32
+ }
33
+
34
+ export { Badge, badgeVariants }
frontend/src/components/ui/breadcrumb.jsx ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Breadcrumb = React.forwardRef(
8
+ ({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />
9
+ )
10
+ Breadcrumb.displayName = "Breadcrumb"
11
+
12
+ const BreadcrumbList = React.forwardRef(({ className, ...props }, ref) => (
13
+ <ol
14
+ ref={ref}
15
+ className={cn(
16
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
17
+ className
18
+ )}
19
+ {...props} />
20
+ ))
21
+ BreadcrumbList.displayName = "BreadcrumbList"
22
+
23
+ const BreadcrumbItem = React.forwardRef(({ className, ...props }, ref) => (
24
+ <li
25
+ ref={ref}
26
+ className={cn("inline-flex items-center gap-1.5", className)}
27
+ {...props} />
28
+ ))
29
+ BreadcrumbItem.displayName = "BreadcrumbItem"
30
+
31
+ const BreadcrumbLink = React.forwardRef(({ asChild, className, ...props }, ref) => {
32
+ const Comp = asChild ? Slot : "a"
33
+
34
+ return (
35
+ <Comp
36
+ ref={ref}
37
+ className={cn("transition-colors hover:text-foreground", className)}
38
+ {...props} />
39
+ );
40
+ })
41
+ BreadcrumbLink.displayName = "BreadcrumbLink"
42
+
43
+ const BreadcrumbPage = React.forwardRef(({ className, ...props }, ref) => (
44
+ <span
45
+ ref={ref}
46
+ role="link"
47
+ aria-disabled="true"
48
+ aria-current="page"
49
+ className={cn("font-normal text-foreground", className)}
50
+ {...props} />
51
+ ))
52
+ BreadcrumbPage.displayName = "BreadcrumbPage"
53
+
54
+ const BreadcrumbSeparator = ({
55
+ children,
56
+ className,
57
+ ...props
58
+ }) => (
59
+ <li
60
+ role="presentation"
61
+ aria-hidden="true"
62
+ className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
63
+ {...props}>
64
+ {children ?? <ChevronRight />}
65
+ </li>
66
+ )
67
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
68
+
69
+ const BreadcrumbEllipsis = ({
70
+ className,
71
+ ...props
72
+ }) => (
73
+ <span
74
+ role="presentation"
75
+ aria-hidden="true"
76
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
77
+ {...props}>
78
+ <MoreHorizontal className="h-4 w-4" />
79
+ <span className="sr-only">More</span>
80
+ </span>
81
+ )
82
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
83
+
84
+ export {
85
+ Breadcrumb,
86
+ BreadcrumbList,
87
+ BreadcrumbItem,
88
+ BreadcrumbLink,
89
+ BreadcrumbPage,
90
+ BreadcrumbSeparator,
91
+ BreadcrumbEllipsis,
92
+ }
frontend/src/components/ui/button.jsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "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",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16
+ outline:
17
+ "border border-input shadow-sm hover:bg-accent hover:text-accent-foreground",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20
+ ghost: "hover:bg-accent hover:text-accent-foreground",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2",
25
+ sm: "h-8 rounded-md px-3 text-xs",
26
+ lg: "h-10 rounded-md px-8",
27
+ icon: "h-9 w-9",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ size: "default",
33
+ },
34
+ }
35
+ )
36
+
37
+ const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
38
+ const Comp = asChild ? Slot : "button"
39
+ return (
40
+ <Comp
41
+ className={cn(buttonVariants({ variant, size, className }))}
42
+ ref={ref}
43
+ {...props} />
44
+ );
45
+ })
46
+ Button.displayName = "Button"
47
+
48
+ export { Button, buttonVariants }
frontend/src/components/ui/calendar.jsx ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { ChevronLeft, ChevronRight } from "lucide-react"
3
+ import { DayPicker } from "react-day-picker"
4
+
5
+ import { cn } from "@/lib/utils"
6
+ import { buttonVariants } from "@/components/ui/button"
7
+
8
+ function Calendar({
9
+ className,
10
+ classNames,
11
+ showOutsideDays = true,
12
+ ...props
13
+ }) {
14
+ return (
15
+ <DayPicker
16
+ showOutsideDays={showOutsideDays}
17
+ className={cn("p-3", className)}
18
+ classNames={{
19
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
20
+ month: "space-y-4",
21
+ caption: "flex justify-center pt-1 relative items-center",
22
+ caption_label: "text-sm font-medium",
23
+ nav: "space-x-1 flex items-center",
24
+ nav_button: cn(
25
+ buttonVariants({ variant: "outline" }),
26
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
27
+ ),
28
+ nav_button_previous: "absolute left-1",
29
+ nav_button_next: "absolute right-1",
30
+ table: "w-full border-collapse space-y-1",
31
+ head_row: "flex",
32
+ head_cell:
33
+ "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
34
+ row: "flex w-full mt-2",
35
+ cell: cn(
36
+ "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
37
+ props.mode === "range"
38
+ ? "[&:has(>.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"
39
+ : "[&:has([aria-selected])]:rounded-md"
40
+ ),
41
+ day: cn(
42
+ buttonVariants({ variant: "ghost" }),
43
+ "h-8 w-8 p-0 font-normal aria-selected:opacity-100"
44
+ ),
45
+ day_range_start: "day-range-start",
46
+ day_range_end: "day-range-end",
47
+ day_selected:
48
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
49
+ day_today: "bg-accent text-accent-foreground",
50
+ day_outside:
51
+ "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
52
+ day_disabled: "text-muted-foreground opacity-50",
53
+ day_range_middle:
54
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
55
+ day_hidden: "invisible",
56
+ ...classNames,
57
+ }}
58
+ components={{
59
+ IconLeft: ({ className, ...props }) => (
60
+ <ChevronLeft className={cn("h-4 w-4", className)} {...props} />
61
+ ),
62
+ IconRight: ({ className, ...props }) => (
63
+ <ChevronRight className={cn("h-4 w-4", className)} {...props} />
64
+ ),
65
+ }}
66
+ {...props} />
67
+ );
68
+ }
69
+ Calendar.displayName = "Calendar"
70
+
71
+ export { Calendar }
frontend/src/components/ui/card.jsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef(({ className, ...props }, ref) => (
6
+ <div
7
+ ref={ref}
8
+ className={cn("rounded-xl border bg-card text-card-foreground shadow", className)}
9
+ {...props} />
10
+ ))
11
+ Card.displayName = "Card"
12
+
13
+ const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
14
+ <div
15
+ ref={ref}
16
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
17
+ {...props} />
18
+ ))
19
+ CardHeader.displayName = "CardHeader"
20
+
21
+ const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
22
+ <div
23
+ ref={ref}
24
+ className={cn("font-semibold leading-none tracking-tight", className)}
25
+ {...props} />
26
+ ))
27
+ CardTitle.displayName = "CardTitle"
28
+
29
+ const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
30
+ <div
31
+ ref={ref}
32
+ className={cn("text-sm text-muted-foreground", className)}
33
+ {...props} />
34
+ ))
35
+ CardDescription.displayName = "CardDescription"
36
+
37
+ const CardContent = React.forwardRef(({ className, ...props }, ref) => (
38
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
39
+ ))
40
+ CardContent.displayName = "CardContent"
41
+
42
+ const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
43
+ <div
44
+ ref={ref}
45
+ className={cn("flex items-center p-6 pt-0", className)}
46
+ {...props} />
47
+ ))
48
+ CardFooter.displayName = "CardFooter"
49
+
50
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
frontend/src/components/ui/carousel.jsx ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import useEmblaCarousel from "embla-carousel-react";
3
+ import { ArrowLeft, ArrowRight } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+ import { Button } from "@/components/ui/button"
7
+
8
+ const CarouselContext = React.createContext(null)
9
+
10
+ function useCarousel() {
11
+ const context = React.useContext(CarouselContext)
12
+
13
+ if (!context) {
14
+ throw new Error("useCarousel must be used within a <Carousel />")
15
+ }
16
+
17
+ return context
18
+ }
19
+
20
+ const Carousel = React.forwardRef((
21
+ {
22
+ orientation = "horizontal",
23
+ opts,
24
+ setApi,
25
+ plugins,
26
+ className,
27
+ children,
28
+ ...props
29
+ },
30
+ ref
31
+ ) => {
32
+ const [carouselRef, api] = useEmblaCarousel({
33
+ ...opts,
34
+ axis: orientation === "horizontal" ? "x" : "y",
35
+ }, plugins)
36
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
37
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
38
+
39
+ const onSelect = React.useCallback((api) => {
40
+ if (!api) {
41
+ return
42
+ }
43
+
44
+ setCanScrollPrev(api.canScrollPrev())
45
+ setCanScrollNext(api.canScrollNext())
46
+ }, [])
47
+
48
+ const scrollPrev = React.useCallback(() => {
49
+ api?.scrollPrev()
50
+ }, [api])
51
+
52
+ const scrollNext = React.useCallback(() => {
53
+ api?.scrollNext()
54
+ }, [api])
55
+
56
+ const handleKeyDown = React.useCallback((event) => {
57
+ if (event.key === "ArrowLeft") {
58
+ event.preventDefault()
59
+ scrollPrev()
60
+ } else if (event.key === "ArrowRight") {
61
+ event.preventDefault()
62
+ scrollNext()
63
+ }
64
+ }, [scrollPrev, scrollNext])
65
+
66
+ React.useEffect(() => {
67
+ if (!api || !setApi) {
68
+ return
69
+ }
70
+
71
+ setApi(api)
72
+ }, [api, setApi])
73
+
74
+ React.useEffect(() => {
75
+ if (!api) {
76
+ return
77
+ }
78
+
79
+ onSelect(api)
80
+ api.on("reInit", onSelect)
81
+ api.on("select", onSelect)
82
+
83
+ return () => {
84
+ api?.off("select", onSelect)
85
+ };
86
+ }, [api, onSelect])
87
+
88
+ return (
89
+ <CarouselContext.Provider
90
+ value={{
91
+ carouselRef,
92
+ api: api,
93
+ opts,
94
+ orientation:
95
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
96
+ scrollPrev,
97
+ scrollNext,
98
+ canScrollPrev,
99
+ canScrollNext,
100
+ }}>
101
+ <div
102
+ ref={ref}
103
+ onKeyDownCapture={handleKeyDown}
104
+ className={cn("relative", className)}
105
+ role="region"
106
+ aria-roledescription="carousel"
107
+ {...props}>
108
+ {children}
109
+ </div>
110
+ </CarouselContext.Provider>
111
+ );
112
+ })
113
+ Carousel.displayName = "Carousel"
114
+
115
+ const CarouselContent = React.forwardRef(({ className, ...props }, ref) => {
116
+ const { carouselRef, orientation } = useCarousel()
117
+
118
+ return (
119
+ <div ref={carouselRef} className="overflow-hidden">
120
+ <div
121
+ ref={ref}
122
+ className={cn(
123
+ "flex",
124
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
125
+ className
126
+ )}
127
+ {...props} />
128
+ </div>
129
+ );
130
+ })
131
+ CarouselContent.displayName = "CarouselContent"
132
+
133
+ const CarouselItem = React.forwardRef(({ className, ...props }, ref) => {
134
+ const { orientation } = useCarousel()
135
+
136
+ return (
137
+ <div
138
+ ref={ref}
139
+ role="group"
140
+ aria-roledescription="slide"
141
+ className={cn(
142
+ "min-w-0 shrink-0 grow-0 basis-full",
143
+ orientation === "horizontal" ? "pl-4" : "pt-4",
144
+ className
145
+ )}
146
+ {...props} />
147
+ );
148
+ })
149
+ CarouselItem.displayName = "CarouselItem"
150
+
151
+ const CarouselPrevious = React.forwardRef(({ className, variant = "outline", size = "icon", ...props }, ref) => {
152
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
153
+
154
+ return (
155
+ <Button
156
+ ref={ref}
157
+ variant={variant}
158
+ size={size}
159
+ className={cn("absolute h-8 w-8 rounded-full", orientation === "horizontal"
160
+ ? "-left-12 top-1/2 -translate-y-1/2"
161
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90", className)}
162
+ disabled={!canScrollPrev}
163
+ onClick={scrollPrev}
164
+ {...props}>
165
+ <ArrowLeft className="h-4 w-4" />
166
+ <span className="sr-only">Previous slide</span>
167
+ </Button>
168
+ );
169
+ })
170
+ CarouselPrevious.displayName = "CarouselPrevious"
171
+
172
+ const CarouselNext = React.forwardRef(({ className, variant = "outline", size = "icon", ...props }, ref) => {
173
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
174
+
175
+ return (
176
+ <Button
177
+ ref={ref}
178
+ variant={variant}
179
+ size={size}
180
+ className={cn("absolute h-8 w-8 rounded-full", orientation === "horizontal"
181
+ ? "-right-12 top-1/2 -translate-y-1/2"
182
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", className)}
183
+ disabled={!canScrollNext}
184
+ onClick={scrollNext}
185
+ {...props}>
186
+ <ArrowRight className="h-4 w-4" />
187
+ <span className="sr-only">Next slide</span>
188
+ </Button>
189
+ );
190
+ })
191
+ CarouselNext.displayName = "CarouselNext"
192
+
193
+ export { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext };
frontend/src/components/ui/checkbox.jsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
+ import { Check } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Checkbox = React.forwardRef(({ className, ...props }, ref) => (
8
+ <CheckboxPrimitive.Root
9
+ ref={ref}
10
+ className={cn(
11
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
12
+ className
13
+ )}
14
+ {...props}>
15
+ <CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")}>
16
+ <Check className="h-4 w-4" />
17
+ </CheckboxPrimitive.Indicator>
18
+ </CheckboxPrimitive.Root>
19
+ ))
20
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
21
+
22
+ export { Checkbox }
frontend/src/components/ui/collapsible.jsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
2
+
3
+ const Collapsible = CollapsiblePrimitive.Root
4
+
5
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
6
+
7
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
8
+
9
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
frontend/src/components/ui/command.jsx ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Command as CommandPrimitive } from "cmdk"
3
+ import { Search } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+ import { Dialog, DialogContent } from "@/components/ui/dialog"
7
+
8
+ const Command = React.forwardRef(({ className, ...props }, ref) => (
9
+ <CommandPrimitive
10
+ ref={ref}
11
+ className={cn(
12
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
13
+ className
14
+ )}
15
+ {...props} />
16
+ ))
17
+ Command.displayName = CommandPrimitive.displayName
18
+
19
+ const CommandDialog = ({
20
+ children,
21
+ ...props
22
+ }) => {
23
+ return (
24
+ <Dialog {...props}>
25
+ <DialogContent className="overflow-hidden p-0">
26
+ <Command
27
+ className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
28
+ {children}
29
+ </Command>
30
+ </DialogContent>
31
+ </Dialog>
32
+ );
33
+ }
34
+
35
+ const CommandInput = React.forwardRef(({ className, ...props }, ref) => (
36
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
37
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
38
+ <CommandPrimitive.Input
39
+ ref={ref}
40
+ className={cn(
41
+ "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
42
+ className
43
+ )}
44
+ {...props} />
45
+ </div>
46
+ ))
47
+
48
+ CommandInput.displayName = CommandPrimitive.Input.displayName
49
+
50
+ const CommandList = React.forwardRef(({ className, ...props }, ref) => (
51
+ <CommandPrimitive.List
52
+ ref={ref}
53
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
54
+ {...props} />
55
+ ))
56
+
57
+ CommandList.displayName = CommandPrimitive.List.displayName
58
+
59
+ const CommandEmpty = React.forwardRef((props, ref) => (
60
+ <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
61
+ ))
62
+
63
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName
64
+
65
+ const CommandGroup = React.forwardRef(({ className, ...props }, ref) => (
66
+ <CommandPrimitive.Group
67
+ ref={ref}
68
+ className={cn(
69
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
70
+ className
71
+ )}
72
+ {...props} />
73
+ ))
74
+
75
+ CommandGroup.displayName = CommandPrimitive.Group.displayName
76
+
77
+ const CommandSeparator = React.forwardRef(({ className, ...props }, ref) => (
78
+ <CommandPrimitive.Separator ref={ref} className={cn("-mx-1 h-px bg-border", className)} {...props} />
79
+ ))
80
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName
81
+
82
+ const CommandItem = React.forwardRef(({ className, ...props }, ref) => (
83
+ <CommandPrimitive.Item
84
+ ref={ref}
85
+ className={cn(
86
+ "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
87
+ className
88
+ )}
89
+ {...props} />
90
+ ))
91
+
92
+ CommandItem.displayName = CommandPrimitive.Item.displayName
93
+
94
+ const CommandShortcut = ({
95
+ className,
96
+ ...props
97
+ }) => {
98
+ return (
99
+ <span
100
+ className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
101
+ {...props} />
102
+ );
103
+ }
104
+ CommandShortcut.displayName = "CommandShortcut"
105
+
106
+ export {
107
+ Command,
108
+ CommandDialog,
109
+ CommandInput,
110
+ CommandList,
111
+ CommandEmpty,
112
+ CommandGroup,
113
+ CommandItem,
114
+ CommandShortcut,
115
+ CommandSeparator,
116
+ }
frontend/src/components/ui/context-menu.jsx ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const ContextMenu = ContextMenuPrimitive.Root
8
+
9
+ const ContextMenuTrigger = ContextMenuPrimitive.Trigger
10
+
11
+ const ContextMenuGroup = ContextMenuPrimitive.Group
12
+
13
+ const ContextMenuPortal = ContextMenuPrimitive.Portal
14
+
15
+ const ContextMenuSub = ContextMenuPrimitive.Sub
16
+
17
+ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
18
+
19
+ const ContextMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => (
20
+ <ContextMenuPrimitive.SubTrigger
21
+ ref={ref}
22
+ className={cn(
23
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
24
+ inset && "pl-8",
25
+ className
26
+ )}
27
+ {...props}>
28
+ {children}
29
+ <ChevronRight className="ml-auto h-4 w-4" />
30
+ </ContextMenuPrimitive.SubTrigger>
31
+ ))
32
+ ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
33
+
34
+ const ContextMenuSubContent = React.forwardRef(({ className, ...props }, ref) => (
35
+ <ContextMenuPrimitive.SubContent
36
+ ref={ref}
37
+ className={cn(
38
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-context-menu-content-transform-origin]",
39
+ className
40
+ )}
41
+ {...props} />
42
+ ))
43
+ ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
44
+
45
+ const ContextMenuContent = React.forwardRef(({ className, ...props }, ref) => (
46
+ <ContextMenuPrimitive.Portal>
47
+ <ContextMenuPrimitive.Content
48
+ ref={ref}
49
+ className={cn(
50
+ "z-50 max-h-[--radix-context-menu-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-context-menu-content-transform-origin]",
51
+ className
52
+ )}
53
+ {...props} />
54
+ </ContextMenuPrimitive.Portal>
55
+ ))
56
+ ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
57
+
58
+ const ContextMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (
59
+ <ContextMenuPrimitive.Item
60
+ ref={ref}
61
+ className={cn(
62
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
63
+ inset && "pl-8",
64
+ className
65
+ )}
66
+ {...props} />
67
+ ))
68
+ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
69
+
70
+ const ContextMenuCheckboxItem = React.forwardRef(({ className, children, checked, ...props }, ref) => (
71
+ <ContextMenuPrimitive.CheckboxItem
72
+ ref={ref}
73
+ className={cn(
74
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
75
+ className
76
+ )}
77
+ checked={checked}
78
+ {...props}>
79
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
80
+ <ContextMenuPrimitive.ItemIndicator>
81
+ <Check className="h-4 w-4" />
82
+ </ContextMenuPrimitive.ItemIndicator>
83
+ </span>
84
+ {children}
85
+ </ContextMenuPrimitive.CheckboxItem>
86
+ ))
87
+ ContextMenuCheckboxItem.displayName =
88
+ ContextMenuPrimitive.CheckboxItem.displayName
89
+
90
+ const ContextMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => (
91
+ <ContextMenuPrimitive.RadioItem
92
+ ref={ref}
93
+ className={cn(
94
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
95
+ className
96
+ )}
97
+ {...props}>
98
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
99
+ <ContextMenuPrimitive.ItemIndicator>
100
+ <Circle className="h-4 w-4 fill-current" />
101
+ </ContextMenuPrimitive.ItemIndicator>
102
+ </span>
103
+ {children}
104
+ </ContextMenuPrimitive.RadioItem>
105
+ ))
106
+ ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
107
+
108
+ const ContextMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => (
109
+ <ContextMenuPrimitive.Label
110
+ ref={ref}
111
+ className={cn(
112
+ "px-2 py-1.5 text-sm font-semibold text-foreground",
113
+ inset && "pl-8",
114
+ className
115
+ )}
116
+ {...props} />
117
+ ))
118
+ ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
119
+
120
+ const ContextMenuSeparator = React.forwardRef(({ className, ...props }, ref) => (
121
+ <ContextMenuPrimitive.Separator
122
+ ref={ref}
123
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
124
+ {...props} />
125
+ ))
126
+ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
127
+
128
+ const ContextMenuShortcut = ({
129
+ className,
130
+ ...props
131
+ }) => {
132
+ return (
133
+ <span
134
+ className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
135
+ {...props} />
136
+ );
137
+ }
138
+ ContextMenuShortcut.displayName = "ContextMenuShortcut"
139
+
140
+ export {
141
+ ContextMenu,
142
+ ContextMenuTrigger,
143
+ ContextMenuContent,
144
+ ContextMenuItem,
145
+ ContextMenuCheckboxItem,
146
+ ContextMenuRadioItem,
147
+ ContextMenuLabel,
148
+ ContextMenuSeparator,
149
+ ContextMenuShortcut,
150
+ ContextMenuGroup,
151
+ ContextMenuPortal,
152
+ ContextMenuSub,
153
+ ContextMenuSubContent,
154
+ ContextMenuSubTrigger,
155
+ ContextMenuRadioGroup,
156
+ }
frontend/src/components/ui/dialog.jsx ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
3
+ import { X } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Dialog = DialogPrimitive.Root
8
+
9
+ const DialogTrigger = DialogPrimitive.Trigger
10
+
11
+ const DialogPortal = DialogPrimitive.Portal
12
+
13
+ const DialogClose = DialogPrimitive.Close
14
+
15
+ const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
16
+ <DialogPrimitive.Overlay
17
+ ref={ref}
18
+ className={cn(
19
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
20
+ className
21
+ )}
22
+ {...props} />
23
+ ))
24
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
25
+
26
+ const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => (
27
+ <DialogPortal>
28
+ <DialogOverlay />
29
+ <DialogPrimitive.Content
30
+ ref={ref}
31
+ className={cn(
32
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
33
+ className
34
+ )}
35
+ {...props}>
36
+ {children}
37
+ <DialogPrimitive.Close
38
+ className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
39
+ <X className="h-4 w-4" />
40
+ <span className="sr-only">Close</span>
41
+ </DialogPrimitive.Close>
42
+ </DialogPrimitive.Content>
43
+ </DialogPortal>
44
+ ))
45
+ DialogContent.displayName = DialogPrimitive.Content.displayName
46
+
47
+ const DialogHeader = ({
48
+ className,
49
+ ...props
50
+ }) => (
51
+ <div
52
+ className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)}
53
+ {...props} />
54
+ )
55
+ DialogHeader.displayName = "DialogHeader"
56
+
57
+ const DialogFooter = ({
58
+ className,
59
+ ...props
60
+ }) => (
61
+ <div
62
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
63
+ {...props} />
64
+ )
65
+ DialogFooter.displayName = "DialogFooter"
66
+
67
+ const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
68
+ <DialogPrimitive.Title
69
+ ref={ref}
70
+ className={cn("text-lg font-semibold leading-none tracking-tight", className)}
71
+ {...props} />
72
+ ))
73
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
74
+
75
+ const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
76
+ <DialogPrimitive.Description
77
+ ref={ref}
78
+ className={cn("text-sm text-muted-foreground", className)}
79
+ {...props} />
80
+ ))
81
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
82
+
83
+ export {
84
+ Dialog,
85
+ DialogPortal,
86
+ DialogOverlay,
87
+ DialogTrigger,
88
+ DialogClose,
89
+ DialogContent,
90
+ DialogHeader,
91
+ DialogFooter,
92
+ DialogTitle,
93
+ DialogDescription,
94
+ }
frontend/src/components/ui/drawer.jsx ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Drawer as DrawerPrimitive } from "vaul"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Drawer = ({
7
+ shouldScaleBackground = true,
8
+ ...props
9
+ }) => (
10
+ <DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />
11
+ )
12
+ Drawer.displayName = "Drawer"
13
+
14
+ const DrawerTrigger = DrawerPrimitive.Trigger
15
+
16
+ const DrawerPortal = DrawerPrimitive.Portal
17
+
18
+ const DrawerClose = DrawerPrimitive.Close
19
+
20
+ const DrawerOverlay = React.forwardRef(({ className, ...props }, ref) => (
21
+ <DrawerPrimitive.Overlay
22
+ ref={ref}
23
+ className={cn("fixed inset-0 z-50 bg-black/80", className)}
24
+ {...props} />
25
+ ))
26
+ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
27
+
28
+ const DrawerContent = React.forwardRef(({ className, children, ...props }, ref) => (
29
+ <DrawerPortal>
30
+ <DrawerOverlay />
31
+ <DrawerPrimitive.Content
32
+ ref={ref}
33
+ className={cn(
34
+ "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
35
+ className
36
+ )}
37
+ {...props}>
38
+ <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
39
+ {children}
40
+ </DrawerPrimitive.Content>
41
+ </DrawerPortal>
42
+ ))
43
+ DrawerContent.displayName = "DrawerContent"
44
+
45
+ const DrawerHeader = ({
46
+ className,
47
+ ...props
48
+ }) => (
49
+ <div
50
+ className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
51
+ {...props} />
52
+ )
53
+ DrawerHeader.displayName = "DrawerHeader"
54
+
55
+ const DrawerFooter = ({
56
+ className,
57
+ ...props
58
+ }) => (
59
+ <div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
60
+ )
61
+ DrawerFooter.displayName = "DrawerFooter"
62
+
63
+ const DrawerTitle = React.forwardRef(({ className, ...props }, ref) => (
64
+ <DrawerPrimitive.Title
65
+ ref={ref}
66
+ className={cn("text-lg font-semibold leading-none tracking-tight", className)}
67
+ {...props} />
68
+ ))
69
+ DrawerTitle.displayName = DrawerPrimitive.Title.displayName
70
+
71
+ const DrawerDescription = React.forwardRef(({ className, ...props }, ref) => (
72
+ <DrawerPrimitive.Description
73
+ ref={ref}
74
+ className={cn("text-sm text-muted-foreground", className)}
75
+ {...props} />
76
+ ))
77
+ DrawerDescription.displayName = DrawerPrimitive.Description.displayName
78
+
79
+ export {
80
+ Drawer,
81
+ DrawerPortal,
82
+ DrawerOverlay,
83
+ DrawerTrigger,
84
+ DrawerClose,
85
+ DrawerContent,
86
+ DrawerHeader,
87
+ DrawerFooter,
88
+ DrawerTitle,
89
+ DrawerDescription,
90
+ }
frontend/src/components/ui/dropdown-menu.jsx ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const DropdownMenu = DropdownMenuPrimitive.Root
8
+
9
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
10
+
11
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
12
+
13
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
14
+
15
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
16
+
17
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
18
+
19
+ const DropdownMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => (
20
+ <DropdownMenuPrimitive.SubTrigger
21
+ ref={ref}
22
+ className={cn(
23
+ "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
24
+ inset && "pl-8",
25
+ className
26
+ )}
27
+ {...props}>
28
+ {children}
29
+ <ChevronRight className="ml-auto" />
30
+ </DropdownMenuPrimitive.SubTrigger>
31
+ ))
32
+ DropdownMenuSubTrigger.displayName =
33
+ DropdownMenuPrimitive.SubTrigger.displayName
34
+
35
+ const DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) => (
36
+ <DropdownMenuPrimitive.SubContent
37
+ ref={ref}
38
+ className={cn(
39
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
40
+ className
41
+ )}
42
+ {...props} />
43
+ ))
44
+ DropdownMenuSubContent.displayName =
45
+ DropdownMenuPrimitive.SubContent.displayName
46
+
47
+ const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (
48
+ <DropdownMenuPrimitive.Portal>
49
+ <DropdownMenuPrimitive.Content
50
+ ref={ref}
51
+ sideOffset={sideOffset}
52
+ className={cn(
53
+ "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
54
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
55
+ className
56
+ )}
57
+ {...props} />
58
+ </DropdownMenuPrimitive.Portal>
59
+ ))
60
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
61
+
62
+ const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (
63
+ <DropdownMenuPrimitive.Item
64
+ ref={ref}
65
+ className={cn(
66
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
67
+ inset && "pl-8",
68
+ className
69
+ )}
70
+ {...props} />
71
+ ))
72
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
73
+
74
+ const DropdownMenuCheckboxItem = React.forwardRef(({ className, children, checked, ...props }, ref) => (
75
+ <DropdownMenuPrimitive.CheckboxItem
76
+ ref={ref}
77
+ className={cn(
78
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
79
+ className
80
+ )}
81
+ checked={checked}
82
+ {...props}>
83
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
84
+ <DropdownMenuPrimitive.ItemIndicator>
85
+ <Check className="h-4 w-4" />
86
+ </DropdownMenuPrimitive.ItemIndicator>
87
+ </span>
88
+ {children}
89
+ </DropdownMenuPrimitive.CheckboxItem>
90
+ ))
91
+ DropdownMenuCheckboxItem.displayName =
92
+ DropdownMenuPrimitive.CheckboxItem.displayName
93
+
94
+ const DropdownMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => (
95
+ <DropdownMenuPrimitive.RadioItem
96
+ ref={ref}
97
+ className={cn(
98
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
99
+ className
100
+ )}
101
+ {...props}>
102
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
103
+ <DropdownMenuPrimitive.ItemIndicator>
104
+ <Circle className="h-2 w-2 fill-current" />
105
+ </DropdownMenuPrimitive.ItemIndicator>
106
+ </span>
107
+ {children}
108
+ </DropdownMenuPrimitive.RadioItem>
109
+ ))
110
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
111
+
112
+ const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => (
113
+ <DropdownMenuPrimitive.Label
114
+ ref={ref}
115
+ className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
116
+ {...props} />
117
+ ))
118
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
119
+
120
+ const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => (
121
+ <DropdownMenuPrimitive.Separator
122
+ ref={ref}
123
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
124
+ {...props} />
125
+ ))
126
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
127
+
128
+ const DropdownMenuShortcut = ({
129
+ className,
130
+ ...props
131
+ }) => {
132
+ return (
133
+ <span
134
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
135
+ {...props} />
136
+ );
137
+ }
138
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
139
+
140
+ export {
141
+ DropdownMenu,
142
+ DropdownMenuTrigger,
143
+ DropdownMenuContent,
144
+ DropdownMenuItem,
145
+ DropdownMenuCheckboxItem,
146
+ DropdownMenuRadioItem,
147
+ DropdownMenuLabel,
148
+ DropdownMenuSeparator,
149
+ DropdownMenuShortcut,
150
+ DropdownMenuGroup,
151
+ DropdownMenuPortal,
152
+ DropdownMenuSub,
153
+ DropdownMenuSubContent,
154
+ DropdownMenuSubTrigger,
155
+ DropdownMenuRadioGroup,
156
+ }
frontend/src/components/ui/form.jsx ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { Controller, FormProvider, useFormContext } from "react-hook-form";
4
+
5
+ import { cn } from "@/lib/utils"
6
+ import { Label } from "@/components/ui/label"
7
+
8
+ const Form = FormProvider
9
+
10
+ const FormFieldContext = React.createContext({})
11
+
12
+ const FormField = (
13
+ {
14
+ ...props
15
+ }
16
+ ) => {
17
+ return (
18
+ <FormFieldContext.Provider value={{ name: props.name }}>
19
+ <Controller {...props} />
20
+ </FormFieldContext.Provider>
21
+ );
22
+ }
23
+
24
+ const useFormField = () => {
25
+ const fieldContext = React.useContext(FormFieldContext)
26
+ const itemContext = React.useContext(FormItemContext)
27
+ const { getFieldState, formState } = useFormContext()
28
+
29
+ const fieldState = getFieldState(fieldContext.name, formState)
30
+
31
+ if (!fieldContext) {
32
+ throw new Error("useFormField should be used within <FormField>")
33
+ }
34
+
35
+ const { id } = itemContext
36
+
37
+ return {
38
+ id,
39
+ name: fieldContext.name,
40
+ formItemId: `${id}-form-item`,
41
+ formDescriptionId: `${id}-form-item-description`,
42
+ formMessageId: `${id}-form-item-message`,
43
+ ...fieldState,
44
+ }
45
+ }
46
+
47
+ const FormItemContext = React.createContext({})
48
+
49
+ const FormItem = React.forwardRef(({ className, ...props }, ref) => {
50
+ const id = React.useId()
51
+
52
+ return (
53
+ <FormItemContext.Provider value={{ id }}>
54
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
55
+ </FormItemContext.Provider>
56
+ );
57
+ })
58
+ FormItem.displayName = "FormItem"
59
+
60
+ const FormLabel = React.forwardRef(({ className, ...props }, ref) => {
61
+ const { error, formItemId } = useFormField()
62
+
63
+ return (
64
+ <Label
65
+ ref={ref}
66
+ className={cn(error && "text-destructive", className)}
67
+ htmlFor={formItemId}
68
+ {...props} />
69
+ );
70
+ })
71
+ FormLabel.displayName = "FormLabel"
72
+
73
+ const FormControl = React.forwardRef(({ ...props }, ref) => {
74
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
75
+
76
+ return (
77
+ <Slot
78
+ ref={ref}
79
+ id={formItemId}
80
+ aria-describedby={
81
+ !error
82
+ ? `${formDescriptionId}`
83
+ : `${formDescriptionId} ${formMessageId}`
84
+ }
85
+ aria-invalid={!!error}
86
+ {...props} />
87
+ );
88
+ })
89
+ FormControl.displayName = "FormControl"
90
+
91
+ const FormDescription = React.forwardRef(({ className, ...props }, ref) => {
92
+ const { formDescriptionId } = useFormField()
93
+
94
+ return (
95
+ <p
96
+ ref={ref}
97
+ id={formDescriptionId}
98
+ className={cn("text-[0.8rem] text-muted-foreground", className)}
99
+ {...props} />
100
+ );
101
+ })
102
+ FormDescription.displayName = "FormDescription"
103
+
104
+ const FormMessage = React.forwardRef(({ className, children, ...props }, ref) => {
105
+ const { error, formMessageId } = useFormField()
106
+ const body = error ? String(error?.message ?? "") : children
107
+
108
+ if (!body) {
109
+ return null
110
+ }
111
+
112
+ return (
113
+ <p
114
+ ref={ref}
115
+ id={formMessageId}
116
+ className={cn("text-[0.8rem] font-medium text-destructive", className)}
117
+ {...props}>
118
+ {body}
119
+ </p>
120
+ );
121
+ })
122
+ FormMessage.displayName = "FormMessage"
123
+
124
+ export {
125
+ useFormField,
126
+ Form,
127
+ FormItem,
128
+ FormLabel,
129
+ FormControl,
130
+ FormDescription,
131
+ FormMessage,
132
+ FormField,
133
+ }
frontend/src/components/ui/hover-card.jsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const HoverCard = HoverCardPrimitive.Root
7
+
8
+ const HoverCardTrigger = HoverCardPrimitive.Trigger
9
+
10
+ const HoverCardContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
11
+ <HoverCardPrimitive.Content
12
+ ref={ref}
13
+ align={align}
14
+ sideOffset={sideOffset}
15
+ className={cn(
16
+ "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]",
17
+ className
18
+ )}
19
+ {...props} />
20
+ ))
21
+ HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
22
+
23
+ export { HoverCard, HoverCardTrigger, HoverCardContent }
frontend/src/components/ui/input-otp.jsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { OTPInput, OTPInputContext } from "input-otp"
3
+ import { Minus } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const InputOTP = React.forwardRef(({ className, containerClassName, ...props }, ref) => (
8
+ <OTPInput
9
+ ref={ref}
10
+ containerClassName={cn("flex items-center gap-2 has-[:disabled]:opacity-50", containerClassName)}
11
+ className={cn("disabled:cursor-not-allowed", className)}
12
+ {...props} />
13
+ ))
14
+ InputOTP.displayName = "InputOTP"
15
+
16
+ const InputOTPGroup = React.forwardRef(({ className, ...props }, ref) => (
17
+ <div ref={ref} className={cn("flex items-center", className)} {...props} />
18
+ ))
19
+ InputOTPGroup.displayName = "InputOTPGroup"
20
+
21
+ const InputOTPSlot = React.forwardRef(({ index, className, ...props }, ref) => {
22
+ const inputOTPContext = React.useContext(OTPInputContext)
23
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
24
+
25
+ return (
26
+ <div
27
+ ref={ref}
28
+ className={cn(
29
+ "relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
30
+ isActive && "z-10 ring-1 ring-ring",
31
+ className
32
+ )}
33
+ {...props}>
34
+ {char}
35
+ {hasFakeCaret && (
36
+ <div
37
+ className="pointer-events-none absolute inset-0 flex items-center justify-center">
38
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
39
+ </div>
40
+ )}
41
+ </div>
42
+ );
43
+ })
44
+ InputOTPSlot.displayName = "InputOTPSlot"
45
+
46
+ const InputOTPSeparator = React.forwardRef(({ ...props }, ref) => (
47
+ <div ref={ref} role="separator" {...props}>
48
+ <Minus />
49
+ </div>
50
+ ))
51
+ InputOTPSeparator.displayName = "InputOTPSeparator"
52
+
53
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
frontend/src/components/ui/input.jsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Input = React.forwardRef(({ className, type, ...props }, ref) => {
6
+ return (
7
+ <input
8
+ type={type}
9
+ className={cn(
10
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
11
+ className
12
+ )}
13
+ ref={ref}
14
+ {...props} />
15
+ );
16
+ })
17
+ Input.displayName = "Input"
18
+
19
+ export { Input }
frontend/src/components/ui/label.jsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { cva } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const labelVariants = cva(
8
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9
+ )
10
+
11
+ const Label = React.forwardRef(({ className, ...props }, ref) => (
12
+ <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
13
+ ))
14
+ Label.displayName = LabelPrimitive.Root.displayName
15
+
16
+ export { Label }
frontend/src/components/ui/menubar.jsx ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as MenubarPrimitive from "@radix-ui/react-menubar"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ function MenubarMenu({
8
+ ...props
9
+ }) {
10
+ return <MenubarPrimitive.Menu {...props} />;
11
+ }
12
+
13
+ function MenubarGroup({
14
+ ...props
15
+ }) {
16
+ return <MenubarPrimitive.Group {...props} />;
17
+ }
18
+
19
+ function MenubarPortal({
20
+ ...props
21
+ }) {
22
+ return <MenubarPrimitive.Portal {...props} />;
23
+ }
24
+
25
+ function MenubarRadioGroup({
26
+ ...props
27
+ }) {
28
+ return <MenubarPrimitive.RadioGroup {...props} />;
29
+ }
30
+
31
+ function MenubarSub({
32
+ ...props
33
+ }) {
34
+ return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;
35
+ }
36
+
37
+ const Menubar = React.forwardRef(({ className, ...props }, ref) => (
38
+ <MenubarPrimitive.Root
39
+ ref={ref}
40
+ className={cn(
41
+ "flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm",
42
+ className
43
+ )}
44
+ {...props} />
45
+ ))
46
+ Menubar.displayName = MenubarPrimitive.Root.displayName
47
+
48
+ const MenubarTrigger = React.forwardRef(({ className, ...props }, ref) => (
49
+ <MenubarPrimitive.Trigger
50
+ ref={ref}
51
+ className={cn(
52
+ "flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
53
+ className
54
+ )}
55
+ {...props} />
56
+ ))
57
+ MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
58
+
59
+ const MenubarSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => (
60
+ <MenubarPrimitive.SubTrigger
61
+ ref={ref}
62
+ className={cn(
63
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
64
+ inset && "pl-8",
65
+ className
66
+ )}
67
+ {...props}>
68
+ {children}
69
+ <ChevronRight className="ml-auto h-4 w-4" />
70
+ </MenubarPrimitive.SubTrigger>
71
+ ))
72
+ MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
73
+
74
+ const MenubarSubContent = React.forwardRef(({ className, ...props }, ref) => (
75
+ <MenubarPrimitive.SubContent
76
+ ref={ref}
77
+ className={cn(
78
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-menubar-content-transform-origin]",
79
+ className
80
+ )}
81
+ {...props} />
82
+ ))
83
+ MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
84
+
85
+ const MenubarContent = React.forwardRef((
86
+ { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
87
+ ref
88
+ ) => (
89
+ <MenubarPrimitive.Portal>
90
+ <MenubarPrimitive.Content
91
+ ref={ref}
92
+ align={align}
93
+ alignOffset={alignOffset}
94
+ sideOffset={sideOffset}
95
+ className={cn(
96
+ "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-menubar-content-transform-origin]",
97
+ className
98
+ )}
99
+ {...props} />
100
+ </MenubarPrimitive.Portal>
101
+ ))
102
+ MenubarContent.displayName = MenubarPrimitive.Content.displayName
103
+
104
+ const MenubarItem = React.forwardRef(({ className, inset, ...props }, ref) => (
105
+ <MenubarPrimitive.Item
106
+ ref={ref}
107
+ className={cn(
108
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
109
+ inset && "pl-8",
110
+ className
111
+ )}
112
+ {...props} />
113
+ ))
114
+ MenubarItem.displayName = MenubarPrimitive.Item.displayName
115
+
116
+ const MenubarCheckboxItem = React.forwardRef(({ className, children, checked, ...props }, ref) => (
117
+ <MenubarPrimitive.CheckboxItem
118
+ ref={ref}
119
+ className={cn(
120
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
121
+ className
122
+ )}
123
+ checked={checked}
124
+ {...props}>
125
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
126
+ <MenubarPrimitive.ItemIndicator>
127
+ <Check className="h-4 w-4" />
128
+ </MenubarPrimitive.ItemIndicator>
129
+ </span>
130
+ {children}
131
+ </MenubarPrimitive.CheckboxItem>
132
+ ))
133
+ MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
134
+
135
+ const MenubarRadioItem = React.forwardRef(({ className, children, ...props }, ref) => (
136
+ <MenubarPrimitive.RadioItem
137
+ ref={ref}
138
+ className={cn(
139
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
140
+ className
141
+ )}
142
+ {...props}>
143
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
144
+ <MenubarPrimitive.ItemIndicator>
145
+ <Circle className="h-4 w-4 fill-current" />
146
+ </MenubarPrimitive.ItemIndicator>
147
+ </span>
148
+ {children}
149
+ </MenubarPrimitive.RadioItem>
150
+ ))
151
+ MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
152
+
153
+ const MenubarLabel = React.forwardRef(({ className, inset, ...props }, ref) => (
154
+ <MenubarPrimitive.Label
155
+ ref={ref}
156
+ className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
157
+ {...props} />
158
+ ))
159
+ MenubarLabel.displayName = MenubarPrimitive.Label.displayName
160
+
161
+ const MenubarSeparator = React.forwardRef(({ className, ...props }, ref) => (
162
+ <MenubarPrimitive.Separator
163
+ ref={ref}
164
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
165
+ {...props} />
166
+ ))
167
+ MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
168
+
169
+ const MenubarShortcut = ({
170
+ className,
171
+ ...props
172
+ }) => {
173
+ return (
174
+ <span
175
+ className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
176
+ {...props} />
177
+ );
178
+ }
179
+ MenubarShortcut.displayname = "MenubarShortcut"
180
+
181
+ export {
182
+ Menubar,
183
+ MenubarMenu,
184
+ MenubarTrigger,
185
+ MenubarContent,
186
+ MenubarItem,
187
+ MenubarSeparator,
188
+ MenubarLabel,
189
+ MenubarCheckboxItem,
190
+ MenubarRadioGroup,
191
+ MenubarRadioItem,
192
+ MenubarPortal,
193
+ MenubarSubContent,
194
+ MenubarSubTrigger,
195
+ MenubarGroup,
196
+ MenubarSub,
197
+ MenubarShortcut,
198
+ }
frontend/src/components/ui/navigation-menu.jsx ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
3
+ import { cva } from "class-variance-authority"
4
+ import { ChevronDown } from "lucide-react"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const NavigationMenu = React.forwardRef(({ className, children, ...props }, ref) => (
9
+ <NavigationMenuPrimitive.Root
10
+ ref={ref}
11
+ className={cn(
12
+ "relative z-10 flex max-w-max flex-1 items-center justify-center",
13
+ className
14
+ )}
15
+ {...props}>
16
+ {children}
17
+ <NavigationMenuViewport />
18
+ </NavigationMenuPrimitive.Root>
19
+ ))
20
+ NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
21
+
22
+ const NavigationMenuList = React.forwardRef(({ className, ...props }, ref) => (
23
+ <NavigationMenuPrimitive.List
24
+ ref={ref}
25
+ className={cn(
26
+ "group flex flex-1 list-none items-center justify-center space-x-1",
27
+ className
28
+ )}
29
+ {...props} />
30
+ ))
31
+ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
32
+
33
+ const NavigationMenuItem = NavigationMenuPrimitive.Item
34
+
35
+ const navigationMenuTriggerStyle = cva(
36
+ "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"
37
+ )
38
+
39
+ const NavigationMenuTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
40
+ <NavigationMenuPrimitive.Trigger
41
+ ref={ref}
42
+ className={cn(navigationMenuTriggerStyle(), "group", className)}
43
+ {...props}>
44
+ {children}{" "}
45
+ <ChevronDown
46
+ className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
47
+ aria-hidden="true" />
48
+ </NavigationMenuPrimitive.Trigger>
49
+ ))
50
+ NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
51
+
52
+ const NavigationMenuContent = React.forwardRef(({ className, ...props }, ref) => (
53
+ <NavigationMenuPrimitive.Content
54
+ ref={ref}
55
+ className={cn(
56
+ "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
57
+ className
58
+ )}
59
+ {...props} />
60
+ ))
61
+ NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
62
+
63
+ const NavigationMenuLink = NavigationMenuPrimitive.Link
64
+
65
+ const NavigationMenuViewport = React.forwardRef(({ className, ...props }, ref) => (
66
+ <div className={cn("absolute left-0 top-full flex justify-center")}>
67
+ <NavigationMenuPrimitive.Viewport
68
+ className={cn(
69
+ "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
70
+ className
71
+ )}
72
+ ref={ref}
73
+ {...props} />
74
+ </div>
75
+ ))
76
+ NavigationMenuViewport.displayName =
77
+ NavigationMenuPrimitive.Viewport.displayName
78
+
79
+ const NavigationMenuIndicator = React.forwardRef(({ className, ...props }, ref) => (
80
+ <NavigationMenuPrimitive.Indicator
81
+ ref={ref}
82
+ className={cn(
83
+ "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
84
+ className
85
+ )}
86
+ {...props}>
87
+ <div
88
+ className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
89
+ </NavigationMenuPrimitive.Indicator>
90
+ ))
91
+ NavigationMenuIndicator.displayName =
92
+ NavigationMenuPrimitive.Indicator.displayName
93
+
94
+ export {
95
+ navigationMenuTriggerStyle,
96
+ NavigationMenu,
97
+ NavigationMenuList,
98
+ NavigationMenuItem,
99
+ NavigationMenuContent,
100
+ NavigationMenuTrigger,
101
+ NavigationMenuLink,
102
+ NavigationMenuIndicator,
103
+ NavigationMenuViewport,
104
+ }
frontend/src/components/ui/pagination.jsx ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+ import { buttonVariants } from "@/components/ui/button";
6
+
7
+ const Pagination = ({
8
+ className,
9
+ ...props
10
+ }) => (
11
+ <nav
12
+ role="navigation"
13
+ aria-label="pagination"
14
+ className={cn("mx-auto flex w-full justify-center", className)}
15
+ {...props} />
16
+ )
17
+ Pagination.displayName = "Pagination"
18
+
19
+ const PaginationContent = React.forwardRef(({ className, ...props }, ref) => (
20
+ <ul
21
+ ref={ref}
22
+ className={cn("flex flex-row items-center gap-1", className)}
23
+ {...props} />
24
+ ))
25
+ PaginationContent.displayName = "PaginationContent"
26
+
27
+ const PaginationItem = React.forwardRef(({ className, ...props }, ref) => (
28
+ <li ref={ref} className={cn("", className)} {...props} />
29
+ ))
30
+ PaginationItem.displayName = "PaginationItem"
31
+
32
+ const PaginationLink = ({
33
+ className,
34
+ isActive,
35
+ size = "icon",
36
+ ...props
37
+ }) => (
38
+ <a
39
+ aria-current={isActive ? "page" : undefined}
40
+ className={cn(buttonVariants({
41
+ variant: isActive ? "outline" : "ghost",
42
+ size,
43
+ }), className)}
44
+ {...props} />
45
+ )
46
+ PaginationLink.displayName = "PaginationLink"
47
+
48
+ const PaginationPrevious = ({
49
+ className,
50
+ ...props
51
+ }) => (
52
+ <PaginationLink
53
+ aria-label="Go to previous page"
54
+ size="default"
55
+ className={cn("gap-1 pl-2.5", className)}
56
+ {...props}>
57
+ <ChevronLeft className="h-4 w-4" />
58
+ <span>Previous</span>
59
+ </PaginationLink>
60
+ )
61
+ PaginationPrevious.displayName = "PaginationPrevious"
62
+
63
+ const PaginationNext = ({
64
+ className,
65
+ ...props
66
+ }) => (
67
+ <PaginationLink
68
+ aria-label="Go to next page"
69
+ size="default"
70
+ className={cn("gap-1 pr-2.5", className)}
71
+ {...props}>
72
+ <span>Next</span>
73
+ <ChevronRight className="h-4 w-4" />
74
+ </PaginationLink>
75
+ )
76
+ PaginationNext.displayName = "PaginationNext"
77
+
78
+ const PaginationEllipsis = ({
79
+ className,
80
+ ...props
81
+ }) => (
82
+ <span
83
+ aria-hidden
84
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
85
+ {...props}>
86
+ <MoreHorizontal className="h-4 w-4" />
87
+ <span className="sr-only">More pages</span>
88
+ </span>
89
+ )
90
+ PaginationEllipsis.displayName = "PaginationEllipsis"
91
+
92
+ export {
93
+ Pagination,
94
+ PaginationContent,
95
+ PaginationLink,
96
+ PaginationItem,
97
+ PaginationPrevious,
98
+ PaginationNext,
99
+ PaginationEllipsis,
100
+ }
frontend/src/components/ui/popover.jsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as PopoverPrimitive from "@radix-ui/react-popover"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Popover = PopoverPrimitive.Root
7
+
8
+ const PopoverTrigger = PopoverPrimitive.Trigger
9
+
10
+ const PopoverAnchor = PopoverPrimitive.Anchor
11
+
12
+ const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
13
+ <PopoverPrimitive.Portal>
14
+ <PopoverPrimitive.Content
15
+ ref={ref}
16
+ align={align}
17
+ sideOffset={sideOffset}
18
+ className={cn(
19
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",
20
+ className
21
+ )}
22
+ {...props} />
23
+ </PopoverPrimitive.Portal>
24
+ ))
25
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName
26
+
27
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
frontend/src/components/ui/progress.jsx ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as ProgressPrimitive from "@radix-ui/react-progress"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Progress = React.forwardRef(({ className, value, ...props }, ref) => (
7
+ <ProgressPrimitive.Root
8
+ ref={ref}
9
+ className={cn(
10
+ "relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
11
+ className
12
+ )}
13
+ {...props}>
14
+ <ProgressPrimitive.Indicator
15
+ className="h-full w-full flex-1 bg-primary transition-all"
16
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }} />
17
+ </ProgressPrimitive.Root>
18
+ ))
19
+ Progress.displayName = ProgressPrimitive.Root.displayName
20
+
21
+ export { Progress }
frontend/src/components/ui/radio-group.jsx ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
3
+ import { Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const RadioGroup = React.forwardRef(({ className, ...props }, ref) => {
8
+ return (<RadioGroupPrimitive.Root className={cn("grid gap-2", className)} {...props} ref={ref} />);
9
+ })
10
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
11
+
12
+ const RadioGroupItem = React.forwardRef(({ className, ...props }, ref) => {
13
+ return (
14
+ <RadioGroupPrimitive.Item
15
+ ref={ref}
16
+ className={cn(
17
+ "aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
18
+ className
19
+ )}
20
+ {...props}>
21
+ <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
22
+ <Circle className="h-3.5 w-3.5 fill-primary" />
23
+ </RadioGroupPrimitive.Indicator>
24
+ </RadioGroupPrimitive.Item>
25
+ );
26
+ })
27
+ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
28
+
29
+ export { RadioGroup, RadioGroupItem }
frontend/src/components/ui/resizable.jsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { GripVertical } from "lucide-react"
2
+ import * as ResizablePrimitive from "react-resizable-panels"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const ResizablePanelGroup = ({
7
+ className,
8
+ ...props
9
+ }) => (
10
+ <ResizablePrimitive.PanelGroup
11
+ className={cn(
12
+ "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
13
+ className
14
+ )}
15
+ {...props} />
16
+ )
17
+
18
+ const ResizablePanel = ResizablePrimitive.Panel
19
+
20
+ const ResizableHandle = ({
21
+ withHandle,
22
+ className,
23
+ ...props
24
+ }) => (
25
+ <ResizablePrimitive.PanelResizeHandle
26
+ className={cn(
27
+ "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
28
+ className
29
+ )}
30
+ {...props}>
31
+ {withHandle && (
32
+ <div
33
+ className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
34
+ <GripVertical className="h-2.5 w-2.5" />
35
+ </div>
36
+ )}
37
+ </ResizablePrimitive.PanelResizeHandle>
38
+ )
39
+
40
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
frontend/src/components/ui/scroll-area.jsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => (
7
+ <ScrollAreaPrimitive.Root
8
+ ref={ref}
9
+ className={cn("relative overflow-hidden", className)}
10
+ {...props}>
11
+ <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
12
+ {children}
13
+ </ScrollAreaPrimitive.Viewport>
14
+ <ScrollBar />
15
+ <ScrollAreaPrimitive.Corner />
16
+ </ScrollAreaPrimitive.Root>
17
+ ))
18
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
19
+
20
+ const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => (
21
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
22
+ ref={ref}
23
+ orientation={orientation}
24
+ className={cn(
25
+ "flex touch-none select-none transition-colors",
26
+ orientation === "vertical" &&
27
+ "h-full w-2.5 border-l border-l-transparent p-[1px]",
28
+ orientation === "horizontal" &&
29
+ "h-2.5 flex-col border-t border-t-transparent p-[1px]",
30
+ className
31
+ )}
32
+ {...props}>
33
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
34
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
35
+ ))
36
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
37
+
38
+ export { ScrollArea, ScrollBar }
frontend/src/components/ui/select.jsx ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as SelectPrimitive from "@radix-ui/react-select"
3
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Select = SelectPrimitive.Root
8
+
9
+ const SelectGroup = SelectPrimitive.Group
10
+
11
+ const SelectValue = SelectPrimitive.Value
12
+
13
+ const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
14
+ <SelectPrimitive.Trigger
15
+ ref={ref}
16
+ className={cn(
17
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
18
+ className
19
+ )}
20
+ {...props}>
21
+ {children}
22
+ <SelectPrimitive.Icon asChild>
23
+ <ChevronDown className="h-4 w-4 opacity-50" />
24
+ </SelectPrimitive.Icon>
25
+ </SelectPrimitive.Trigger>
26
+ ))
27
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
28
+
29
+ const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => (
30
+ <SelectPrimitive.ScrollUpButton
31
+ ref={ref}
32
+ className={cn("flex cursor-default items-center justify-center py-1", className)}
33
+ {...props}>
34
+ <ChevronUp className="h-4 w-4" />
35
+ </SelectPrimitive.ScrollUpButton>
36
+ ))
37
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
38
+
39
+ const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => (
40
+ <SelectPrimitive.ScrollDownButton
41
+ ref={ref}
42
+ className={cn("flex cursor-default items-center justify-center py-1", className)}
43
+ {...props}>
44
+ <ChevronDown className="h-4 w-4" />
45
+ </SelectPrimitive.ScrollDownButton>
46
+ ))
47
+ SelectScrollDownButton.displayName =
48
+ SelectPrimitive.ScrollDownButton.displayName
49
+
50
+ const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => (
51
+ <SelectPrimitive.Portal>
52
+ <SelectPrimitive.Content
53
+ ref={ref}
54
+ className={cn(
55
+ "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
56
+ position === "popper" &&
57
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
58
+ className
59
+ )}
60
+ position={position}
61
+ {...props}>
62
+ <SelectScrollUpButton />
63
+ <SelectPrimitive.Viewport
64
+ className={cn("p-1", position === "popper" &&
65
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]")}>
66
+ {children}
67
+ </SelectPrimitive.Viewport>
68
+ <SelectScrollDownButton />
69
+ </SelectPrimitive.Content>
70
+ </SelectPrimitive.Portal>
71
+ ))
72
+ SelectContent.displayName = SelectPrimitive.Content.displayName
73
+
74
+ const SelectLabel = React.forwardRef(({ className, ...props }, ref) => (
75
+ <SelectPrimitive.Label
76
+ ref={ref}
77
+ className={cn("px-2 py-1.5 text-sm font-semibold", className)}
78
+ {...props} />
79
+ ))
80
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
81
+
82
+ const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => (
83
+ <SelectPrimitive.Item
84
+ ref={ref}
85
+ className={cn(
86
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
87
+ className
88
+ )}
89
+ {...props}>
90
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
91
+ <SelectPrimitive.ItemIndicator>
92
+ <Check className="h-4 w-4" />
93
+ </SelectPrimitive.ItemIndicator>
94
+ </span>
95
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
96
+ </SelectPrimitive.Item>
97
+ ))
98
+ SelectItem.displayName = SelectPrimitive.Item.displayName
99
+
100
+ const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => (
101
+ <SelectPrimitive.Separator
102
+ ref={ref}
103
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
104
+ {...props} />
105
+ ))
106
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
107
+
108
+ export {
109
+ Select,
110
+ SelectGroup,
111
+ SelectValue,
112
+ SelectTrigger,
113
+ SelectContent,
114
+ SelectLabel,
115
+ SelectItem,
116
+ SelectSeparator,
117
+ SelectScrollUpButton,
118
+ SelectScrollDownButton,
119
+ }