commit 78487430fe8003ec9f21919db35dc75c8d25e39c Author: sumona-banerjeee Date: Wed Jan 28 16:36:10 2026 +0530 VisionRoad-Frontend diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef1aed0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# dependencies +node_modules/ + +# Next.js build output +.next/ +out/ +dist/ + +# environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +logs/ +*.log + +# lock files (OPTIONAL – keep only ONE) +# If you use npm → keep package-lock.json +# If you use pnpm → keep pnpm-lock.yaml +# If you use yarn → keep yarn.lock +# Uncomment the ones you DON'T use + +# yarn.lock +# pnpm-lock.yaml +# package-lock.json + +# OS files +.DS_Store +Thumbs.db + +# Editor / IDE +.vscode/* +!.vscode/extensions.json +.idea/ +*.swp +*.swo + +# TypeScript +*.tsbuildinfo + +# Test / coverage +coverage/ + +# Misc +.vercel diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..0264b06 --- /dev/null +++ b/Readme.md @@ -0,0 +1,584 @@ +# YOLOPOTHOLE - Pothole Detection System + +Real-time pothole detection system using YOLO with adaptive ROI, ByteTrack tracking, and WebSocket-based progress monitoring. + +--- + +## 📋 Table of Contents + +- [Features](#features) +- [Architecture](#architecture) +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Configuration](#configuration) +- [Usage](#usage) +- [API Reference](#api-reference) +- [WebSocket Protocol](#websocket-protocol) +- [Sample Data](#sample-data) +- [Troubleshooting](#troubleshooting) + +--- + +## ✨ Features + +- **Adaptive ROI Detection**: Adjusts detection region based on vehicle speed +- **ByteTrack Multi-Object Tracking**: Prevents duplicate pothole counting +- **Real-time WebSocket Updates**: Live progress monitoring during processing +- **Frame-by-Frame Analysis**: Detailed detection logs with bounding box coordinates +- **Video Playback with Overlays**: Interactive video player with detection visualization +- **RESTful API**: Complete CRUD operations for video processing + +--- + +## 🏗️ Architecture + +``` +YOLOPOTHOLE/ +├── app/ +│ ├── core/ +│ │ └── storage.py # In-memory storage +│ ├── routes/ +│ │ └── upload_process_routes.py # API endpoints +│ ├── services/ +│ │ ├── upload_service.py # File upload handling +│ │ └── video_processor.py # Core detection logic +│ └── ws/ +│ └── websocket_manager.py # WebSocket management +├── frontend/ +│ ├── app/ +│ │ └── page.tsx # Main page +│ └── components/ +│ ├── upload-section.tsx # Upload UI +│ ├── summary-section.tsx # Results summary +│ └── video-player-section.tsx # Video player +├── models/ +│ └── pothole-detector.pt # YOLO model +├── uploads/ # Uploaded videos +├── results/ # JSON results +└── main.py # FastAPI entry +``` + +--- + +## 📦 Prerequisites + +### Backend +- Python 3.9+ +- CUDA-compatible GPU (optional, recommended) +- FFmpeg + +### Frontend +- Node.js 18+ +- npm/yarn/pnpm + +--- + +## 🚀 Installation + +### Backend Setup + +```bash +# Clone repository +git clone +cd YOLOPOTHOLE + +# Create virtual environment +python -m venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate + +# Install dependencies +pip install fastapi uvicorn python-multipart +pip install opencv-python ultralytics +pip install websockets + +# Create required directories +mkdir -p uploads results models +``` + +### Frontend Setup + +```bash +cd frontend + +# Install dependencies +npm install + +# Required packages +npm install lucide-react +npm install @radix-ui/react-progress +npm install @radix-ui/react-scroll-area +``` + +--- + +## ⚙️ Configuration + +### Backend Configuration + +**`app/core/storage.py`** +```python +from pathlib import Path + +# Storage directories +UPLOAD_DIR = Path("uploads") +RESULTS_DIR = Path("results") +MODELS_DIR = Path("models") + +# In-memory storage +processing_status = {} +detection_results = {} + +# Ensure directories exist +UPLOAD_DIR.mkdir(exist_ok=True) +RESULTS_DIR.mkdir(exist_ok=True) +MODELS_DIR.mkdir(exist_ok=True) +``` + +**`main.py`** +```python +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from app.routes.upload_process_routes import router + +app = FastAPI(title="YOLOPOTHOLE API", version="1.0.0") + +# CORS configuration +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:3000"], # Frontend URL + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include routes +app.include_router(router, prefix="/api/v1", tags=["detection"]) + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +### Frontend Configuration + +**`.env.local`** +```bash +NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1 +NEXT_PUBLIC_WS_URL=ws://localhost:8000/api/v1 +``` + +### Model Configuration + +**Adaptive Parameters** (in `video_processor.py`): +```python +# Speed < 30 km/h: ROI 50%, Confidence 0.35 +# Speed 30-60 km/h: ROI 65%, Confidence 0.28 +# Speed > 60 km/h: ROI 75%, Confidence 0.22 + +MIN_DETECTION_FRAMES = 3 # Frames needed to confirm pothole +DETECTION_TIME_WINDOW = 1.0 # Time window in seconds +``` + +--- + +## 📖 Usage + +### Starting the Backend + +```bash +# Development +python main.py + +# Production +uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 +``` + +### Starting the Frontend + +```bash +cd frontend + +# Development +npm run dev + +# Production +npm run build +npm start +``` + +### Basic Workflow + +1. **Upload Video**: Select video file and set vehicle speed +2. **Monitor Progress**: Real-time WebSocket updates +3. **View Results**: Summary statistics and video playback +4. **Analyze Detections**: Frame-by-frame detection logs + +--- + +## 🔌 API Reference + +### REST Endpoints + +#### 1. Upload Video +```http +POST /api/v1/upload +Content-Type: multipart/form-data + +Parameters: +- file: Video file (mp4, avi, mov, mkv) +- speed_kmh: Vehicle speed (integer, default: 30) + +Response: +{ + "video_id": "uuid-string", + "filename": "video.mp4", + "message": "Video uploaded successfully. Processing started.", + "status": "queued" +} +``` + +#### 2. Get Processing Status +```http +GET /api/v1/status/{video_id} + +Response: +{ + "status": "processing", + "progress": 45, + "message": "Processing frame 450/1000" +} +``` + +#### 3. Get Detection Results +```http +GET /api/v1/results/{video_id} + +Response: See "Sample Detection Results" below +``` + +#### 4. List All Videos +```http +GET /api/v1/videos + +Response: +{ + "videos": [ + { + "video_id": "uuid-string", + "status": "completed", + "progress": 100, + "summary": { ... } + } + ] +} +``` + +### Internal API Calls (Frontend) + +**Upload Request**: +```typescript +const formData = new FormData() +formData.append("file", file) +formData.append("speed_kmh", "30") + +const response = await fetch(`${API_URL}/upload`, { + method: "POST", + body: formData +}) + +const result = await response.json() +// Returns: { video_id, filename, message, status } +``` + +**Results Request**: +```typescript +const response = await fetch(`${API_URL}/results/${videoId}`) +const detectionData: DetectionData = await response.json() +``` + +--- + +## 🔄 WebSocket Protocol + +### Connection +```javascript +const ws = new WebSocket(`ws://localhost:8000/api/v1/ws/${videoId}`) +``` + +### Message Types + +#### 1. Status Update +```json +{ + "type": "status", + "status": "processing", + "progress": 0, + "message": "Loading model..." +} +``` + +#### 2. Progress Update +```json +{ + "type": "progress", + "progress": 45, + "message": "Processing frame 450/1000", + "unique_potholes": 12, + "total_detections": 87 +} +``` + +#### 3. Completion +```json +{ + "type": "complete", + "status": "completed", + "progress": 100, + "message": "Processing completed successfully", + "summary": { + "unique_potholes": 25, + "total_detections": 143, + "total_frames": 1000, + "detection_rate": 35.2 + } +} +``` + +#### 4. Error +```json +{ + "type": "error", + "status": "error", + "message": "Processing failed: Model not found" +} +``` + +#### 5. Heartbeat +```json +{ + "type": "heartbeat" +} +``` + +--- + +## 📊 Sample Data + +### Sample Detection Results + +```json +{ + "video_id": "550e8400-e29b-41d4-a716-446655440000", + "video_path": "uploads/550e8400-e29b-41d4-a716-446655440000.mp4", + "speed_kmh": 45, + "processed_at": "2025-12-18T10:30:45.123456", + "video_info": { + "total_frames": 1200, + "fps": 30.0, + "duration": 40.0, + "width": 1920, + "height": 1080, + "resolution": "1920x1080" + }, + "summary": { + "total_frames": 1200, + "unique_potholes": 18, + "total_detections": 245, + "frames_with_detections": 147, + "detection_rate": 12.25 + }, + "pothole_list": [ + { + "pothole_id": 1, + "first_detected_frame": 45, + "first_detected_time": 1.5, + "confidence": 0.876 + }, + { + "pothole_id": 2, + "first_detected_frame": 128, + "first_detected_time": 4.27, + "confidence": 0.923 + } + ], + "frames": [ + { + "frame_id": 45, + "speed_kmh": 45, + "roi_ratio": 0.65, + "potholes": [ + { + "frame_id": 45, + "pothole_id": 1, + "type": "pothole", + "confidence": 0.876, + "bbox": { + "x1": 450, + "y1": 720, + "x2": 580, + "y2": 820 + }, + "center": { + "x": 515, + "y": 770 + }, + "area": 13000 + } + ] + } + ] +} +``` + +### Sample WebSocket Messages (Sequential) + +```javascript +// 1. Initial connection +{ "type": "status", "status": "queued", "progress": 0, "message": "Video uploaded, starting processing..." } + +// 2. Model loading +{ "type": "status", "status": "processing", "progress": 0, "message": "Loading model..." } + +// 3. Processing started +{ "type": "status", "status": "processing", "progress": 5, "message": "Model loaded, processing video..." } + +// 4. Progress updates (every 5%) +{ "type": "progress", "progress": 10, "message": "Processing frame 120/1200", "unique_potholes": 3, "total_detections": 18 } +{ "type": "progress", "progress": 25, "message": "Processing frame 300/1200", "unique_potholes": 7, "total_detections": 52 } +{ "type": "progress", "progress": 50, "message": "Processing frame 600/1200", "unique_potholes": 12, "total_detections": 134 } + +// 5. Completion +{ + "type": "complete", + "status": "completed", + "progress": 100, + "message": "Processing completed successfully", + "summary": { + "unique_potholes": 18, + "total_detections": 245, + "total_frames": 1200, + "detection_rate": 12.25 + } +} +``` + +### Sample Frontend State + +```typescript +type DetectionData = { + video_id: string + video_info: { + fps: number // 30.0 + width: number // 1920 + height: number // 1080 + total_frames: number // 1200 + } + summary: { + unique_potholes: number // 18 + total_detections: number // 245 + total_frames: number // 1200 + detection_rate: number // 12.25 + } + frames: Array<{ + frame_id: number + potholes: Array<{ + pothole_id: number + bbox: { x1: number; y1: number; x2: number; y2: number } + confidence: number + }> + }> +} +``` + +--- + +## 🐛 Troubleshooting + +### Backend Issues + +**Model Not Loading** +```bash +# Check model path +ls models/pothole-detector.pt + +# Test YOLO installation +python -c "from ultralytics import YOLO; print('OK')" +``` + +**CUDA/GPU Issues** +```bash +# Check CUDA availability +python -c "import torch; print(torch.cuda.is_available())" + +# Force CPU mode in video_processor.py +self.pothole_model = YOLO("models/pothole-detector.pt", device='cpu') +``` + +**WebSocket Connection Failed** +- Ensure CORS is properly configured +- Check firewall settings for port 8000 +- Verify WebSocket URL matches backend + +### Frontend Issues + +**Video Not Playing** +```typescript +// Check browser console for errors +// Ensure video MIME type is supported +// Verify video file is accessible via ObjectURL +``` + +**Bounding Boxes Not Showing** +```typescript +// Check canvas dimensions match video +// Verify detection data structure +// Inspect frameDetectionMap in DevTools +``` + +**Progress Not Updating** +```typescript +// Check WebSocket connection status +// Verify video_id matches between upload and WS +// Look for network errors in browser DevTools +``` + +### Performance Optimization + +**Slow Processing** +- Use GPU acceleration (CUDA) +- Reduce video resolution +- Lower frame rate +- Adjust confidence thresholds + +**High Memory Usage** +```python +# Limit thread pool workers +executor = ThreadPoolExecutor(max_workers=2) + +# Reduce detection history +pothole_tracker = defaultdict(lambda: deque(maxlen=10)) +``` + +--- + +## 📝 Notes + +- **Tracking**: Requires consistent object IDs from ByteTrack +- **Frame Calculation**: Uses `Math.round(currentTime * fps)` for accuracy +- **Logging**: Limited to last 50 entries to prevent memory issues +- **Storage**: Results saved to both memory and JSON files +- **Cleanup**: Implement periodic cleanup for old videos/results + +--- + +## 🔒 Security Considerations + +- Validate file types and sizes on upload +- Sanitize video_id to prevent path traversal +- Implement rate limiting for API endpoints +- Add authentication for production deployments +- Use HTTPS/WSS in production + + + +**Built with FastAPI, YOLO, React, and shadcn/ui** \ No newline at end of file diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..dc2aea1 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,125 @@ +@import 'tailwindcss'; +@import 'tw-animate-css'; + +@custom-variant dark (&:is(.dark *)); + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.145 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.145 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.985 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.269 0 0); + --input: oklch(0.269 0 0); + --ring: oklch(0.439 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(0.269 0 0); + --sidebar-ring: oklch(0.439 0 0); +} + +@theme inline { + --font-sans: 'Geist', 'Geist Fallback'; + --font-mono: 'Geist Mono', 'Geist Mono Fallback'; + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..2299c3e --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,59 @@ +import type React from "react" +import type { Metadata } from "next" +import { Geist, Geist_Mono } from "next/font/google" +import { Analytics } from "@vercel/analytics/next" +import "./globals.css" + +const _geist = Geist({ subsets: ["latin"] }) +const _geistMono = Geist_Mono({ subsets: ["latin"] }) + +export const metadata: Metadata = { + title: "Pothole Detection System", + description: "AI-powered pothole detection and tracking system", + generator: "v0.app", + icons: { + icon: [ + { + url: "/icon-light-32x32.png", + media: "(prefers-color-scheme: light)", + }, + { + url: "/icon-dark-32x32.png", + media: "(prefers-color-scheme: dark)", + }, + { + url: "/icon.svg", + type: "image/svg+xml", + }, + ], + apple: "/apple-icon.png", + }, +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + {children} + + + + ) +} + + + + + + + + + + + + + diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..263be95 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,78 @@ +"use client" + +import { useState } from "react" +import { UploadSection } from "@/components/upload-section" +// import { SummarySection } from "@/components/summary-section" +import VideoPlayerSection from "@/components/video-player-section" + +export type DetectionData = { + video_id: string + video_info: { + fps: number + width: number + height: number + total_frames: number + } + summary: { + unique_potholes: number + total_detections: number + total_frames: number + detection_rate: number + } + frames: Array<{ + frame_id: number + potholes: Array<{ + pothole_id: number + bbox: { + x1: number + y1: number + x2: number + y2: number + } + confidence: number + }> + }> +} + +export default function PotholeDetectionPage() { + const [detectionData, setDetectionData] = useState(null) + const [videoFile, setVideoFile] = useState(null) + + return ( +
+
+ {/* Header */} +
+

+ Pothole Detection System +

+

Upload a video to detect and track potholes with AI-powered analysis

+
+ + {/* Upload Section */} +
+ { + setDetectionData(data) + setVideoFile(file) + }} + /> +
+ + {/* Summary Section */} + {/* {detectionData && ( +
+ +
+ )} */} + + {/* Video Player Section */} + {detectionData && videoFile && ( +
+ +
+ )} +
+
+ ) +} diff --git a/components/summary-section.tsx b/components/summary-section.tsx new file mode 100644 index 0000000..b34e97d --- /dev/null +++ b/components/summary-section.tsx @@ -0,0 +1,85 @@ +"use client" + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Target, AlertTriangle, Film, Activity, Gauge, Monitor } from "lucide-react" +import type { DetectionData } from "@/app/page" + +type SummarySectionProps = { + data: DetectionData +} + +export function SummarySection({ data }: SummarySectionProps) { + const stats = [ + { + label: "Unique Potholes", + value: data.summary.unique_potholes || 0, + icon: AlertTriangle, + color: "text-red-500", + bgColor: "bg-red-50 dark:bg-red-950/30", + }, + { + label: "Total Detections", + value: data.summary.total_detections || 0, + icon: Target, + color: "text-blue-500", + bgColor: "bg-blue-50 dark:bg-blue-950/30", + }, + { + label: "Total Frames", + value: data.summary.total_frames || data.video_info.total_frames, + icon: Film, + color: "text-purple-500", + bgColor: "bg-purple-50 dark:bg-purple-950/30", + }, + { + label: "Detection Rate", + value: `${(data.summary.detection_rate || 0).toFixed(1)}%`, + icon: Activity, + color: "text-green-500", + bgColor: "bg-green-50 dark:bg-green-950/30", + }, + { + label: "Video FPS", + value: (data.video_info.fps || 0).toFixed(1), + icon: Gauge, + color: "text-orange-500", + bgColor: "bg-orange-50 dark:bg-orange-950/30", + }, + { + label: "Resolution", + value: `${data.video_info.width}×${data.video_info.height}`, + icon: Monitor, + color: "text-cyan-500", + bgColor: "bg-cyan-50 dark:bg-cyan-950/30", + }, + ] + + return ( + + + Detection Summary + Overview of pothole detection results + + +
+ {stats.map((stat, index) => { + const Icon = stat.icon + return ( +
+
+ +
+
{stat.value}
+
{stat.label}
+
+ ) + })} +
+
+
+ ) +} diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx new file mode 100644 index 0000000..55c2f6e --- /dev/null +++ b/components/theme-provider.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as React from 'react' +import { + ThemeProvider as NextThemesProvider, + type ThemeProviderProps, +} from 'next-themes' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx new file mode 100644 index 0000000..e538a33 --- /dev/null +++ b/components/ui/accordion.tsx @@ -0,0 +1,66 @@ +'use client' + +import * as React from 'react' +import * as AccordionPrimitive from '@radix-ui/react-accordion' +import { ChevronDownIcon } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function Accordion({ + ...props +}: React.ComponentProps) { + return +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180', + className, + )} + {...props} + > + {children} + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..9704452 --- /dev/null +++ b/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +'use client' + +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' + +import { cn } from '@/lib/utils' +import { buttonVariants } from '@/components/ui/button' + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx new file mode 100644 index 0000000..e6751ab --- /dev/null +++ b/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', + { + variants: { + variant: { + default: 'bg-card text-card-foreground', + destructive: + 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..40bb120 --- /dev/null +++ b/components/ui/aspect-ratio.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio' + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return +} + +export { AspectRatio } diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..aa98465 --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +'use client' + +import * as React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' + +import { cn } from '@/lib/utils' + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..fc4126b --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<'span'> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span' + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..1750ff2 --- /dev/null +++ b/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { ChevronRight, MoreHorizontal } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) { + return