feat: Convert React application to HTML/CSS/JS with Alpine.js
- Migrate from React/TypeScript to vanilla HTML/CSS/JS with Alpine.js - Implement all original functionality: authentication, media queue, OAuth flow - Add auto-loading of media queue on dashboard access - Enhance JWT expiration handling with proper redirects - Improve OAuth callback page with beautiful UI and status updates - Remove unused HTMX dependency - Clean up old React project files - Update README with live demo link and development instructions Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
		
							
								
								
									
										47
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								README.md
									
									
									
									
									
								
							@@ -1 +1,46 @@
 | 
			
		||||
All AI writen
 | 
			
		||||
All AI written
 | 
			
		||||
 | 
			
		||||
# CatsOfMastodonGo Admin Dashboard v2
 | 
			
		||||
 | 
			
		||||
A modern, lightweight admin dashboard for reviewing media content, built with HTML, CSS, JavaScript, Alpine.js, and Tailwind CSS.
 | 
			
		||||
 | 
			
		||||
## Live Demo
 | 
			
		||||
 | 
			
		||||
A live demo of the project is available at: [cat.monasef.ir](https://cat.monasef.ir)
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
- Password-based authentication
 | 
			
		||||
- Gitea OAuth integration
 | 
			
		||||
- Media queue system with preloading
 | 
			
		||||
- Approval/rejection workflow for media content
 | 
			
		||||
- Responsive design for desktop and mobile
 | 
			
		||||
- Modern UI with smooth transitions
 | 
			
		||||
 | 
			
		||||
## Tech Stack
 | 
			
		||||
 | 
			
		||||
- HTML5
 | 
			
		||||
- CSS3 (with Tailwind CSS)
 | 
			
		||||
- JavaScript (with Alpine.js for state management)
 | 
			
		||||
- HTMX (for enhanced interactivity)
 | 
			
		||||
 | 
			
		||||
## Development Setup
 | 
			
		||||
 | 
			
		||||
1. Install dependencies: `pnpm install`
 | 
			
		||||
2. Start development server: `pnpm run dev`
 | 
			
		||||
3. The application will be available at: `http://localhost:8080/admin/`
 | 
			
		||||
 | 
			
		||||
## Production Build
 | 
			
		||||
 | 
			
		||||
1. Build for production: `pnpm run build`
 | 
			
		||||
2. The built files will be in the `dist` directory
 | 
			
		||||
 | 
			
		||||
## API Integration
 | 
			
		||||
 | 
			
		||||
The application integrates with the backend API at the following endpoints:
 | 
			
		||||
- `POST /admin/api/login` - Authenticate with password
 | 
			
		||||
- `GET /admin/api/login/oauth/gitea` - Initiate Gitea OAuth flow
 | 
			
		||||
- `POST /admin/api/login/oauth/gitea/final` - Complete Gitea OAuth flow
 | 
			
		||||
- `GET /admin/api/getmedia` - Get next media item to review
 | 
			
		||||
- `POST /admin/api/approve` - Approve a media item
 | 
			
		||||
- `POST /admin/api/reject` - Reject a media item
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "https://ui.shadcn.com/schema.json",
 | 
			
		||||
  "style": "default",
 | 
			
		||||
  "rsc": false,
 | 
			
		||||
  "tsx": true,
 | 
			
		||||
  "tailwind": {
 | 
			
		||||
    "config": "tailwind.config.ts",
 | 
			
		||||
    "css": "src/index.css",
 | 
			
		||||
    "baseColor": "slate",
 | 
			
		||||
    "cssVariables": true,
 | 
			
		||||
    "prefix": ""
 | 
			
		||||
  },
 | 
			
		||||
  "aliases": {
 | 
			
		||||
    "components": "@/components",
 | 
			
		||||
    "utils": "@/lib/utils",
 | 
			
		||||
    "ui": "@/components/ui",
 | 
			
		||||
    "lib": "@/lib",
 | 
			
		||||
    "hooks": "@/hooks"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								css/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								css/style.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
/* Additional custom styles if needed */
 | 
			
		||||
[x-cloak] { display: none !important; }
 | 
			
		||||
 | 
			
		||||
/* Ensure body takes full height */
 | 
			
		||||
body {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Loading spinner animation */
 | 
			
		||||
.animate-spin {
 | 
			
		||||
    animation: spin 1s linear infinite;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes spin {
 | 
			
		||||
    0% { transform: rotate(0deg); }
 | 
			
		||||
    100% { transform: rotate(360deg); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Object fit for images */
 | 
			
		||||
.object-contain {
 | 
			
		||||
    object-fit: contain;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
import js from "@eslint/js";
 | 
			
		||||
import globals from "globals";
 | 
			
		||||
import reactHooks from "eslint-plugin-react-hooks";
 | 
			
		||||
import reactRefresh from "eslint-plugin-react-refresh";
 | 
			
		||||
import tseslint from "typescript-eslint";
 | 
			
		||||
 | 
			
		||||
export default tseslint.config(
 | 
			
		||||
  { ignores: ["dist"] },
 | 
			
		||||
  {
 | 
			
		||||
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
 | 
			
		||||
    files: ["**/*.{ts,tsx}"],
 | 
			
		||||
    languageOptions: {
 | 
			
		||||
      ecmaVersion: 2020,
 | 
			
		||||
      globals: globals.browser,
 | 
			
		||||
    },
 | 
			
		||||
    plugins: {
 | 
			
		||||
      "react-hooks": reactHooks,
 | 
			
		||||
      "react-refresh": reactRefresh,
 | 
			
		||||
    },
 | 
			
		||||
    rules: {
 | 
			
		||||
      ...reactHooks.configs.recommended.rules,
 | 
			
		||||
      "react-refresh/only-export-components": [
 | 
			
		||||
        "warn",
 | 
			
		||||
        { allowConstantExport: true },
 | 
			
		||||
      ],
 | 
			
		||||
      "@typescript-eslint/no-unused-vars": "off",
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										299
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										299
									
								
								index.html
									
									
									
									
									
								
							@@ -1,24 +1,301 @@
 | 
			
		||||
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>Admin Dashboard</title>
 | 
			
		||||
    <title>Admin Dashboard v2</title>
 | 
			
		||||
    <meta name="description" content="Admin Dashboard for Media Review" />
 | 
			
		||||
    <meta name="author" content="" />
 | 
			
		||||
 | 
			
		||||
    <meta property="og:title" content="Admin Dashboard" />
 | 
			
		||||
    <meta property="og:title" content="Admin Dashboard v2" />
 | 
			
		||||
    <meta property="og:description" content="Admin Dashboard for Media Review" />
 | 
			
		||||
    <meta property="og:type" content="website" />
 | 
			
		||||
 | 
			
		||||
    <meta name="twitter:card" content="summary_large_image" />
 | 
			
		||||
  </head>
 | 
			
		||||
    
 | 
			
		||||
  <body>
 | 
			
		||||
    <div id="root"></div>
 | 
			
		||||
    <!-- IMPORTANT: DO NOT REMOVE THIS SCRIPT TAG OR THIS VERY COMMENT! -->
 | 
			
		||||
    <script src="https://cdn.gpteng.co/gptengineer.js" type="module"></script>
 | 
			
		||||
    <script type="module" src="./src/main.tsx"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
    <!-- Tailwind CSS -->
 | 
			
		||||
    <script src="https://cdn.tailwindcss.com"></script>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Alpine.js -->
 | 
			
		||||
    <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    <link rel="stylesheet" href="./css/style.css">
 | 
			
		||||
    
 | 
			
		||||
    <script>
 | 
			
		||||
        // Configure tailwind
 | 
			
		||||
        tailwind.config = {
 | 
			
		||||
            darkMode: 'class',
 | 
			
		||||
            theme: {
 | 
			
		||||
                extend: {
 | 
			
		||||
                    colors: {
 | 
			
		||||
                        border: "hsl(var(--border))",
 | 
			
		||||
                        input: "hsl(var(--input))",
 | 
			
		||||
                        ring: "hsl(var(--ring))",
 | 
			
		||||
                        background: "hsl(var(--background))",
 | 
			
		||||
                        foreground: "hsl(var(--foreground))",
 | 
			
		||||
                        primary: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--primary))",
 | 
			
		||||
                            foreground: "hsl(var(--primary-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        secondary: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--secondary))",
 | 
			
		||||
                            foreground: "hsl(var(--secondary-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        destructive: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--destructive))",
 | 
			
		||||
                            foreground: "hsl(var(--destructive-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        muted: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--muted))",
 | 
			
		||||
                            foreground: "hsl(var(--muted-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        accent: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--accent))",
 | 
			
		||||
                            foreground: "hsl(var(--accent-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        popover: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--popover))",
 | 
			
		||||
                            foreground: "hsl(var(--popover-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        card: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--card))",
 | 
			
		||||
                            foreground: "hsl(var(--card-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                    borderRadius: {
 | 
			
		||||
                        lg: "var(--radius)",
 | 
			
		||||
                        md: "calc(var(--radius) - 2px)",
 | 
			
		||||
                        sm: "calc(var(--radius) - 4px)",
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
    <style type="text/tailwindcss">
 | 
			
		||||
        @layer base {
 | 
			
		||||
            :root {
 | 
			
		||||
                --background: 0 0% 100%;
 | 
			
		||||
                --foreground: 222.2 84% 4.9%;
 | 
			
		||||
                --muted: 210 40% 96.1%;
 | 
			
		||||
                --muted-foreground: 215.4 16.3% 46.9%;
 | 
			
		||||
                --popover: 0 0% 100%;
 | 
			
		||||
                --popover-foreground: 222.2 84% 4.9%;
 | 
			
		||||
                --card: 0 0% 100%;
 | 
			
		||||
                --card-foreground: 222.2 84% 4.9%;
 | 
			
		||||
                --border: 214.3 31.8% 91.4%;
 | 
			
		||||
                --input: 214.3 31.8% 91.4%;
 | 
			
		||||
                --primary: 222.2 47.4% 11.2%;
 | 
			
		||||
                --primary-foreground: 210 40% 98%;
 | 
			
		||||
                --secondary: 210 40% 96.1%;
 | 
			
		||||
                --secondary-foreground: 222.2 47.4% 11.2%;
 | 
			
		||||
                --accent: 210 40% 96.1%;
 | 
			
		||||
                --accent-foreground: 222.2 47.4% 11.2%;
 | 
			
		||||
                --destructive: 0 84.2% 60.2%;
 | 
			
		||||
                --destructive-foreground: 210 40% 98%;
 | 
			
		||||
                --ring: 215 20.2% 65.1%;
 | 
			
		||||
                --radius: 0.5rem;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .dark {
 | 
			
		||||
                --background: 222.2 84% 4.9%;
 | 
			
		||||
                --foreground: 210 40% 98%;
 | 
			
		||||
                --muted: 217.2 32.6% 17.5%;
 | 
			
		||||
                --muted-foreground: 215 20.2% 65.1%;
 | 
			
		||||
                --popover: 222.2 84% 4.9%;
 | 
			
		||||
                --popover-foreground: 210 40% 98%;
 | 
			
		||||
                --card: 222.2 84% 4.9%;
 | 
			
		||||
                --card-foreground: 210 40% 98%;
 | 
			
		||||
                --border: 217.2 32.6% 17.5%;
 | 
			
		||||
                --input: 217.2 32.6% 17.5%;
 | 
			
		||||
                --primary: 210 40% 98%;
 | 
			
		||||
                --primary-foreground: 222.2 47.4% 11.2%;
 | 
			
		||||
                --secondary: 217.2 32.6% 17.5%;
 | 
			
		||||
                --secondary-foreground: 210 40% 98%;
 | 
			
		||||
                --accent: 217.2 32.6% 17.5%;
 | 
			
		||||
                --accent-foreground: 210 40% 98%;
 | 
			
		||||
                --destructive: 0 62.8% 30.6%;
 | 
			
		||||
                --destructive-foreground: 210 40% 98%;
 | 
			
		||||
                --ring: 212.7 26.8% 83.9%;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        @layer base {
 | 
			
		||||
            * {
 | 
			
		||||
                @apply border-border;
 | 
			
		||||
            }
 | 
			
		||||
            body {
 | 
			
		||||
                @apply bg-background text-foreground;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body class="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
 | 
			
		||||
    <div id="app" x-data="app()" x-cloak>
 | 
			
		||||
        <!-- Login View -->
 | 
			
		||||
        <template x-if="!state.token">
 | 
			
		||||
            <div class="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100">
 | 
			
		||||
                <div class="w-full max-w-md shadow-lg rounded-lg p-6 bg-white">
 | 
			
		||||
                    <div class="text-center pb-4">
 | 
			
		||||
                        <div class="mx-auto w-12 h-12 bg-slate-100 rounded-full flex items-center justify-center mb-4">
 | 
			
		||||
                            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6 text-slate-600">
 | 
			
		||||
                                <rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect>
 | 
			
		||||
                                <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
 | 
			
		||||
                            </svg>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <h3 class="text-2xl font-semibold text-slate-800">Admin Login</h3>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <form @submit.prevent="handleLogin" class="space-y-6">
 | 
			
		||||
                        <div class="space-y-2">
 | 
			
		||||
                            <input 
 | 
			
		||||
                                type="password" 
 | 
			
		||||
                                placeholder="Enter admin password" 
 | 
			
		||||
                                x-model="state.password"
 | 
			
		||||
                                class="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-slate-500 focus:border-transparent"
 | 
			
		||||
                                :disabled="state.loading"
 | 
			
		||||
                                required
 | 
			
		||||
                            />
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <button 
 | 
			
		||||
                            type="submit" 
 | 
			
		||||
                            class="w-full h-12 text-base bg-slate-900 text-white rounded-md hover:bg-slate-800 transition-colors disabled:opacity-50"
 | 
			
		||||
                            :disabled="state.loading"
 | 
			
		||||
                        >
 | 
			
		||||
                            <span x-show="!state.loading">Sign In</span>
 | 
			
		||||
                            <span x-show="state.loading" x-cloak>Signing in...</span>
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </form>
 | 
			
		||||
                    
 | 
			
		||||
                    <div class="relative my-6">
 | 
			
		||||
                        <div class="absolute inset-0 flex items-center">
 | 
			
		||||
                            <div class="w-full border-t border-gray-300"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="relative flex justify-center text-sm">
 | 
			
		||||
                            <span class="px-2 bg-white text-gray-500">Or continue with</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <button
 | 
			
		||||
                        @click="handleGiteaLogin"
 | 
			
		||||
                        type="button"
 | 
			
		||||
                        class="w-full h-12 text-base border border-gray-300 rounded-md hover:bg-gray-50 transition-colors flex items-center justify-center gap-2"
 | 
			
		||||
                    >
 | 
			
		||||
                        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5">
 | 
			
		||||
                            <path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/>
 | 
			
		||||
                            <path d="M9 18c-4.51 2-5-2-7-2"/>
 | 
			
		||||
                        </svg>
 | 
			
		||||
                        Login with Gitea
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </template>
 | 
			
		||||
 | 
			
		||||
        <!-- Dashboard View -->
 | 
			
		||||
        <template x-if="state.token">
 | 
			
		||||
            <div class="min-h-screen">
 | 
			
		||||
                <!-- Header -->
 | 
			
		||||
                <div class="bg-white shadow-sm border-b">
 | 
			
		||||
                    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
 | 
			
		||||
                        <div class="flex justify-between items-center h-16">
 | 
			
		||||
                            <h1 class="text-xl font-semibold text-slate-800">Admin Dashboard</h1>
 | 
			
		||||
                            <button
 | 
			
		||||
                                @click="handleLogout"
 | 
			
		||||
                                class="flex items-center gap-2 border border-gray-300 rounded-md px-4 py-2 hover:bg-gray-50 transition-colors"
 | 
			
		||||
                            >
 | 
			
		||||
                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4">
 | 
			
		||||
                                    <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
 | 
			
		||||
                                    <polyline points="16 17 21 12 16 7"></polyline>
 | 
			
		||||
                                    <line x1="21" x2="9" y1="12" y2="12"></line>
 | 
			
		||||
                                </svg>
 | 
			
		||||
                                Logout
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Main Content -->
 | 
			
		||||
                <div class="flex items-center justify-center min-h-[calc(100vh-4rem)] p-8">
 | 
			
		||||
                    <div class="max-w-2xl w-full shadow-lg rounded-lg p-8 bg-white">
 | 
			
		||||
                        <template x-if="state.loading && state.mediaQueue.length === 0">
 | 
			
		||||
                            <div class="flex flex-col items-center justify-center py-16">
 | 
			
		||||
                                <div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-slate-600 mb-4"></div>
 | 
			
		||||
                                <p class="text-slate-600">Loading media...</p>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <template x-if="!state.loading && state.currentMedia">
 | 
			
		||||
                            <div class="space-y-6">
 | 
			
		||||
                                <!-- Image -->
 | 
			
		||||
                                <div class="flex justify-center">
 | 
			
		||||
                                    <img
 | 
			
		||||
                                        :src="state.currentMedia.preview_url"
 | 
			
		||||
                                        alt="Media to review"
 | 
			
		||||
                                        class="max-w-full max-h-96 object-contain"
 | 
			
		||||
                                        @error="handleImageError"
 | 
			
		||||
                                        x-cloak
 | 
			
		||||
                                    />
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <!-- Media Info -->
 | 
			
		||||
                                <div class="text-center text-sm text-slate-600">
 | 
			
		||||
                                    <p>Media ID: <span x-text="state.currentMedia.id"></span></p>
 | 
			
		||||
                                    <p>Post ID: <span x-text="state.currentMedia.PostID"></span></p>
 | 
			
		||||
                                    <p class="text-xs text-slate-400 mt-1" x-show="state.mediaQueue.length > 1">
 | 
			
		||||
                                        <span x-text="state.mediaQueue.length - 1"></span> more item(s) pre-loaded
 | 
			
		||||
                                    </p>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <!-- Status Message -->
 | 
			
		||||
                                <div class="text-center" x-show="state.statusMessage" x-cloak>
 | 
			
		||||
                                    <p class="text-lg font-medium text-slate-700" x-text="state.statusMessage"></p>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <!-- Action Buttons -->
 | 
			
		||||
                                <div class="flex gap-4 justify-center">
 | 
			
		||||
                                    <button
 | 
			
		||||
                                        @click="handleAction('reject')"
 | 
			
		||||
                                        :disabled="state.isProcessing"
 | 
			
		||||
                                        class="flex items-center gap-2 px-8 py-3 border border-transparent rounded-md text-white bg-red-600 hover:bg-red-700 disabled:opacity-50 transition-colors"
 | 
			
		||||
                                    >
 | 
			
		||||
                                        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5">
 | 
			
		||||
                                            <path d="M18 6 6 18"></path>
 | 
			
		||||
                                            <path d="m6 6 12 12"></path>
 | 
			
		||||
                                        </svg>
 | 
			
		||||
                                        Reject
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                    <button
 | 
			
		||||
                                        @click="handleAction('approve')"
 | 
			
		||||
                                        :disabled="state.isProcessing"
 | 
			
		||||
                                        class="flex items-center gap-2 px-8 py-3 border border-transparent rounded-md text-white bg-green-600 hover:bg-green-700 disabled:opacity-50 transition-colors"
 | 
			
		||||
                                    >
 | 
			
		||||
                                        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5">
 | 
			
		||||
                                            <polyline points="20 6 9 17 4 12"></polyline>
 | 
			
		||||
                                        </svg>
 | 
			
		||||
                                        Approve
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <template x-if="!state.currentMedia && !state.loading">
 | 
			
		||||
                            <div class="text-center py-16">
 | 
			
		||||
                                <p class="text-lg text-slate-600">No media available for review</p>
 | 
			
		||||
                                <button
 | 
			
		||||
                                    @click="fillMediaQueue"
 | 
			
		||||
                                    class="mt-4 px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
 | 
			
		||||
                                >
 | 
			
		||||
                                    Refresh
 | 
			
		||||
                                </button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </template>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Scripts -->
 | 
			
		||||
    <script src="./js/app.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										282
									
								
								js/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								js/app.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,282 @@
 | 
			
		||||
// Main application state and logic
 | 
			
		||||
function app() {
 | 
			
		||||
    return {
 | 
			
		||||
        state: {
 | 
			
		||||
            token: null,
 | 
			
		||||
            password: '',
 | 
			
		||||
            loading: false,
 | 
			
		||||
            isProcessing: false,
 | 
			
		||||
            statusMessage: '',
 | 
			
		||||
            mediaQueue: [],
 | 
			
		||||
            currentMedia: null
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        init() {
 | 
			
		||||
            // Check for existing token on component mount
 | 
			
		||||
            this.checkExistingToken();
 | 
			
		||||
            
 | 
			
		||||
            // If we have a token, start filling the media queue
 | 
			
		||||
            if (this.state.token) {
 | 
			
		||||
                // Use setTimeout to ensure Alpine is fully initialized
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                    this.fillMediaQueue();
 | 
			
		||||
                }, 0);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        checkExistingToken() {
 | 
			
		||||
            const savedToken = localStorage.getItem('adminToken');
 | 
			
		||||
            if (savedToken) {
 | 
			
		||||
                try {
 | 
			
		||||
                    // Parse JWT payload to check for expiration
 | 
			
		||||
                    const payload = JSON.parse(atob(savedToken.split('.')[1]));
 | 
			
		||||
                    const currentTime = Math.floor(Date.now() / 1000);
 | 
			
		||||
                    
 | 
			
		||||
                    if (payload.exp && payload.exp > currentTime) {
 | 
			
		||||
                        this.state.token = savedToken;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Token expired, remove it
 | 
			
		||||
                        localStorage.removeItem('adminToken');
 | 
			
		||||
                    }
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                    // Invalid token format, remove it
 | 
			
		||||
                    localStorage.removeItem('adminToken');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        async handleLogin() {
 | 
			
		||||
            if (!this.state.password.trim()) {
 | 
			
		||||
                this.showToast("Error", "Please enter a password", "error");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.state.loading = true;
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                const response = await fetch('/admin/api/login', {
 | 
			
		||||
                    method: 'POST',
 | 
			
		||||
                    headers: {
 | 
			
		||||
                        'Content-Type': 'application/json',
 | 
			
		||||
                    },
 | 
			
		||||
                    body: JSON.stringify({ Password: this.state.password })
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                const data = await response.json();
 | 
			
		||||
 | 
			
		||||
                if (response.ok && data.token) {
 | 
			
		||||
                    this.state.token = data.token;
 | 
			
		||||
                    localStorage.setItem('adminToken', data.token);
 | 
			
		||||
                    this.showToast("Success", data.message || "Login successful");
 | 
			
		||||
                    this.state.password = '';
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.showToast("Error", data.message || "Login failed", "error");
 | 
			
		||||
                }
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                this.showToast("Error", "Network error. Please try again.", "error");
 | 
			
		||||
            } finally {
 | 
			
		||||
                this.state.loading = false;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        handleGiteaLogin() {
 | 
			
		||||
            // Redirect to the Gitea OAuth endpoint
 | 
			
		||||
            window.location.href = '/admin/api/login/oauth/gitea';
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        handleLogout() {
 | 
			
		||||
            this.state.token = null;
 | 
			
		||||
            localStorage.removeItem('adminToken');
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        async fetchMedia() {
 | 
			
		||||
            try {
 | 
			
		||||
                const response = await fetch('/admin/api/getmedia', {
 | 
			
		||||
                    headers: {
 | 
			
		||||
                        'Authorization': `Bearer ${this.state.token}`,
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                if (response.status === 401) {
 | 
			
		||||
                    this.showToast("Session Expired", "Please login again", "error");
 | 
			
		||||
                    this.handleLogout();
 | 
			
		||||
                    // Redirect to login page after logout
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                        // Trigger a page reload to update the UI based on state
 | 
			
		||||
                        location.reload();
 | 
			
		||||
                    }, 1500);
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (response.ok) {
 | 
			
		||||
                    return await response.json();
 | 
			
		||||
                } else {
 | 
			
		||||
                    const errorData = await response.json();
 | 
			
		||||
                    this.showToast("Error", errorData.message || "Failed to fetch media", "error");
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                this.showToast("Error", "Network error. Please try again.", "error");
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        async fetchNextMedia() {
 | 
			
		||||
            const media = await this.fetchMedia();
 | 
			
		||||
            if (media) {
 | 
			
		||||
                this.state.mediaQueue.push(media);
 | 
			
		||||
                
 | 
			
		||||
                // Update current media if it's the first item
 | 
			
		||||
                if (this.state.mediaQueue.length === 1) {
 | 
			
		||||
                    this.state.currentMedia = media;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // Preload next items (up to 5 in queue total)
 | 
			
		||||
                if (this.state.mediaQueue.length < 5) {
 | 
			
		||||
                    this.preloadMedia();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        async fillMediaQueue() {
 | 
			
		||||
            // Don't fetch if already loading or queue is full
 | 
			
		||||
            if (this.state.loading || this.state.mediaQueue.length >= 5) return;
 | 
			
		||||
 | 
			
		||||
            // Determine how many more items we need
 | 
			
		||||
            const itemsNeeded = 5 - this.state.mediaQueue.length;
 | 
			
		||||
 | 
			
		||||
            // Fetch items one by one
 | 
			
		||||
            for (let i = 0; i < itemsNeeded; i++) {
 | 
			
		||||
                await this.fetchNextMedia();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        preloadMedia() {
 | 
			
		||||
            // Preload all items after the first one in the queue
 | 
			
		||||
            this.state.mediaQueue.slice(1, 5).forEach(media => {
 | 
			
		||||
                if (!media.preview_url) return;
 | 
			
		||||
 | 
			
		||||
                const img = new Image();
 | 
			
		||||
                img.onload = () => {
 | 
			
		||||
                    // Successful preload - nothing to do
 | 
			
		||||
                };
 | 
			
		||||
                img.onerror = () => {
 | 
			
		||||
                    // This media is broken even before we show it: remove it now
 | 
			
		||||
                    this.state.mediaQueue = this.state.mediaQueue.filter(item => item.id !== media.id);
 | 
			
		||||
                    // Update current media if needed
 | 
			
		||||
                    if (this.state.currentMedia && this.state.currentMedia.id === media.id) {
 | 
			
		||||
                        this.state.currentMedia = this.state.mediaQueue.length > 0 ? this.state.mediaQueue[0] : null;
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                img.src = media.preview_url;
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        async handleAction(action) {
 | 
			
		||||
            if (!this.state.currentMedia) return;
 | 
			
		||||
 | 
			
		||||
            this.state.isProcessing = true;
 | 
			
		||||
            this.state.statusMessage = `${action === 'approve' ? 'Approving' : 'Rejecting'}...`;
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                const response = await fetch(`/admin/api/${action}`, {
 | 
			
		||||
                    method: 'POST',
 | 
			
		||||
                    headers: {
 | 
			
		||||
                        'Content-Type': 'application/json',
 | 
			
		||||
                        'Authorization': `Bearer ${this.state.token}`,
 | 
			
		||||
                    },
 | 
			
		||||
                    body: JSON.stringify({ mediaId: this.state.currentMedia.id }),
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                if (response.status === 401) {
 | 
			
		||||
                    this.showToast("Session Expired", "Please login again", "error");
 | 
			
		||||
                    this.handleLogout();
 | 
			
		||||
                    // Redirect to login page after logout
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                        // Trigger a page reload to update the UI based on state
 | 
			
		||||
                        location.reload();
 | 
			
		||||
                    }, 1500);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const data = await response.json();
 | 
			
		||||
 | 
			
		||||
                if (response.ok) {
 | 
			
		||||
                    this.state.statusMessage = `${action === 'approve' ? 'Approved' : 'Rejected'} successfully!`;
 | 
			
		||||
 | 
			
		||||
                    // Show success message briefly, then move to the next item
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                        // Remove the current item from the queue
 | 
			
		||||
                        this.state.mediaQueue.shift();
 | 
			
		||||
                        
 | 
			
		||||
                        // Update current media
 | 
			
		||||
                        if (this.state.mediaQueue.length > 0) {
 | 
			
		||||
                            this.state.currentMedia = this.state.mediaQueue[0];
 | 
			
		||||
                            // Fetch next item to keep queue full
 | 
			
		||||
                            this.fetchNextMedia();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            this.state.currentMedia = null;
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        this.state.statusMessage = '';
 | 
			
		||||
                        this.state.isProcessing = false;
 | 
			
		||||
                    }, 1000);
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.showToast("Error", data.message || `Failed to ${action} media`, "error");
 | 
			
		||||
                    this.state.isProcessing = false;
 | 
			
		||||
                    this.state.statusMessage = '';
 | 
			
		||||
                }
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                this.showToast("Error", "Network error. Please try again.", "error");
 | 
			
		||||
                this.state.isProcessing = false;
 | 
			
		||||
                this.state.statusMessage = '';
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        handleImageError(e) {
 | 
			
		||||
            const imgEl = e.target;
 | 
			
		||||
            if (!imgEl.dataset.fallbackTried) {
 | 
			
		||||
                imgEl.dataset.fallbackTried = 'true';
 | 
			
		||||
                imgEl.src = this.state.currentMedia.remote_url;
 | 
			
		||||
            } else {
 | 
			
		||||
                // Both preview AND remote have failed - drop it
 | 
			
		||||
                this.state.mediaQueue = this.state.mediaQueue.filter(
 | 
			
		||||
                    item => item.id !== this.state.currentMedia.id
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                // Update current media
 | 
			
		||||
                if (this.state.mediaQueue.length > 0) {
 | 
			
		||||
                    this.state.currentMedia = this.state.mediaQueue[0];
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.state.currentMedia = null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        showToast(title, message, variant = 'default') {
 | 
			
		||||
            // Simple toast notification
 | 
			
		||||
            const toast = document.createElement('div');
 | 
			
		||||
            toast.className = `fixed top-4 right-4 p-4 rounded-md shadow-lg z-50 max-w-md ${
 | 
			
		||||
                variant === 'error' ? 'bg-red-100 border border-red-300 text-red-700' : 'bg-green-100 border border-green-300 text-green-700'
 | 
			
		||||
            }`;
 | 
			
		||||
            toast.innerHTML = `
 | 
			
		||||
                <div class="flex justify-between items-start">
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <div class="font-bold">${title}</div>
 | 
			
		||||
                        <div>${message}</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <button class="ml-4 text-gray-600 hover:text-gray-900" onclick="this.parentElement.parentElement.remove()">×</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            `;
 | 
			
		||||
            
 | 
			
		||||
            document.body.appendChild(toast);
 | 
			
		||||
            
 | 
			
		||||
            // Auto remove after 5 seconds
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                if (toast.parentElement) {
 | 
			
		||||
                    toast.remove();
 | 
			
		||||
                }
 | 
			
		||||
            }, 5000);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										297
									
								
								oauth-callback.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										297
									
								
								oauth-callback.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,297 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>Completing Authentication</title>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Tailwind CSS -->
 | 
			
		||||
    <script src="https://cdn.tailwindcss.com"></script>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Alpine.js -->
 | 
			
		||||
    <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    <script>
 | 
			
		||||
        // Configure tailwind
 | 
			
		||||
        tailwind.config = {
 | 
			
		||||
            darkMode: 'class',
 | 
			
		||||
            theme: {
 | 
			
		||||
                extend: {
 | 
			
		||||
                    colors: {
 | 
			
		||||
                        border: "hsl(var(--border))",
 | 
			
		||||
                        input: "hsl(var(--input))",
 | 
			
		||||
                        ring: "hsl(var(--ring))",
 | 
			
		||||
                        background: "hsl(var(--background))",
 | 
			
		||||
                        foreground: "hsl(var(--foreground))",
 | 
			
		||||
                        primary: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--primary))",
 | 
			
		||||
                            foreground: "hsl(var(--primary-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        secondary: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--secondary))",
 | 
			
		||||
                            foreground: "hsl(var(--secondary-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        destructive: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--destructive))",
 | 
			
		||||
                            foreground: "hsl(var(--destructive-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        muted: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--muted))",
 | 
			
		||||
                            foreground: "hsl(var(--muted-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        accent: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--accent))",
 | 
			
		||||
                            foreground: "hsl(var(--accent-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        popover: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--popover))",
 | 
			
		||||
                            foreground: "hsl(var(--popover-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                        card: {
 | 
			
		||||
                            DEFAULT: "hsl(var(--card))",
 | 
			
		||||
                            foreground: "hsl(var(--card-foreground))",
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                    borderRadius: {
 | 
			
		||||
                        lg: "var(--radius)",
 | 
			
		||||
                        md: "calc(var(--radius) - 2px)",
 | 
			
		||||
                        sm: "calc(var(--radius) - 4px)",
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
    <style type="text/tailwindcss">
 | 
			
		||||
        @layer base {
 | 
			
		||||
            :root {
 | 
			
		||||
                --background: 0 0% 100%;
 | 
			
		||||
                --foreground: 222.2 84% 4.9%;
 | 
			
		||||
                --muted: 210 40% 96.1%;
 | 
			
		||||
                --muted-foreground: 215.4 16.3% 46.9%;
 | 
			
		||||
                --popover: 0 0% 100%;
 | 
			
		||||
                --popover-foreground: 222.2 84% 4.9%;
 | 
			
		||||
                --card: 0 0% 100%;
 | 
			
		||||
                --card-foreground: 222.2 84% 4.9%;
 | 
			
		||||
                --border: 214.3 31.8% 91.4%;
 | 
			
		||||
                --input: 214.3 31.8% 91.4%;
 | 
			
		||||
                --primary: 222.2 47.4% 11.2%;
 | 
			
		||||
                --primary-foreground: 210 40% 98%;
 | 
			
		||||
                --secondary: 210 40% 96.1%;
 | 
			
		||||
                --secondary-foreground: 222.2 47.4% 11.2%;
 | 
			
		||||
                --accent: 210 40% 96.1%;
 | 
			
		||||
                --accent-foreground: 222.2 47.4% 11.2%;
 | 
			
		||||
                --destructive: 0 84.2% 60.2%;
 | 
			
		||||
                --destructive-foreground: 210 40% 98%;
 | 
			
		||||
                --ring: 215 20.2% 65.1%;
 | 
			
		||||
                --radius: 0.5rem;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .dark {
 | 
			
		||||
                --background: 222.2 84% 4.9%;
 | 
			
		||||
                --foreground: 210 40% 98%;
 | 
			
		||||
                --muted: 217.2 32.6% 17.5%;
 | 
			
		||||
                --muted-foreground: 215 20.2% 65.1%;
 | 
			
		||||
                --popover: 222.2 84% 4.9%;
 | 
			
		||||
                --popover-foreground: 210 40% 98%;
 | 
			
		||||
                --card: 222.2 84% 4.9%;
 | 
			
		||||
                --card-foreground: 210 40% 98%;
 | 
			
		||||
                --border: 217.2 32.6% 17.5%;
 | 
			
		||||
                --input: 217.2 32.6% 17.5%;
 | 
			
		||||
                --primary: 210 40% 98%;
 | 
			
		||||
                --primary-foreground: 222.2 47.4% 11.2%;
 | 
			
		||||
                --secondary: 217.2 32.6% 17.5%;
 | 
			
		||||
                --secondary-foreground: 210 40% 98%;
 | 
			
		||||
                --accent: 217.2 32.6% 17.5%;
 | 
			
		||||
                --accent-foreground: 210 40% 98%;
 | 
			
		||||
                --destructive: 0 62.8% 30.6%;
 | 
			
		||||
                --destructive-foreground: 210 40% 98%;
 | 
			
		||||
                --ring: 212.7 26.8% 83.9%;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        @layer base {
 | 
			
		||||
            * {
 | 
			
		||||
                @apply border-border;
 | 
			
		||||
            }
 | 
			
		||||
            body {
 | 
			
		||||
                @apply bg-background text-foreground;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        [x-cloak] { display: none !important; }
 | 
			
		||||
        
 | 
			
		||||
        .animate-spin {
 | 
			
		||||
            animation: spin 1s linear infinite;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        @keyframes spin {
 | 
			
		||||
            0% { transform: rotate(0deg); }
 | 
			
		||||
            100% { transform: rotate(360deg); }
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body class="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
 | 
			
		||||
    <div x-data="oauthCallback()" x-init="handleOAuthCallback">
 | 
			
		||||
        <div class="min-h-screen flex items-center justify-center p-4">
 | 
			
		||||
            <div class="max-w-md w-full">
 | 
			
		||||
                <div class="bg-white rounded-2xl shadow-xl overflow-hidden">
 | 
			
		||||
                    <div class="p-8 text-center">
 | 
			
		||||
                        <div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-blue-100 mb-6">
 | 
			
		||||
                            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-8 w-8 text-blue-600">
 | 
			
		||||
                                <path d="M18 6 6 18"></path>
 | 
			
		||||
                                <path d="m6 6 12 12"></path>
 | 
			
		||||
                            </svg>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
                        <h2 class="text-2xl font-bold text-gray-900 mb-2">Completing Authentication</h2>
 | 
			
		||||
                        <p class="text-gray-600 mb-6">Please wait while we complete your Gitea login...</p>
 | 
			
		||||
                        
 | 
			
		||||
                        <div class="flex justify-center mb-4">
 | 
			
		||||
                            <div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
                        <p class="text-sm text-gray-500">This should only take a few seconds</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <script>
 | 
			
		||||
        function oauthCallback() {
 | 
			
		||||
            return {
 | 
			
		||||
                async handleOAuthCallback() {
 | 
			
		||||
                    // Get the code from the URL parameters
 | 
			
		||||
                    const urlParams = new URLSearchParams(window.location.search);
 | 
			
		||||
                    const code = urlParams.get('code');
 | 
			
		||||
                    
 | 
			
		||||
                    if (!code) {
 | 
			
		||||
                        this.showToast("Error", "No authorization code received from Gitea", "error");
 | 
			
		||||
                        setTimeout(() => {
 | 
			
		||||
                            window.location.href = '/admin';
 | 
			
		||||
                        }, 2000);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Show processing status
 | 
			
		||||
                    document.querySelector('h2').textContent = "Verifying Credentials";
 | 
			
		||||
                    document.querySelector('p').textContent = "Checking your Gitea account information...";
 | 
			
		||||
 | 
			
		||||
                    try {
 | 
			
		||||
                        // Send the code to our backend to exchange for a JWT token
 | 
			
		||||
                        const response = await fetch('/admin/api/login/oauth/gitea/final', {
 | 
			
		||||
                            method: 'POST',
 | 
			
		||||
                            headers: {
 | 
			
		||||
                                'Content-Type': 'application/json',
 | 
			
		||||
                            },
 | 
			
		||||
                            body: JSON.stringify({ code: code }),
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        const data = await response.json();
 | 
			
		||||
 | 
			
		||||
                        if (response.ok && data.token) {
 | 
			
		||||
                            // Store the token in localStorage
 | 
			
		||||
                            localStorage.setItem('adminToken', data.token);
 | 
			
		||||
                            
 | 
			
		||||
                            // Show success status
 | 
			
		||||
                            document.querySelector('h2').textContent = "Success!";
 | 
			
		||||
                            document.querySelector('p').textContent = "You're being redirected to the dashboard...";
 | 
			
		||||
                            
 | 
			
		||||
                            const spinner = document.querySelector('.animate-spin');
 | 
			
		||||
                            spinner.outerHTML = `
 | 
			
		||||
                                <div class="mx-auto mb-4">
 | 
			
		||||
                                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-12 w-12 text-green-500 mx-auto">
 | 
			
		||||
                                        <path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path>
 | 
			
		||||
                                        <path d="m9 12 2 2 4-4"></path>
 | 
			
		||||
                                    </svg>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            `;
 | 
			
		||||
                            
 | 
			
		||||
                            this.showToast("Success", data.message || "Login successful");
 | 
			
		||||
                            
 | 
			
		||||
                            // Redirect to the admin dashboard after a short delay
 | 
			
		||||
                            setTimeout(() => {
 | 
			
		||||
                                window.location.href = '/admin';
 | 
			
		||||
                            }, 1500);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            // Show error status
 | 
			
		||||
                            document.querySelector('h2').textContent = "Authentication Failed";
 | 
			
		||||
                            document.querySelector('p').textContent = "There was an issue with your login. Please try again.";
 | 
			
		||||
                            
 | 
			
		||||
                            const spinner = document.querySelector('.animate-spin');
 | 
			
		||||
                            spinner.outerHTML = `
 | 
			
		||||
                                <div class="mx-auto mb-4">
 | 
			
		||||
                                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-12 w-12 text-red-500 mx-auto">
 | 
			
		||||
                                        <circle cx="12" cy="12" r="10"></circle>
 | 
			
		||||
                                        <path d="M15 9l-6 6"></path>
 | 
			
		||||
                                        <path d="M9 9l6 6"></path>
 | 
			
		||||
                                    </svg>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            `;
 | 
			
		||||
                            
 | 
			
		||||
                            this.showToast("Error", data.error || "Login failed", "error");
 | 
			
		||||
                            setTimeout(() => {
 | 
			
		||||
                                window.location.href = '/admin';
 | 
			
		||||
                            }, 3000);
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch (error) {
 | 
			
		||||
                        // Show error status
 | 
			
		||||
                        document.querySelector('h2').textContent = "Network Error";
 | 
			
		||||
                        document.querySelector('p').textContent = "Unable to connect to authentication server. Please try again.";
 | 
			
		||||
                        
 | 
			
		||||
                        const spinner = document.querySelector('.animate-spin');
 | 
			
		||||
                        spinner.outerHTML = `
 | 
			
		||||
                            <div class="mx-auto mb-4">
 | 
			
		||||
                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-12 w-12 text-red-500 mx-auto">
 | 
			
		||||
                                    <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10"></path>
 | 
			
		||||
                                    <path d="M9.84 9.78c-.97.42-1.81 1.2-2.34 2.23"></path>
 | 
			
		||||
                                    <path d="M14.16 9.78c.97.42 1.81 1.2 2.34 2.23"></path>
 | 
			
		||||
                                    <path d="M12 16c-.73 0-1.4-.15-2-.43"></path>
 | 
			
		||||
                                    <path d="M12 16c.73 0 1.4-.15 2-.43"></path>
 | 
			
		||||
                                </svg>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        `;
 | 
			
		||||
                        
 | 
			
		||||
                        this.showToast("Error", "Network error. Please try again.", "error");
 | 
			
		||||
                        setTimeout(() => {
 | 
			
		||||
                            window.location.href = '/admin';
 | 
			
		||||
                        }, 3000);
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                
 | 
			
		||||
                showToast(title, message, variant = 'default') {
 | 
			
		||||
                    // Remove any existing toasts
 | 
			
		||||
                    const existingToasts = document.querySelectorAll('.fixed.top-4');
 | 
			
		||||
                    existingToasts.forEach(toast => toast.remove());
 | 
			
		||||
                    
 | 
			
		||||
                    // Simple toast notification
 | 
			
		||||
                    const toast = document.createElement('div');
 | 
			
		||||
                    toast.className = `fixed top-4 right-4 p-4 rounded-md shadow-lg z-50 max-w-md ${
 | 
			
		||||
                        variant === 'error' ? 'bg-red-100 border border-red-300 text-red-700' : 'bg-green-100 border border-green-300 text-green-700'
 | 
			
		||||
                    }`;
 | 
			
		||||
                    toast.innerHTML = `
 | 
			
		||||
                        <div class="flex justify-between items-start">
 | 
			
		||||
                            <div>
 | 
			
		||||
                                <div class="font-bold">${title}</div>
 | 
			
		||||
                                <div>${message}</div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <button class="ml-4 text-gray-600 hover:text-gray-900" onclick="this.parentElement.parentElement.remove()">×</button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    `;
 | 
			
		||||
                    
 | 
			
		||||
                    document.body.appendChild(toast);
 | 
			
		||||
                    
 | 
			
		||||
                    // Auto remove after 5 seconds
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                        if (toast.parentElement) {
 | 
			
		||||
                            toast.remove();
 | 
			
		||||
                        }
 | 
			
		||||
                    }, 5000);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										74
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								package.json
									
									
									
									
									
								
							@@ -1,82 +1,18 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "vite_react_shadcn_ts",
 | 
			
		||||
  "name": "catsofmastodon-go-admin-v2",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "version": "0.0.0",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vite",
 | 
			
		||||
    "build": "vite build",
 | 
			
		||||
    "build:dev": "vite build --mode development",
 | 
			
		||||
    "lint": "eslint .",
 | 
			
		||||
    "preview": "vite preview"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@hookform/resolvers": "^3.9.0",
 | 
			
		||||
    "@radix-ui/react-accordion": "^1.2.0",
 | 
			
		||||
    "@radix-ui/react-alert-dialog": "^1.1.1",
 | 
			
		||||
    "@radix-ui/react-aspect-ratio": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-avatar": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-checkbox": "^1.1.1",
 | 
			
		||||
    "@radix-ui/react-collapsible": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-context-menu": "^2.2.1",
 | 
			
		||||
    "@radix-ui/react-dialog": "^1.1.2",
 | 
			
		||||
    "@radix-ui/react-dropdown-menu": "^2.1.1",
 | 
			
		||||
    "@radix-ui/react-hover-card": "^1.1.1",
 | 
			
		||||
    "@radix-ui/react-label": "^2.1.0",
 | 
			
		||||
    "@radix-ui/react-menubar": "^1.1.1",
 | 
			
		||||
    "@radix-ui/react-navigation-menu": "^1.2.0",
 | 
			
		||||
    "@radix-ui/react-popover": "^1.1.1",
 | 
			
		||||
    "@radix-ui/react-progress": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-radio-group": "^1.2.0",
 | 
			
		||||
    "@radix-ui/react-scroll-area": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-select": "^2.1.1",
 | 
			
		||||
    "@radix-ui/react-separator": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-slider": "^1.2.0",
 | 
			
		||||
    "@radix-ui/react-slot": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-switch": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-tabs": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-toast": "^1.2.1",
 | 
			
		||||
    "@radix-ui/react-toggle": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-toggle-group": "^1.1.0",
 | 
			
		||||
    "@radix-ui/react-tooltip": "^1.1.4",
 | 
			
		||||
    "@tanstack/react-query": "^5.56.2",
 | 
			
		||||
    "class-variance-authority": "^0.7.1",
 | 
			
		||||
    "clsx": "^2.1.1",
 | 
			
		||||
    "cmdk": "^1.0.0",
 | 
			
		||||
    "date-fns": "^3.6.0",
 | 
			
		||||
    "embla-carousel-react": "^8.3.0",
 | 
			
		||||
    "input-otp": "^1.2.4",
 | 
			
		||||
    "lucide-react": "^0.462.0",
 | 
			
		||||
    "next-themes": "^0.3.0",
 | 
			
		||||
    "react": "^18.3.1",
 | 
			
		||||
    "react-day-picker": "^8.10.1",
 | 
			
		||||
    "react-dom": "^18.3.1",
 | 
			
		||||
    "react-hook-form": "^7.53.0",
 | 
			
		||||
    "react-resizable-panels": "^2.1.3",
 | 
			
		||||
    "react-router-dom": "^6.26.2",
 | 
			
		||||
    "recharts": "^2.12.7",
 | 
			
		||||
    "sonner": "^1.5.0",
 | 
			
		||||
    "tailwind-merge": "^2.5.2",
 | 
			
		||||
    "tailwindcss-animate": "^1.0.7",
 | 
			
		||||
    "vaul": "^0.9.3",
 | 
			
		||||
    "zod": "^3.23.8"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@eslint/js": "^9.9.0",
 | 
			
		||||
    "@tailwindcss/typography": "^0.5.15",
 | 
			
		||||
    "@types/node": "^22.5.5",
 | 
			
		||||
    "@types/react": "^18.3.3",
 | 
			
		||||
    "@types/react-dom": "^18.3.0",
 | 
			
		||||
    "@vitejs/plugin-react-swc": "^3.5.0",
 | 
			
		||||
    "autoprefixer": "^10.4.20",
 | 
			
		||||
    "eslint": "^9.9.0",
 | 
			
		||||
    "eslint-plugin-react-hooks": "^5.1.0-rc.0",
 | 
			
		||||
    "eslint-plugin-react-refresh": "^0.4.9",
 | 
			
		||||
    "globals": "^15.9.0",
 | 
			
		||||
    "postcss": "^8.4.47",
 | 
			
		||||
    "tailwindcss": "^3.4.11",
 | 
			
		||||
    "typescript": "^5.5.3",
 | 
			
		||||
    "typescript-eslint": "^8.0.1",
 | 
			
		||||
    "vite": "^5.4.1"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "alpinejs": "^3.14.1",
 | 
			
		||||
    "tailwindcss": "^3.4.11"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4550
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4550
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,6 +0,0 @@
 | 
			
		||||
export default {
 | 
			
		||||
  plugins: {
 | 
			
		||||
    tailwindcss: {},
 | 
			
		||||
    autoprefixer: {},
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								src/App.css
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								src/App.css
									
									
									
									
									
								
							@@ -1,42 +0,0 @@
 | 
			
		||||
#root {
 | 
			
		||||
  max-width: 1280px;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  padding: 2rem;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.logo {
 | 
			
		||||
  height: 6em;
 | 
			
		||||
  padding: 1.5em;
 | 
			
		||||
  will-change: filter;
 | 
			
		||||
  transition: filter 300ms;
 | 
			
		||||
}
 | 
			
		||||
.logo:hover {
 | 
			
		||||
  filter: drop-shadow(0 0 2em #646cffaa);
 | 
			
		||||
}
 | 
			
		||||
.logo.react:hover {
 | 
			
		||||
  filter: drop-shadow(0 0 2em #61dafbaa);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes logo-spin {
 | 
			
		||||
  from {
 | 
			
		||||
    transform: rotate(0deg);
 | 
			
		||||
  }
 | 
			
		||||
  to {
 | 
			
		||||
    transform: rotate(360deg);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (prefers-reduced-motion: no-preference) {
 | 
			
		||||
  a:nth-of-type(2) .logo {
 | 
			
		||||
    animation: logo-spin infinite 20s linear;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card {
 | 
			
		||||
  padding: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.read-the-docs {
 | 
			
		||||
  color: #888;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								src/App.tsx
									
									
									
									
									
								
							@@ -1,29 +0,0 @@
 | 
			
		||||
import { Toaster } from "@/components/ui/toaster";
 | 
			
		||||
import { Toaster as Sonner } from "@/components/ui/sonner";
 | 
			
		||||
import { TooltipProvider } from "@/components/ui/tooltip";
 | 
			
		||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
 | 
			
		||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
 | 
			
		||||
import Index from "./pages/Index";
 | 
			
		||||
import GiteaOAuthCallback from "./pages/GiteaOAuthCallback";
 | 
			
		||||
import NotFound from "./pages/NotFound";
 | 
			
		||||
 | 
			
		||||
const queryClient = new QueryClient();
 | 
			
		||||
 | 
			
		||||
const App = () => (
 | 
			
		||||
  <QueryClientProvider client={queryClient}>
 | 
			
		||||
    <TooltipProvider>
 | 
			
		||||
      <Toaster />
 | 
			
		||||
      <Sonner />
 | 
			
		||||
      <BrowserRouter basename="/admin">
 | 
			
		||||
        <Routes>
 | 
			
		||||
          <Route path="/" element={<Index />} />
 | 
			
		||||
          <Route path="/oauth/gitea/callback" element={<GiteaOAuthCallback />} />
 | 
			
		||||
          {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
 | 
			
		||||
          <Route path="*" element={<NotFound />} />
 | 
			
		||||
        </Routes>
 | 
			
		||||
      </BrowserRouter>
 | 
			
		||||
    </TooltipProvider>
 | 
			
		||||
  </QueryClientProvider>
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export default App;
 | 
			
		||||
@@ -1,289 +0,0 @@
 | 
			
		||||
import React, { useState, useEffect } from 'react';
 | 
			
		||||
import { Button } from '@/components/ui/button';
 | 
			
		||||
import { Card, CardContent } from '@/components/ui/card';
 | 
			
		||||
import { toast } from '@/hooks/use-toast';
 | 
			
		||||
import { Check, X, LogOut, Loader2 } from 'lucide-react';
 | 
			
		||||
 | 
			
		||||
interface MediaItem {
 | 
			
		||||
  id: string;
 | 
			
		||||
  type: string;
 | 
			
		||||
  url: string;
 | 
			
		||||
  preview_url: string;
 | 
			
		||||
  remote_url: string;
 | 
			
		||||
  PostID: string;
 | 
			
		||||
  approved: boolean;
 | 
			
		||||
  rejected: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface AdminDashboardProps {
 | 
			
		||||
  token: string;
 | 
			
		||||
  onLogout: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const AdminDashboard = ({ token, onLogout }: AdminDashboardProps) => {
 | 
			
		||||
  const [mediaQueue, setMediaQueue] = useState<MediaItem[]>([]);
 | 
			
		||||
  const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
  const [isProcessing, setIsProcessing] = useState(false);
 | 
			
		||||
  const [statusMessage, setStatusMessage] = useState('');
 | 
			
		||||
 | 
			
		||||
  // Get current media item
 | 
			
		||||
  const currentMedia = mediaQueue.length > 0 ? mediaQueue[0] : null;
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    // only preload if there are items after the current one
 | 
			
		||||
    mediaQueue.slice(1, 5).forEach(media => {
 | 
			
		||||
      if (!media.preview_url) return;
 | 
			
		||||
 | 
			
		||||
      const img = new Image();
 | 
			
		||||
      img.onload = () => {
 | 
			
		||||
        // successful preload — nothing to do
 | 
			
		||||
      };
 | 
			
		||||
      img.onerror = () => {
 | 
			
		||||
        // this media is broken even before we show it: remove it now
 | 
			
		||||
        setMediaQueue(queue =>
 | 
			
		||||
          queue.filter(item => item.id !== media.id)
 | 
			
		||||
        );
 | 
			
		||||
      };
 | 
			
		||||
      img.src = media.preview_url;
 | 
			
		||||
    });
 | 
			
		||||
  }, [mediaQueue]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  const fetchMedia = async () => {
 | 
			
		||||
    setIsLoading(true);
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await fetch('/admin/api/getmedia', {
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Authorization': `Bearer ${token}`,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (response.status === 401) {
 | 
			
		||||
        toast({
 | 
			
		||||
          title: "Session Expired",
 | 
			
		||||
          description: "Please login again",
 | 
			
		||||
          variant: "destructive",
 | 
			
		||||
        });
 | 
			
		||||
        onLogout();
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (response.ok) {
 | 
			
		||||
        return await response.json();
 | 
			
		||||
      } else {
 | 
			
		||||
        const errorData = await response.json();
 | 
			
		||||
        toast({
 | 
			
		||||
          title: "Error",
 | 
			
		||||
          description: errorData.message || "Failed to fetch media",
 | 
			
		||||
          variant: "destructive",
 | 
			
		||||
        });
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      toast({
 | 
			
		||||
        title: "Error",
 | 
			
		||||
        description: "Network error. Please try again.",
 | 
			
		||||
        variant: "destructive",
 | 
			
		||||
      });
 | 
			
		||||
      return null;
 | 
			
		||||
    } finally {
 | 
			
		||||
      setIsLoading(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const fetchNextMedia = async () => {
 | 
			
		||||
    const media = await fetchMedia();
 | 
			
		||||
    if (media) {
 | 
			
		||||
      setMediaQueue(prevQueue => [...prevQueue, media]);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Fill the queue with initial media items
 | 
			
		||||
  const fillMediaQueue = async () => {
 | 
			
		||||
    // If queue is already being filled or has items, don't fetch more
 | 
			
		||||
    if (isLoading || mediaQueue.length >= 5) return;
 | 
			
		||||
 | 
			
		||||
    // Determine how many more items we need
 | 
			
		||||
    const itemsNeeded = 5 - mediaQueue.length;
 | 
			
		||||
 | 
			
		||||
    // Fetch the items one by one
 | 
			
		||||
    for (let i = 0; i < itemsNeeded; i++) {
 | 
			
		||||
      await fetchNextMedia();
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Initial load and keep the queue filled
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fillMediaQueue();
 | 
			
		||||
  }, [mediaQueue.length]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  const handleAction = async (action: 'approve' | 'reject') => {
 | 
			
		||||
    if (!currentMedia) return;
 | 
			
		||||
 | 
			
		||||
    setIsProcessing(true);
 | 
			
		||||
    setStatusMessage(`${action === 'approve' ? 'Approving' : 'Rejecting'}...`);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await fetch(`/admin/api/${action}`, {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
          'Authorization': `Bearer ${token}`,
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({ mediaId: currentMedia.id }),
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (response.status === 401) {
 | 
			
		||||
        toast({
 | 
			
		||||
          title: "Session Expired",
 | 
			
		||||
          description: "Please login again",
 | 
			
		||||
          variant: "destructive",
 | 
			
		||||
        });
 | 
			
		||||
        onLogout();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const data = await response.json();
 | 
			
		||||
 | 
			
		||||
      if (response.ok) {
 | 
			
		||||
        setStatusMessage(`${action === 'approve' ? 'Approved' : 'Rejected'} successfully!`);
 | 
			
		||||
 | 
			
		||||
        // Show success message briefly, then move to the next item
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          // Remove the current item from the queue
 | 
			
		||||
          setMediaQueue(prevQueue => prevQueue.slice(1));
 | 
			
		||||
          setStatusMessage('');
 | 
			
		||||
          setIsProcessing(false);
 | 
			
		||||
        }, 1000);
 | 
			
		||||
      } else {
 | 
			
		||||
        toast({
 | 
			
		||||
          title: "Error",
 | 
			
		||||
          description: data.message || `Failed to ${action} media`,
 | 
			
		||||
          variant: "destructive",
 | 
			
		||||
        });
 | 
			
		||||
        setIsProcessing(false);
 | 
			
		||||
        setStatusMessage('');
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      toast({
 | 
			
		||||
        title: "Error",
 | 
			
		||||
        description: "Network error. Please try again.",
 | 
			
		||||
        variant: "destructive",
 | 
			
		||||
      });
 | 
			
		||||
      setIsProcessing(false);
 | 
			
		||||
      setStatusMessage('');
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
 | 
			
		||||
      {/* Header */}
 | 
			
		||||
      <div className="bg-white shadow-sm border-b">
 | 
			
		||||
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
 | 
			
		||||
          <div className="flex justify-between items-center h-16">
 | 
			
		||||
            <h1 className="text-xl font-semibold text-slate-800">Admin Dashboard</h1>
 | 
			
		||||
            <Button
 | 
			
		||||
              variant="outline"
 | 
			
		||||
              onClick={onLogout}
 | 
			
		||||
              className="flex items-center gap-2"
 | 
			
		||||
            >
 | 
			
		||||
              <LogOut className="w-4 h-4" />
 | 
			
		||||
              Logout
 | 
			
		||||
            </Button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {/* Main Content */}
 | 
			
		||||
      <div className="flex items-center justify-center min-h-[calc(100vh-4rem)] p-8">
 | 
			
		||||
        <Card className="max-w-2xl w-full shadow-lg">
 | 
			
		||||
          <CardContent className="p-8">
 | 
			
		||||
            {isLoading && mediaQueue.length === 0 ? (
 | 
			
		||||
              <div className="flex flex-col items-center justify-center py-16">
 | 
			
		||||
                <Loader2 className="w-8 h-8 animate-spin text-slate-600 mb-4" />
 | 
			
		||||
                <p className="text-slate-600">Loading media...</p>
 | 
			
		||||
              </div>
 | 
			
		||||
            ) : currentMedia ? (
 | 
			
		||||
              <div className="space-y-6">
 | 
			
		||||
                {/* Image */}
 | 
			
		||||
                <div className="flex justify-center">
 | 
			
		||||
                  <img
 | 
			
		||||
                    src={currentMedia.preview_url}
 | 
			
		||||
                    alt="Media to review"
 | 
			
		||||
                    className="max-w-full max-h-96 … object-contain"
 | 
			
		||||
                    onError={(e) => {
 | 
			
		||||
                      const imgEl = e.currentTarget;
 | 
			
		||||
                      if (!imgEl.dataset.fallbackTried) {
 | 
			
		||||
                        imgEl.dataset.fallbackTried = 'true';
 | 
			
		||||
                        imgEl.src = currentMedia.remote_url;
 | 
			
		||||
                      } else {
 | 
			
		||||
                        // both preview AND remote have failed — drop it
 | 
			
		||||
                        setMediaQueue(queue =>
 | 
			
		||||
                          queue.filter(item => item.id !== currentMedia.id)
 | 
			
		||||
                        );
 | 
			
		||||
                      }
 | 
			
		||||
                    }}
 | 
			
		||||
                  />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                {/* Media Info */}
 | 
			
		||||
                <div className="text-center text-sm text-slate-600">
 | 
			
		||||
                  <p>Media ID: {currentMedia.id}</p>
 | 
			
		||||
                  <p>Post ID: {currentMedia.PostID}</p>
 | 
			
		||||
                  <p className="text-xs text-slate-400 mt-1">
 | 
			
		||||
                    {mediaQueue.length > 1 ? `${mediaQueue.length - 1} more item(s) pre-loaded` : 'Loading more items...'}
 | 
			
		||||
                  </p>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                {/* Status Message */}
 | 
			
		||||
                {statusMessage && (
 | 
			
		||||
                  <div className="text-center">
 | 
			
		||||
                    <p className="text-lg font-medium text-slate-700">{statusMessage}</p>
 | 
			
		||||
                  </div>
 | 
			
		||||
                )}
 | 
			
		||||
 | 
			
		||||
                {/* Action Buttons */}
 | 
			
		||||
                <div className="flex gap-4 justify-center">
 | 
			
		||||
                  <Button
 | 
			
		||||
                    onClick={() => handleAction('reject')}
 | 
			
		||||
                    disabled={isProcessing}
 | 
			
		||||
                    variant="destructive"
 | 
			
		||||
                    className="flex items-center gap-2 px-8 py-3"
 | 
			
		||||
                  >
 | 
			
		||||
                    <X className="w-5 h-5" />
 | 
			
		||||
                    Reject
 | 
			
		||||
                  </Button>
 | 
			
		||||
                  <Button
 | 
			
		||||
                    onClick={() => handleAction('approve')}
 | 
			
		||||
                    disabled={isProcessing}
 | 
			
		||||
                    className="flex items-center gap-2 px-8 py-3 bg-green-600 hover:bg-green-700"
 | 
			
		||||
                  >
 | 
			
		||||
                    <Check className="w-5 h-5" />
 | 
			
		||||
                    Approve
 | 
			
		||||
                  </Button>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <div className="text-center py-16">
 | 
			
		||||
                <p className="text-lg text-slate-600">No media available for review</p>
 | 
			
		||||
                <Button
 | 
			
		||||
                  onClick={fillMediaQueue}
 | 
			
		||||
                  className="mt-4"
 | 
			
		||||
                  variant="outline"
 | 
			
		||||
                >
 | 
			
		||||
                  Refresh
 | 
			
		||||
                </Button>
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
          </CardContent>
 | 
			
		||||
        </Card>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default AdminDashboard;
 | 
			
		||||
 | 
			
		||||
@@ -1,124 +0,0 @@
 | 
			
		||||
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { Button } from '@/components/ui/button';
 | 
			
		||||
import { Input } from '@/components/ui/input';
 | 
			
		||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
 | 
			
		||||
import { toast } from '@/hooks/use-toast';
 | 
			
		||||
import { Lock, Github } from 'lucide-react';
 | 
			
		||||
 | 
			
		||||
interface LoginPageProps {
 | 
			
		||||
  onLogin: (token: string) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const LoginPage = ({ onLogin }: LoginPageProps) => {
 | 
			
		||||
  const [password, setPassword] = useState('');
 | 
			
		||||
  const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const handleLogin = async (e: React.FormEvent) => {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    if (!password.trim()) {
 | 
			
		||||
      toast({
 | 
			
		||||
        title: "Error",
 | 
			
		||||
        description: "Please enter a password",
 | 
			
		||||
        variant: "destructive",
 | 
			
		||||
      });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setIsLoading(true);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await fetch('/admin/api/login', {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({ Password: password }),
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const data = await response.json();
 | 
			
		||||
 | 
			
		||||
      if (response.ok && data.token) {
 | 
			
		||||
        toast({
 | 
			
		||||
          title: "Success",
 | 
			
		||||
          description: data.message || "Login successful",
 | 
			
		||||
        });
 | 
			
		||||
        onLogin(data.token);
 | 
			
		||||
      } else {
 | 
			
		||||
        toast({
 | 
			
		||||
          title: "Error",
 | 
			
		||||
          description: data.message || "Login failed",
 | 
			
		||||
          variant: "destructive",
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      toast({
 | 
			
		||||
        title: "Error",
 | 
			
		||||
        description: "Network error. Please try again.",
 | 
			
		||||
        variant: "destructive",
 | 
			
		||||
      });
 | 
			
		||||
    } finally {
 | 
			
		||||
      setIsLoading(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleGiteaLogin = () => {
 | 
			
		||||
    // Redirect to the Gitea OAuth endpoint
 | 
			
		||||
    window.location.href = '/admin/api/login/oauth/gitea';
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100">
 | 
			
		||||
      <Card className="w-full max-w-md shadow-lg">
 | 
			
		||||
        <CardHeader className="text-center pb-4">
 | 
			
		||||
          <div className="mx-auto w-12 h-12 bg-slate-100 rounded-full flex items-center justify-center mb-4">
 | 
			
		||||
            <Lock className="w-6 h-6 text-slate-600" />
 | 
			
		||||
          </div>
 | 
			
		||||
          <CardTitle className="text-2xl font-semibold text-slate-800">Admin Login</CardTitle>
 | 
			
		||||
        </CardHeader>
 | 
			
		||||
        <CardContent>
 | 
			
		||||
          <form onSubmit={handleLogin} className="space-y-6">
 | 
			
		||||
            <div className="space-y-2">
 | 
			
		||||
              <Input
 | 
			
		||||
                type="password"
 | 
			
		||||
                placeholder="Enter admin password"
 | 
			
		||||
                value={password}
 | 
			
		||||
                onChange={(e) => setPassword(e.target.value)}
 | 
			
		||||
                className="h-12"
 | 
			
		||||
                disabled={isLoading}
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
            <Button 
 | 
			
		||||
              type="submit" 
 | 
			
		||||
              className="w-full h-12 text-base"
 | 
			
		||||
              disabled={isLoading}
 | 
			
		||||
            >
 | 
			
		||||
              {isLoading ? "Signing in..." : "Sign In"}
 | 
			
		||||
            </Button>
 | 
			
		||||
          </form>
 | 
			
		||||
          
 | 
			
		||||
          <div className="relative my-6">
 | 
			
		||||
            <div className="absolute inset-0 flex items-center">
 | 
			
		||||
              <div className="w-full border-t border-gray-300"></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className="relative flex justify-center text-sm">
 | 
			
		||||
              <span className="px-2 bg-white text-gray-500">Or continue with</span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          
 | 
			
		||||
          <Button
 | 
			
		||||
            type="button"
 | 
			
		||||
            onClick={handleGiteaLogin}
 | 
			
		||||
            className="w-full h-12 text-base"
 | 
			
		||||
            variant="outline"
 | 
			
		||||
          >
 | 
			
		||||
            <Github className="w-5 h-5 mr-2" />
 | 
			
		||||
            Login with Gitea
 | 
			
		||||
          </Button>
 | 
			
		||||
        </CardContent>
 | 
			
		||||
      </Card>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default LoginPage;
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
 | 
			
		||||
import { ChevronDown } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Accordion = AccordionPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const AccordionItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AccordionPrimitive.Item>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <AccordionPrimitive.Item
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("border-b", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
AccordionItem.displayName = "AccordionItem"
 | 
			
		||||
 | 
			
		||||
const AccordionTrigger = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AccordionPrimitive.Trigger>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <AccordionPrimitive.Header className="flex">
 | 
			
		||||
    <AccordionPrimitive.Trigger
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      {children}
 | 
			
		||||
      <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
 | 
			
		||||
    </AccordionPrimitive.Trigger>
 | 
			
		||||
  </AccordionPrimitive.Header>
 | 
			
		||||
))
 | 
			
		||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
 | 
			
		||||
 | 
			
		||||
const AccordionContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AccordionPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <AccordionPrimitive.Content
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <div className={cn("pb-4 pt-0", className)}>{children}</div>
 | 
			
		||||
  </AccordionPrimitive.Content>
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
 | 
			
		||||
@@ -1,139 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
import { buttonVariants } from "@/components/ui/button"
 | 
			
		||||
 | 
			
		||||
const AlertDialog = AlertDialogPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
 | 
			
		||||
 | 
			
		||||
const AlertDialogPortal = AlertDialogPrimitive.Portal
 | 
			
		||||
 | 
			
		||||
const AlertDialogOverlay = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <AlertDialogPrimitive.Overlay
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
    ref={ref}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
 | 
			
		||||
 | 
			
		||||
const AlertDialogContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AlertDialogPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <AlertDialogPortal>
 | 
			
		||||
    <AlertDialogOverlay />
 | 
			
		||||
    <AlertDialogPrimitive.Content
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "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",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  </AlertDialogPortal>
 | 
			
		||||
))
 | 
			
		||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
const AlertDialogHeader = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
			
		||||
  <div
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex flex-col space-y-2 text-center sm:text-left",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
AlertDialogHeader.displayName = "AlertDialogHeader"
 | 
			
		||||
 | 
			
		||||
const AlertDialogFooter = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
			
		||||
  <div
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
AlertDialogFooter.displayName = "AlertDialogFooter"
 | 
			
		||||
 | 
			
		||||
const AlertDialogTitle = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AlertDialogPrimitive.Title>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <AlertDialogPrimitive.Title
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("text-lg font-semibold", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
 | 
			
		||||
 | 
			
		||||
const AlertDialogDescription = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AlertDialogPrimitive.Description>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <AlertDialogPrimitive.Description
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("text-sm text-muted-foreground", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
AlertDialogDescription.displayName =
 | 
			
		||||
  AlertDialogPrimitive.Description.displayName
 | 
			
		||||
 | 
			
		||||
const AlertDialogAction = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AlertDialogPrimitive.Action>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <AlertDialogPrimitive.Action
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(buttonVariants(), className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
 | 
			
		||||
 | 
			
		||||
const AlertDialogCancel = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <AlertDialogPrimitive.Cancel
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      buttonVariants({ variant: "outline" }),
 | 
			
		||||
      "mt-2 sm:mt-0",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  AlertDialog,
 | 
			
		||||
  AlertDialogPortal,
 | 
			
		||||
  AlertDialogOverlay,
 | 
			
		||||
  AlertDialogTrigger,
 | 
			
		||||
  AlertDialogContent,
 | 
			
		||||
  AlertDialogHeader,
 | 
			
		||||
  AlertDialogFooter,
 | 
			
		||||
  AlertDialogTitle,
 | 
			
		||||
  AlertDialogDescription,
 | 
			
		||||
  AlertDialogAction,
 | 
			
		||||
  AlertDialogCancel,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,59 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import { cva, type VariantProps } from "class-variance-authority"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const alertVariants = cva(
 | 
			
		||||
  "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
 | 
			
		||||
  {
 | 
			
		||||
    variants: {
 | 
			
		||||
      variant: {
 | 
			
		||||
        default: "bg-background text-foreground",
 | 
			
		||||
        destructive:
 | 
			
		||||
          "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    defaultVariants: {
 | 
			
		||||
      variant: "default",
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const Alert = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
 | 
			
		||||
>(({ className, variant, ...props }, ref) => (
 | 
			
		||||
  <div
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    role="alert"
 | 
			
		||||
    className={cn(alertVariants({ variant }), className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
Alert.displayName = "Alert"
 | 
			
		||||
 | 
			
		||||
const AlertTitle = React.forwardRef<
 | 
			
		||||
  HTMLParagraphElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLHeadingElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <h5
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("mb-1 font-medium leading-none tracking-tight", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
AlertTitle.displayName = "AlertTitle"
 | 
			
		||||
 | 
			
		||||
const AlertDescription = React.forwardRef<
 | 
			
		||||
  HTMLParagraphElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLParagraphElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("text-sm [&_p]:leading-relaxed", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
AlertDescription.displayName = "AlertDescription"
 | 
			
		||||
 | 
			
		||||
export { Alert, AlertTitle, AlertDescription }
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
 | 
			
		||||
 | 
			
		||||
const AspectRatio = AspectRatioPrimitive.Root
 | 
			
		||||
 | 
			
		||||
export { AspectRatio }
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Avatar = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AvatarPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <AvatarPrimitive.Root
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
Avatar.displayName = AvatarPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
const AvatarImage = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AvatarPrimitive.Image>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <AvatarPrimitive.Image
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("aspect-square h-full w-full", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName
 | 
			
		||||
 | 
			
		||||
const AvatarFallback = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof AvatarPrimitive.Fallback>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <AvatarPrimitive.Fallback
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex h-full w-full items-center justify-center rounded-full bg-muted",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
 | 
			
		||||
 | 
			
		||||
export { Avatar, AvatarImage, AvatarFallback }
 | 
			
		||||
@@ -1,36 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import { cva, type VariantProps } from "class-variance-authority"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const badgeVariants = cva(
 | 
			
		||||
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
 | 
			
		||||
  {
 | 
			
		||||
    variants: {
 | 
			
		||||
      variant: {
 | 
			
		||||
        default:
 | 
			
		||||
          "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
 | 
			
		||||
        secondary:
 | 
			
		||||
          "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
 | 
			
		||||
        destructive:
 | 
			
		||||
          "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
 | 
			
		||||
        outline: "text-foreground",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    defaultVariants: {
 | 
			
		||||
      variant: "default",
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
export interface BadgeProps
 | 
			
		||||
  extends React.HTMLAttributes<HTMLDivElement>,
 | 
			
		||||
    VariantProps<typeof badgeVariants> {}
 | 
			
		||||
 | 
			
		||||
function Badge({ className, variant, ...props }: BadgeProps) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={cn(badgeVariants({ variant }), className)} {...props} />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { Badge, badgeVariants }
 | 
			
		||||
@@ -1,115 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import { Slot } from "@radix-ui/react-slot"
 | 
			
		||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Breadcrumb = React.forwardRef<
 | 
			
		||||
  HTMLElement,
 | 
			
		||||
  React.ComponentPropsWithoutRef<"nav"> & {
 | 
			
		||||
    separator?: React.ReactNode
 | 
			
		||||
  }
 | 
			
		||||
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
 | 
			
		||||
Breadcrumb.displayName = "Breadcrumb"
 | 
			
		||||
 | 
			
		||||
const BreadcrumbList = React.forwardRef<
 | 
			
		||||
  HTMLOListElement,
 | 
			
		||||
  React.ComponentPropsWithoutRef<"ol">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ol
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
BreadcrumbList.displayName = "BreadcrumbList"
 | 
			
		||||
 | 
			
		||||
const BreadcrumbItem = React.forwardRef<
 | 
			
		||||
  HTMLLIElement,
 | 
			
		||||
  React.ComponentPropsWithoutRef<"li">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <li
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("inline-flex items-center gap-1.5", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
BreadcrumbItem.displayName = "BreadcrumbItem"
 | 
			
		||||
 | 
			
		||||
const BreadcrumbLink = React.forwardRef<
 | 
			
		||||
  HTMLAnchorElement,
 | 
			
		||||
  React.ComponentPropsWithoutRef<"a"> & {
 | 
			
		||||
    asChild?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ asChild, className, ...props }, ref) => {
 | 
			
		||||
  const Comp = asChild ? Slot : "a"
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Comp
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn("transition-colors hover:text-foreground", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
BreadcrumbLink.displayName = "BreadcrumbLink"
 | 
			
		||||
 | 
			
		||||
const BreadcrumbPage = React.forwardRef<
 | 
			
		||||
  HTMLSpanElement,
 | 
			
		||||
  React.ComponentPropsWithoutRef<"span">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <span
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    role="link"
 | 
			
		||||
    aria-disabled="true"
 | 
			
		||||
    aria-current="page"
 | 
			
		||||
    className={cn("font-normal text-foreground", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
BreadcrumbPage.displayName = "BreadcrumbPage"
 | 
			
		||||
 | 
			
		||||
const BreadcrumbSeparator = ({
 | 
			
		||||
  children,
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<"li">) => (
 | 
			
		||||
  <li
 | 
			
		||||
    role="presentation"
 | 
			
		||||
    aria-hidden="true"
 | 
			
		||||
    className={cn("[&>svg]:size-3.5", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    {children ?? <ChevronRight />}
 | 
			
		||||
  </li>
 | 
			
		||||
)
 | 
			
		||||
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
 | 
			
		||||
 | 
			
		||||
const BreadcrumbEllipsis = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<"span">) => (
 | 
			
		||||
  <span
 | 
			
		||||
    role="presentation"
 | 
			
		||||
    aria-hidden="true"
 | 
			
		||||
    className={cn("flex h-9 w-9 items-center justify-center", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <MoreHorizontal className="h-4 w-4" />
 | 
			
		||||
    <span className="sr-only">More</span>
 | 
			
		||||
  </span>
 | 
			
		||||
)
 | 
			
		||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Breadcrumb,
 | 
			
		||||
  BreadcrumbList,
 | 
			
		||||
  BreadcrumbItem,
 | 
			
		||||
  BreadcrumbLink,
 | 
			
		||||
  BreadcrumbPage,
 | 
			
		||||
  BreadcrumbSeparator,
 | 
			
		||||
  BreadcrumbEllipsis,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
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 buttonVariants = cva(
 | 
			
		||||
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
 | 
			
		||||
  {
 | 
			
		||||
    variants: {
 | 
			
		||||
      variant: {
 | 
			
		||||
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
 | 
			
		||||
        destructive:
 | 
			
		||||
          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
 | 
			
		||||
        outline:
 | 
			
		||||
          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
 | 
			
		||||
        secondary:
 | 
			
		||||
          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
 | 
			
		||||
        ghost: "hover:bg-accent hover:text-accent-foreground",
 | 
			
		||||
        link: "text-primary underline-offset-4 hover:underline",
 | 
			
		||||
      },
 | 
			
		||||
      size: {
 | 
			
		||||
        default: "h-10 px-4 py-2",
 | 
			
		||||
        sm: "h-9 rounded-md px-3",
 | 
			
		||||
        lg: "h-11 rounded-md px-8",
 | 
			
		||||
        icon: "h-10 w-10",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    defaultVariants: {
 | 
			
		||||
      variant: "default",
 | 
			
		||||
      size: "default",
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
export interface ButtonProps
 | 
			
		||||
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
 | 
			
		||||
    VariantProps<typeof buttonVariants> {
 | 
			
		||||
  asChild?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
 | 
			
		||||
  ({ className, variant, size, asChild = false, ...props }, ref) => {
 | 
			
		||||
    const Comp = asChild ? Slot : "button"
 | 
			
		||||
    return (
 | 
			
		||||
      <Comp
 | 
			
		||||
        className={cn(buttonVariants({ variant, size, className }))}
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        {...props}
 | 
			
		||||
      />
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
Button.displayName = "Button"
 | 
			
		||||
 | 
			
		||||
export { Button, buttonVariants }
 | 
			
		||||
@@ -1,64 +0,0 @@
 | 
			
		||||
import * as React from "react";
 | 
			
		||||
import { ChevronLeft, ChevronRight } from "lucide-react";
 | 
			
		||||
import { DayPicker } from "react-day-picker";
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
import { buttonVariants } from "@/components/ui/button";
 | 
			
		||||
 | 
			
		||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
 | 
			
		||||
 | 
			
		||||
function Calendar({
 | 
			
		||||
  className,
 | 
			
		||||
  classNames,
 | 
			
		||||
  showOutsideDays = true,
 | 
			
		||||
  ...props
 | 
			
		||||
}: CalendarProps) {
 | 
			
		||||
  return (
 | 
			
		||||
    <DayPicker
 | 
			
		||||
      showOutsideDays={showOutsideDays}
 | 
			
		||||
      className={cn("p-3", className)}
 | 
			
		||||
      classNames={{
 | 
			
		||||
        months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
 | 
			
		||||
        month: "space-y-4",
 | 
			
		||||
        caption: "flex justify-center pt-1 relative items-center",
 | 
			
		||||
        caption_label: "text-sm font-medium",
 | 
			
		||||
        nav: "space-x-1 flex items-center",
 | 
			
		||||
        nav_button: cn(
 | 
			
		||||
          buttonVariants({ variant: "outline" }),
 | 
			
		||||
          "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
 | 
			
		||||
        ),
 | 
			
		||||
        nav_button_previous: "absolute left-1",
 | 
			
		||||
        nav_button_next: "absolute right-1",
 | 
			
		||||
        table: "w-full border-collapse space-y-1",
 | 
			
		||||
        head_row: "flex",
 | 
			
		||||
        head_cell:
 | 
			
		||||
          "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
 | 
			
		||||
        row: "flex w-full mt-2",
 | 
			
		||||
        cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
 | 
			
		||||
        day: cn(
 | 
			
		||||
          buttonVariants({ variant: "ghost" }),
 | 
			
		||||
          "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
 | 
			
		||||
        ),
 | 
			
		||||
        day_range_end: "day-range-end",
 | 
			
		||||
        day_selected:
 | 
			
		||||
          "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
 | 
			
		||||
        day_today: "bg-accent text-accent-foreground",
 | 
			
		||||
        day_outside:
 | 
			
		||||
          "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
 | 
			
		||||
        day_disabled: "text-muted-foreground opacity-50",
 | 
			
		||||
        day_range_middle:
 | 
			
		||||
          "aria-selected:bg-accent aria-selected:text-accent-foreground",
 | 
			
		||||
        day_hidden: "invisible",
 | 
			
		||||
        ...classNames,
 | 
			
		||||
      }}
 | 
			
		||||
      components={{
 | 
			
		||||
        IconLeft: ({ ..._props }) => <ChevronLeft className="h-4 w-4" />,
 | 
			
		||||
        IconRight: ({ ..._props }) => <ChevronRight className="h-4 w-4" />,
 | 
			
		||||
      }}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
Calendar.displayName = "Calendar";
 | 
			
		||||
 | 
			
		||||
export { Calendar };
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Card = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLDivElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "rounded-lg border bg-card text-card-foreground shadow-sm",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
Card.displayName = "Card"
 | 
			
		||||
 | 
			
		||||
const CardHeader = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLDivElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("flex flex-col space-y-1.5 p-6", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
CardHeader.displayName = "CardHeader"
 | 
			
		||||
 | 
			
		||||
const CardTitle = React.forwardRef<
 | 
			
		||||
  HTMLParagraphElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLHeadingElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <h3
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "text-2xl font-semibold leading-none tracking-tight",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
CardTitle.displayName = "CardTitle"
 | 
			
		||||
 | 
			
		||||
const CardDescription = React.forwardRef<
 | 
			
		||||
  HTMLParagraphElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLParagraphElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <p
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("text-sm text-muted-foreground", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
CardDescription.displayName = "CardDescription"
 | 
			
		||||
 | 
			
		||||
const CardContent = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLDivElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
 | 
			
		||||
))
 | 
			
		||||
CardContent.displayName = "CardContent"
 | 
			
		||||
 | 
			
		||||
const CardFooter = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLDivElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("flex items-center p-6 pt-0", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
CardFooter.displayName = "CardFooter"
 | 
			
		||||
 | 
			
		||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
 | 
			
		||||
@@ -1,260 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import useEmblaCarousel, {
 | 
			
		||||
  type UseEmblaCarouselType,
 | 
			
		||||
} from "embla-carousel-react"
 | 
			
		||||
import { ArrowLeft, ArrowRight } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
import { Button } from "@/components/ui/button"
 | 
			
		||||
 | 
			
		||||
type CarouselApi = UseEmblaCarouselType[1]
 | 
			
		||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
 | 
			
		||||
type CarouselOptions = UseCarouselParameters[0]
 | 
			
		||||
type CarouselPlugin = UseCarouselParameters[1]
 | 
			
		||||
 | 
			
		||||
type CarouselProps = {
 | 
			
		||||
  opts?: CarouselOptions
 | 
			
		||||
  plugins?: CarouselPlugin
 | 
			
		||||
  orientation?: "horizontal" | "vertical"
 | 
			
		||||
  setApi?: (api: CarouselApi) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CarouselContextProps = {
 | 
			
		||||
  carouselRef: ReturnType<typeof useEmblaCarousel>[0]
 | 
			
		||||
  api: ReturnType<typeof useEmblaCarousel>[1]
 | 
			
		||||
  scrollPrev: () => void
 | 
			
		||||
  scrollNext: () => void
 | 
			
		||||
  canScrollPrev: boolean
 | 
			
		||||
  canScrollNext: boolean
 | 
			
		||||
} & CarouselProps
 | 
			
		||||
 | 
			
		||||
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
 | 
			
		||||
 | 
			
		||||
function useCarousel() {
 | 
			
		||||
  const context = React.useContext(CarouselContext)
 | 
			
		||||
 | 
			
		||||
  if (!context) {
 | 
			
		||||
    throw new Error("useCarousel must be used within a <Carousel />")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Carousel = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLDivElement> & CarouselProps
 | 
			
		||||
>(
 | 
			
		||||
  (
 | 
			
		||||
    {
 | 
			
		||||
      orientation = "horizontal",
 | 
			
		||||
      opts,
 | 
			
		||||
      setApi,
 | 
			
		||||
      plugins,
 | 
			
		||||
      className,
 | 
			
		||||
      children,
 | 
			
		||||
      ...props
 | 
			
		||||
    },
 | 
			
		||||
    ref
 | 
			
		||||
  ) => {
 | 
			
		||||
    const [carouselRef, api] = useEmblaCarousel(
 | 
			
		||||
      {
 | 
			
		||||
        ...opts,
 | 
			
		||||
        axis: orientation === "horizontal" ? "x" : "y",
 | 
			
		||||
      },
 | 
			
		||||
      plugins
 | 
			
		||||
    )
 | 
			
		||||
    const [canScrollPrev, setCanScrollPrev] = React.useState(false)
 | 
			
		||||
    const [canScrollNext, setCanScrollNext] = React.useState(false)
 | 
			
		||||
 | 
			
		||||
    const onSelect = React.useCallback((api: CarouselApi) => {
 | 
			
		||||
      if (!api) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      setCanScrollPrev(api.canScrollPrev())
 | 
			
		||||
      setCanScrollNext(api.canScrollNext())
 | 
			
		||||
    }, [])
 | 
			
		||||
 | 
			
		||||
    const scrollPrev = React.useCallback(() => {
 | 
			
		||||
      api?.scrollPrev()
 | 
			
		||||
    }, [api])
 | 
			
		||||
 | 
			
		||||
    const scrollNext = React.useCallback(() => {
 | 
			
		||||
      api?.scrollNext()
 | 
			
		||||
    }, [api])
 | 
			
		||||
 | 
			
		||||
    const handleKeyDown = React.useCallback(
 | 
			
		||||
      (event: React.KeyboardEvent<HTMLDivElement>) => {
 | 
			
		||||
        if (event.key === "ArrowLeft") {
 | 
			
		||||
          event.preventDefault()
 | 
			
		||||
          scrollPrev()
 | 
			
		||||
        } else if (event.key === "ArrowRight") {
 | 
			
		||||
          event.preventDefault()
 | 
			
		||||
          scrollNext()
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      [scrollPrev, scrollNext]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
      if (!api || !setApi) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      setApi(api)
 | 
			
		||||
    }, [api, setApi])
 | 
			
		||||
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
      if (!api) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      onSelect(api)
 | 
			
		||||
      api.on("reInit", onSelect)
 | 
			
		||||
      api.on("select", onSelect)
 | 
			
		||||
 | 
			
		||||
      return () => {
 | 
			
		||||
        api?.off("select", onSelect)
 | 
			
		||||
      }
 | 
			
		||||
    }, [api, onSelect])
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <CarouselContext.Provider
 | 
			
		||||
        value={{
 | 
			
		||||
          carouselRef,
 | 
			
		||||
          api: api,
 | 
			
		||||
          opts,
 | 
			
		||||
          orientation:
 | 
			
		||||
            orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
 | 
			
		||||
          scrollPrev,
 | 
			
		||||
          scrollNext,
 | 
			
		||||
          canScrollPrev,
 | 
			
		||||
          canScrollNext,
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          ref={ref}
 | 
			
		||||
          onKeyDownCapture={handleKeyDown}
 | 
			
		||||
          className={cn("relative", className)}
 | 
			
		||||
          role="region"
 | 
			
		||||
          aria-roledescription="carousel"
 | 
			
		||||
          {...props}
 | 
			
		||||
        >
 | 
			
		||||
          {children}
 | 
			
		||||
        </div>
 | 
			
		||||
      </CarouselContext.Provider>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
Carousel.displayName = "Carousel"
 | 
			
		||||
 | 
			
		||||
const CarouselContent = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLDivElement>
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  const { carouselRef, orientation } = useCarousel()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div ref={carouselRef} className="overflow-hidden">
 | 
			
		||||
      <div
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "flex",
 | 
			
		||||
          orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
 | 
			
		||||
          className
 | 
			
		||||
        )}
 | 
			
		||||
        {...props}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
CarouselContent.displayName = "CarouselContent"
 | 
			
		||||
 | 
			
		||||
const CarouselItem = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLDivElement>
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  const { orientation } = useCarousel()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      role="group"
 | 
			
		||||
      aria-roledescription="slide"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "min-w-0 shrink-0 grow-0 basis-full",
 | 
			
		||||
        orientation === "horizontal" ? "pl-4" : "pt-4",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
CarouselItem.displayName = "CarouselItem"
 | 
			
		||||
 | 
			
		||||
const CarouselPrevious = React.forwardRef<
 | 
			
		||||
  HTMLButtonElement,
 | 
			
		||||
  React.ComponentProps<typeof Button>
 | 
			
		||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
 | 
			
		||||
  const { orientation, scrollPrev, canScrollPrev } = useCarousel()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Button
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      variant={variant}
 | 
			
		||||
      size={size}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "absolute  h-8 w-8 rounded-full",
 | 
			
		||||
        orientation === "horizontal"
 | 
			
		||||
          ? "-left-12 top-1/2 -translate-y-1/2"
 | 
			
		||||
          : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      disabled={!canScrollPrev}
 | 
			
		||||
      onClick={scrollPrev}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <ArrowLeft className="h-4 w-4" />
 | 
			
		||||
      <span className="sr-only">Previous slide</span>
 | 
			
		||||
    </Button>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
CarouselPrevious.displayName = "CarouselPrevious"
 | 
			
		||||
 | 
			
		||||
const CarouselNext = React.forwardRef<
 | 
			
		||||
  HTMLButtonElement,
 | 
			
		||||
  React.ComponentProps<typeof Button>
 | 
			
		||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
 | 
			
		||||
  const { orientation, scrollNext, canScrollNext } = useCarousel()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Button
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      variant={variant}
 | 
			
		||||
      size={size}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "absolute h-8 w-8 rounded-full",
 | 
			
		||||
        orientation === "horizontal"
 | 
			
		||||
          ? "-right-12 top-1/2 -translate-y-1/2"
 | 
			
		||||
          : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      disabled={!canScrollNext}
 | 
			
		||||
      onClick={scrollNext}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <ArrowRight className="h-4 w-4" />
 | 
			
		||||
      <span className="sr-only">Next slide</span>
 | 
			
		||||
    </Button>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
CarouselNext.displayName = "CarouselNext"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  type CarouselApi,
 | 
			
		||||
  Carousel,
 | 
			
		||||
  CarouselContent,
 | 
			
		||||
  CarouselItem,
 | 
			
		||||
  CarouselPrevious,
 | 
			
		||||
  CarouselNext,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,363 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as RechartsPrimitive from "recharts"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
// Format: { THEME_NAME: CSS_SELECTOR }
 | 
			
		||||
const THEMES = { light: "", dark: ".dark" } as const
 | 
			
		||||
 | 
			
		||||
export type ChartConfig = {
 | 
			
		||||
  [k in string]: {
 | 
			
		||||
    label?: React.ReactNode
 | 
			
		||||
    icon?: React.ComponentType
 | 
			
		||||
  } & (
 | 
			
		||||
    | { color?: string; theme?: never }
 | 
			
		||||
    | { color?: never; theme: Record<keyof typeof THEMES, string> }
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ChartContextProps = {
 | 
			
		||||
  config: ChartConfig
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ChartContext = React.createContext<ChartContextProps | null>(null)
 | 
			
		||||
 | 
			
		||||
function useChart() {
 | 
			
		||||
  const context = React.useContext(ChartContext)
 | 
			
		||||
 | 
			
		||||
  if (!context) {
 | 
			
		||||
    throw new Error("useChart must be used within a <ChartContainer />")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ChartContainer = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div"> & {
 | 
			
		||||
    config: ChartConfig
 | 
			
		||||
    children: React.ComponentProps<
 | 
			
		||||
      typeof RechartsPrimitive.ResponsiveContainer
 | 
			
		||||
    >["children"]
 | 
			
		||||
  }
 | 
			
		||||
>(({ id, className, children, config, ...props }, ref) => {
 | 
			
		||||
  const uniqueId = React.useId()
 | 
			
		||||
  const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <ChartContext.Provider value={{ config }}>
 | 
			
		||||
      <div
 | 
			
		||||
        data-chart={chartId}
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
 | 
			
		||||
          className
 | 
			
		||||
        )}
 | 
			
		||||
        {...props}
 | 
			
		||||
      >
 | 
			
		||||
        <ChartStyle id={chartId} config={config} />
 | 
			
		||||
        <RechartsPrimitive.ResponsiveContainer>
 | 
			
		||||
          {children}
 | 
			
		||||
        </RechartsPrimitive.ResponsiveContainer>
 | 
			
		||||
      </div>
 | 
			
		||||
    </ChartContext.Provider>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
ChartContainer.displayName = "Chart"
 | 
			
		||||
 | 
			
		||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
 | 
			
		||||
  const colorConfig = Object.entries(config).filter(
 | 
			
		||||
    ([_, config]) => config.theme || config.color
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  if (!colorConfig.length) {
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <style
 | 
			
		||||
      dangerouslySetInnerHTML={{
 | 
			
		||||
        __html: Object.entries(THEMES)
 | 
			
		||||
          .map(
 | 
			
		||||
            ([theme, prefix]) => `
 | 
			
		||||
${prefix} [data-chart=${id}] {
 | 
			
		||||
${colorConfig
 | 
			
		||||
  .map(([key, itemConfig]) => {
 | 
			
		||||
    const color =
 | 
			
		||||
      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
 | 
			
		||||
      itemConfig.color
 | 
			
		||||
    return color ? `  --color-${key}: ${color};` : null
 | 
			
		||||
  })
 | 
			
		||||
  .join("\n")}
 | 
			
		||||
}
 | 
			
		||||
`
 | 
			
		||||
          )
 | 
			
		||||
          .join("\n"),
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ChartTooltip = RechartsPrimitive.Tooltip
 | 
			
		||||
 | 
			
		||||
const ChartTooltipContent = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
 | 
			
		||||
    React.ComponentProps<"div"> & {
 | 
			
		||||
      hideLabel?: boolean
 | 
			
		||||
      hideIndicator?: boolean
 | 
			
		||||
      indicator?: "line" | "dot" | "dashed"
 | 
			
		||||
      nameKey?: string
 | 
			
		||||
      labelKey?: string
 | 
			
		||||
    }
 | 
			
		||||
>(
 | 
			
		||||
  (
 | 
			
		||||
    {
 | 
			
		||||
      active,
 | 
			
		||||
      payload,
 | 
			
		||||
      className,
 | 
			
		||||
      indicator = "dot",
 | 
			
		||||
      hideLabel = false,
 | 
			
		||||
      hideIndicator = false,
 | 
			
		||||
      label,
 | 
			
		||||
      labelFormatter,
 | 
			
		||||
      labelClassName,
 | 
			
		||||
      formatter,
 | 
			
		||||
      color,
 | 
			
		||||
      nameKey,
 | 
			
		||||
      labelKey,
 | 
			
		||||
    },
 | 
			
		||||
    ref
 | 
			
		||||
  ) => {
 | 
			
		||||
    const { config } = useChart()
 | 
			
		||||
 | 
			
		||||
    const tooltipLabel = React.useMemo(() => {
 | 
			
		||||
      if (hideLabel || !payload?.length) {
 | 
			
		||||
        return null
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const [item] = payload
 | 
			
		||||
      const key = `${labelKey || item.dataKey || item.name || "value"}`
 | 
			
		||||
      const itemConfig = getPayloadConfigFromPayload(config, item, key)
 | 
			
		||||
      const value =
 | 
			
		||||
        !labelKey && typeof label === "string"
 | 
			
		||||
          ? config[label as keyof typeof config]?.label || label
 | 
			
		||||
          : itemConfig?.label
 | 
			
		||||
 | 
			
		||||
      if (labelFormatter) {
 | 
			
		||||
        return (
 | 
			
		||||
          <div className={cn("font-medium", labelClassName)}>
 | 
			
		||||
            {labelFormatter(value, payload)}
 | 
			
		||||
          </div>
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!value) {
 | 
			
		||||
        return null
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return <div className={cn("font-medium", labelClassName)}>{value}</div>
 | 
			
		||||
    }, [
 | 
			
		||||
      label,
 | 
			
		||||
      labelFormatter,
 | 
			
		||||
      payload,
 | 
			
		||||
      hideLabel,
 | 
			
		||||
      labelClassName,
 | 
			
		||||
      config,
 | 
			
		||||
      labelKey,
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
    if (!active || !payload?.length) {
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const nestLabel = payload.length === 1 && indicator !== "dot"
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
 | 
			
		||||
          className
 | 
			
		||||
        )}
 | 
			
		||||
      >
 | 
			
		||||
        {!nestLabel ? tooltipLabel : null}
 | 
			
		||||
        <div className="grid gap-1.5">
 | 
			
		||||
          {payload.map((item, index) => {
 | 
			
		||||
            const key = `${nameKey || item.name || item.dataKey || "value"}`
 | 
			
		||||
            const itemConfig = getPayloadConfigFromPayload(config, item, key)
 | 
			
		||||
            const indicatorColor = color || item.payload.fill || item.color
 | 
			
		||||
 | 
			
		||||
            return (
 | 
			
		||||
              <div
 | 
			
		||||
                key={item.dataKey}
 | 
			
		||||
                className={cn(
 | 
			
		||||
                  "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
 | 
			
		||||
                  indicator === "dot" && "items-center"
 | 
			
		||||
                )}
 | 
			
		||||
              >
 | 
			
		||||
                {formatter && item?.value !== undefined && item.name ? (
 | 
			
		||||
                  formatter(item.value, item.name, item, index, item.payload)
 | 
			
		||||
                ) : (
 | 
			
		||||
                  <>
 | 
			
		||||
                    {itemConfig?.icon ? (
 | 
			
		||||
                      <itemConfig.icon />
 | 
			
		||||
                    ) : (
 | 
			
		||||
                      !hideIndicator && (
 | 
			
		||||
                        <div
 | 
			
		||||
                          className={cn(
 | 
			
		||||
                            "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
 | 
			
		||||
                            {
 | 
			
		||||
                              "h-2.5 w-2.5": indicator === "dot",
 | 
			
		||||
                              "w-1": indicator === "line",
 | 
			
		||||
                              "w-0 border-[1.5px] border-dashed bg-transparent":
 | 
			
		||||
                                indicator === "dashed",
 | 
			
		||||
                              "my-0.5": nestLabel && indicator === "dashed",
 | 
			
		||||
                            }
 | 
			
		||||
                          )}
 | 
			
		||||
                          style={
 | 
			
		||||
                            {
 | 
			
		||||
                              "--color-bg": indicatorColor,
 | 
			
		||||
                              "--color-border": indicatorColor,
 | 
			
		||||
                            } as React.CSSProperties
 | 
			
		||||
                          }
 | 
			
		||||
                        />
 | 
			
		||||
                      )
 | 
			
		||||
                    )}
 | 
			
		||||
                    <div
 | 
			
		||||
                      className={cn(
 | 
			
		||||
                        "flex flex-1 justify-between leading-none",
 | 
			
		||||
                        nestLabel ? "items-end" : "items-center"
 | 
			
		||||
                      )}
 | 
			
		||||
                    >
 | 
			
		||||
                      <div className="grid gap-1.5">
 | 
			
		||||
                        {nestLabel ? tooltipLabel : null}
 | 
			
		||||
                        <span className="text-muted-foreground">
 | 
			
		||||
                          {itemConfig?.label || item.name}
 | 
			
		||||
                        </span>
 | 
			
		||||
                      </div>
 | 
			
		||||
                      {item.value && (
 | 
			
		||||
                        <span className="font-mono font-medium tabular-nums text-foreground">
 | 
			
		||||
                          {item.value.toLocaleString()}
 | 
			
		||||
                        </span>
 | 
			
		||||
                      )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </>
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            )
 | 
			
		||||
          })}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
ChartTooltipContent.displayName = "ChartTooltip"
 | 
			
		||||
 | 
			
		||||
const ChartLegend = RechartsPrimitive.Legend
 | 
			
		||||
 | 
			
		||||
const ChartLegendContent = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div"> &
 | 
			
		||||
    Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
 | 
			
		||||
      hideIcon?: boolean
 | 
			
		||||
      nameKey?: string
 | 
			
		||||
    }
 | 
			
		||||
>(
 | 
			
		||||
  (
 | 
			
		||||
    { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
 | 
			
		||||
    ref
 | 
			
		||||
  ) => {
 | 
			
		||||
    const { config } = useChart()
 | 
			
		||||
 | 
			
		||||
    if (!payload?.length) {
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "flex items-center justify-center gap-4",
 | 
			
		||||
          verticalAlign === "top" ? "pb-3" : "pt-3",
 | 
			
		||||
          className
 | 
			
		||||
        )}
 | 
			
		||||
      >
 | 
			
		||||
        {payload.map((item) => {
 | 
			
		||||
          const key = `${nameKey || item.dataKey || "value"}`
 | 
			
		||||
          const itemConfig = getPayloadConfigFromPayload(config, item, key)
 | 
			
		||||
 | 
			
		||||
          return (
 | 
			
		||||
            <div
 | 
			
		||||
              key={item.value}
 | 
			
		||||
              className={cn(
 | 
			
		||||
                "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
 | 
			
		||||
              )}
 | 
			
		||||
            >
 | 
			
		||||
              {itemConfig?.icon && !hideIcon ? (
 | 
			
		||||
                <itemConfig.icon />
 | 
			
		||||
              ) : (
 | 
			
		||||
                <div
 | 
			
		||||
                  className="h-2 w-2 shrink-0 rounded-[2px]"
 | 
			
		||||
                  style={{
 | 
			
		||||
                    backgroundColor: item.color,
 | 
			
		||||
                  }}
 | 
			
		||||
                />
 | 
			
		||||
              )}
 | 
			
		||||
              {itemConfig?.label}
 | 
			
		||||
            </div>
 | 
			
		||||
          )
 | 
			
		||||
        })}
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
ChartLegendContent.displayName = "ChartLegend"
 | 
			
		||||
 | 
			
		||||
// Helper to extract item config from a payload.
 | 
			
		||||
function getPayloadConfigFromPayload(
 | 
			
		||||
  config: ChartConfig,
 | 
			
		||||
  payload: unknown,
 | 
			
		||||
  key: string
 | 
			
		||||
) {
 | 
			
		||||
  if (typeof payload !== "object" || payload === null) {
 | 
			
		||||
    return undefined
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const payloadPayload =
 | 
			
		||||
    "payload" in payload &&
 | 
			
		||||
    typeof payload.payload === "object" &&
 | 
			
		||||
    payload.payload !== null
 | 
			
		||||
      ? payload.payload
 | 
			
		||||
      : undefined
 | 
			
		||||
 | 
			
		||||
  let configLabelKey: string = key
 | 
			
		||||
 | 
			
		||||
  if (
 | 
			
		||||
    key in payload &&
 | 
			
		||||
    typeof payload[key as keyof typeof payload] === "string"
 | 
			
		||||
  ) {
 | 
			
		||||
    configLabelKey = payload[key as keyof typeof payload] as string
 | 
			
		||||
  } else if (
 | 
			
		||||
    payloadPayload &&
 | 
			
		||||
    key in payloadPayload &&
 | 
			
		||||
    typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
 | 
			
		||||
  ) {
 | 
			
		||||
    configLabelKey = payloadPayload[
 | 
			
		||||
      key as keyof typeof payloadPayload
 | 
			
		||||
    ] as string
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return configLabelKey in config
 | 
			
		||||
    ? config[configLabelKey]
 | 
			
		||||
    : config[key as keyof typeof config]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  ChartContainer,
 | 
			
		||||
  ChartTooltip,
 | 
			
		||||
  ChartTooltipContent,
 | 
			
		||||
  ChartLegend,
 | 
			
		||||
  ChartLegendContent,
 | 
			
		||||
  ChartStyle,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
 | 
			
		||||
import { Check } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Checkbox = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof CheckboxPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <CheckboxPrimitive.Root
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <CheckboxPrimitive.Indicator
 | 
			
		||||
      className={cn("flex items-center justify-center text-current")}
 | 
			
		||||
    >
 | 
			
		||||
      <Check className="h-4 w-4" />
 | 
			
		||||
    </CheckboxPrimitive.Indicator>
 | 
			
		||||
  </CheckboxPrimitive.Root>
 | 
			
		||||
))
 | 
			
		||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
export { Checkbox }
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
 | 
			
		||||
 | 
			
		||||
const Collapsible = CollapsiblePrimitive.Root
 | 
			
		||||
 | 
			
		||||
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
 | 
			
		||||
 | 
			
		||||
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
 | 
			
		||||
 | 
			
		||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
 | 
			
		||||
@@ -1,153 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import { type DialogProps } from "@radix-ui/react-dialog"
 | 
			
		||||
import { Command as CommandPrimitive } from "cmdk"
 | 
			
		||||
import { Search } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
import { Dialog, DialogContent } from "@/components/ui/dialog"
 | 
			
		||||
 | 
			
		||||
const Command = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof CommandPrimitive>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof CommandPrimitive>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <CommandPrimitive
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
Command.displayName = CommandPrimitive.displayName
 | 
			
		||||
 | 
			
		||||
interface CommandDialogProps extends DialogProps {}
 | 
			
		||||
 | 
			
		||||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <Dialog {...props}>
 | 
			
		||||
      <DialogContent className="overflow-hidden p-0 shadow-lg">
 | 
			
		||||
        <Command 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">
 | 
			
		||||
          {children}
 | 
			
		||||
        </Command>
 | 
			
		||||
      </DialogContent>
 | 
			
		||||
    </Dialog>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const CommandInput = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof CommandPrimitive.Input>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
 | 
			
		||||
    <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
 | 
			
		||||
    <CommandPrimitive.Input
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
CommandInput.displayName = CommandPrimitive.Input.displayName
 | 
			
		||||
 | 
			
		||||
const CommandList = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof CommandPrimitive.List>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <CommandPrimitive.List
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
CommandList.displayName = CommandPrimitive.List.displayName
 | 
			
		||||
 | 
			
		||||
const CommandEmpty = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof CommandPrimitive.Empty>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
 | 
			
		||||
>((props, ref) => (
 | 
			
		||||
  <CommandPrimitive.Empty
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className="py-6 text-center text-sm"
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
 | 
			
		||||
 | 
			
		||||
const CommandGroup = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof CommandPrimitive.Group>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <CommandPrimitive.Group
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
CommandGroup.displayName = CommandPrimitive.Group.displayName
 | 
			
		||||
 | 
			
		||||
const CommandSeparator = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof CommandPrimitive.Separator>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <CommandPrimitive.Separator
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("-mx-1 h-px bg-border", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
 | 
			
		||||
 | 
			
		||||
const CommandItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof CommandPrimitive.Item>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <CommandPrimitive.Item
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "relative flex cursor-default 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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
CommandItem.displayName = CommandPrimitive.Item.displayName
 | 
			
		||||
 | 
			
		||||
const CommandShortcut = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <span
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "ml-auto text-xs tracking-widest text-muted-foreground",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
CommandShortcut.displayName = "CommandShortcut"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Command,
 | 
			
		||||
  CommandDialog,
 | 
			
		||||
  CommandInput,
 | 
			
		||||
  CommandList,
 | 
			
		||||
  CommandEmpty,
 | 
			
		||||
  CommandGroup,
 | 
			
		||||
  CommandItem,
 | 
			
		||||
  CommandShortcut,
 | 
			
		||||
  CommandSeparator,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,198 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
 | 
			
		||||
import { Check, ChevronRight, Circle } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const ContextMenu = ContextMenuPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
 | 
			
		||||
 | 
			
		||||
const ContextMenuGroup = ContextMenuPrimitive.Group
 | 
			
		||||
 | 
			
		||||
const ContextMenuPortal = ContextMenuPrimitive.Portal
 | 
			
		||||
 | 
			
		||||
const ContextMenuSub = ContextMenuPrimitive.Sub
 | 
			
		||||
 | 
			
		||||
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
 | 
			
		||||
 | 
			
		||||
const ContextMenuSubTrigger = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
 | 
			
		||||
    inset?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, inset, children, ...props }, ref) => (
 | 
			
		||||
  <ContextMenuPrimitive.SubTrigger
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      inset && "pl-8",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    {children}
 | 
			
		||||
    <ChevronRight className="ml-auto h-4 w-4" />
 | 
			
		||||
  </ContextMenuPrimitive.SubTrigger>
 | 
			
		||||
))
 | 
			
		||||
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
 | 
			
		||||
 | 
			
		||||
const ContextMenuSubContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ContextMenuPrimitive.SubContent
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "z-50 min-w-[8rem] overflow-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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
 | 
			
		||||
 | 
			
		||||
const ContextMenuContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ContextMenuPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ContextMenuPrimitive.Portal>
 | 
			
		||||
    <ContextMenuPrimitive.Content
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 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",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  </ContextMenuPrimitive.Portal>
 | 
			
		||||
))
 | 
			
		||||
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
const ContextMenuItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ContextMenuPrimitive.Item>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
 | 
			
		||||
    inset?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, inset, ...props }, ref) => (
 | 
			
		||||
  <ContextMenuPrimitive.Item
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      inset && "pl-8",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
 | 
			
		||||
 | 
			
		||||
const ContextMenuCheckboxItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
 | 
			
		||||
>(({ className, children, checked, ...props }, ref) => (
 | 
			
		||||
  <ContextMenuPrimitive.CheckboxItem
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    checked={checked}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
 | 
			
		||||
      <ContextMenuPrimitive.ItemIndicator>
 | 
			
		||||
        <Check className="h-4 w-4" />
 | 
			
		||||
      </ContextMenuPrimitive.ItemIndicator>
 | 
			
		||||
    </span>
 | 
			
		||||
    {children}
 | 
			
		||||
  </ContextMenuPrimitive.CheckboxItem>
 | 
			
		||||
))
 | 
			
		||||
ContextMenuCheckboxItem.displayName =
 | 
			
		||||
  ContextMenuPrimitive.CheckboxItem.displayName
 | 
			
		||||
 | 
			
		||||
const ContextMenuRadioItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <ContextMenuPrimitive.RadioItem
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
 | 
			
		||||
      <ContextMenuPrimitive.ItemIndicator>
 | 
			
		||||
        <Circle className="h-2 w-2 fill-current" />
 | 
			
		||||
      </ContextMenuPrimitive.ItemIndicator>
 | 
			
		||||
    </span>
 | 
			
		||||
    {children}
 | 
			
		||||
  </ContextMenuPrimitive.RadioItem>
 | 
			
		||||
))
 | 
			
		||||
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
 | 
			
		||||
 | 
			
		||||
const ContextMenuLabel = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ContextMenuPrimitive.Label>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
 | 
			
		||||
    inset?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, inset, ...props }, ref) => (
 | 
			
		||||
  <ContextMenuPrimitive.Label
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "px-2 py-1.5 text-sm font-semibold text-foreground",
 | 
			
		||||
      inset && "pl-8",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
 | 
			
		||||
 | 
			
		||||
const ContextMenuSeparator = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ContextMenuPrimitive.Separator>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ContextMenuPrimitive.Separator
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("-mx-1 my-1 h-px bg-border", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
 | 
			
		||||
 | 
			
		||||
const ContextMenuShortcut = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <span
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "ml-auto text-xs tracking-widest text-muted-foreground",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
ContextMenuShortcut.displayName = "ContextMenuShortcut"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  ContextMenu,
 | 
			
		||||
  ContextMenuTrigger,
 | 
			
		||||
  ContextMenuContent,
 | 
			
		||||
  ContextMenuItem,
 | 
			
		||||
  ContextMenuCheckboxItem,
 | 
			
		||||
  ContextMenuRadioItem,
 | 
			
		||||
  ContextMenuLabel,
 | 
			
		||||
  ContextMenuSeparator,
 | 
			
		||||
  ContextMenuShortcut,
 | 
			
		||||
  ContextMenuGroup,
 | 
			
		||||
  ContextMenuPortal,
 | 
			
		||||
  ContextMenuSub,
 | 
			
		||||
  ContextMenuSubContent,
 | 
			
		||||
  ContextMenuSubTrigger,
 | 
			
		||||
  ContextMenuRadioGroup,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,120 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
 | 
			
		||||
import { X } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Dialog = DialogPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const DialogTrigger = DialogPrimitive.Trigger
 | 
			
		||||
 | 
			
		||||
const DialogPortal = DialogPrimitive.Portal
 | 
			
		||||
 | 
			
		||||
const DialogClose = DialogPrimitive.Close
 | 
			
		||||
 | 
			
		||||
const DialogOverlay = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DialogPrimitive.Overlay>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <DialogPrimitive.Overlay
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
 | 
			
		||||
 | 
			
		||||
const DialogContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DialogPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <DialogPortal>
 | 
			
		||||
    <DialogOverlay />
 | 
			
		||||
    <DialogPrimitive.Content
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "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",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      {children}
 | 
			
		||||
      <DialogPrimitive.Close 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">
 | 
			
		||||
        <X className="h-4 w-4" />
 | 
			
		||||
        <span className="sr-only">Close</span>
 | 
			
		||||
      </DialogPrimitive.Close>
 | 
			
		||||
    </DialogPrimitive.Content>
 | 
			
		||||
  </DialogPortal>
 | 
			
		||||
))
 | 
			
		||||
DialogContent.displayName = DialogPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
const DialogHeader = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
			
		||||
  <div
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex flex-col space-y-1.5 text-center sm:text-left",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
DialogHeader.displayName = "DialogHeader"
 | 
			
		||||
 | 
			
		||||
const DialogFooter = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
			
		||||
  <div
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
DialogFooter.displayName = "DialogFooter"
 | 
			
		||||
 | 
			
		||||
const DialogTitle = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DialogPrimitive.Title>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <DialogPrimitive.Title
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "text-lg font-semibold leading-none tracking-tight",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
 | 
			
		||||
 | 
			
		||||
const DialogDescription = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DialogPrimitive.Description>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <DialogPrimitive.Description
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("text-sm text-muted-foreground", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Dialog,
 | 
			
		||||
  DialogPortal,
 | 
			
		||||
  DialogOverlay,
 | 
			
		||||
  DialogClose,
 | 
			
		||||
  DialogTrigger,
 | 
			
		||||
  DialogContent,
 | 
			
		||||
  DialogHeader,
 | 
			
		||||
  DialogFooter,
 | 
			
		||||
  DialogTitle,
 | 
			
		||||
  DialogDescription,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,116 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import { Drawer as DrawerPrimitive } from "vaul"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Drawer = ({
 | 
			
		||||
  shouldScaleBackground = true,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
 | 
			
		||||
  <DrawerPrimitive.Root
 | 
			
		||||
    shouldScaleBackground={shouldScaleBackground}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
Drawer.displayName = "Drawer"
 | 
			
		||||
 | 
			
		||||
const DrawerTrigger = DrawerPrimitive.Trigger
 | 
			
		||||
 | 
			
		||||
const DrawerPortal = DrawerPrimitive.Portal
 | 
			
		||||
 | 
			
		||||
const DrawerClose = DrawerPrimitive.Close
 | 
			
		||||
 | 
			
		||||
const DrawerOverlay = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DrawerPrimitive.Overlay>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <DrawerPrimitive.Overlay
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("fixed inset-0 z-50 bg-black/80", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
 | 
			
		||||
 | 
			
		||||
const DrawerContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DrawerPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <DrawerPortal>
 | 
			
		||||
    <DrawerOverlay />
 | 
			
		||||
    <DrawerPrimitive.Content
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
 | 
			
		||||
      {children}
 | 
			
		||||
    </DrawerPrimitive.Content>
 | 
			
		||||
  </DrawerPortal>
 | 
			
		||||
))
 | 
			
		||||
DrawerContent.displayName = "DrawerContent"
 | 
			
		||||
 | 
			
		||||
const DrawerHeader = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
			
		||||
  <div
 | 
			
		||||
    className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
DrawerHeader.displayName = "DrawerHeader"
 | 
			
		||||
 | 
			
		||||
const DrawerFooter = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
			
		||||
  <div
 | 
			
		||||
    className={cn("mt-auto flex flex-col gap-2 p-4", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
DrawerFooter.displayName = "DrawerFooter"
 | 
			
		||||
 | 
			
		||||
const DrawerTitle = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DrawerPrimitive.Title>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <DrawerPrimitive.Title
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "text-lg font-semibold leading-none tracking-tight",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
 | 
			
		||||
 | 
			
		||||
const DrawerDescription = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DrawerPrimitive.Description>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <DrawerPrimitive.Description
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("text-sm text-muted-foreground", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Drawer,
 | 
			
		||||
  DrawerPortal,
 | 
			
		||||
  DrawerOverlay,
 | 
			
		||||
  DrawerTrigger,
 | 
			
		||||
  DrawerClose,
 | 
			
		||||
  DrawerContent,
 | 
			
		||||
  DrawerHeader,
 | 
			
		||||
  DrawerFooter,
 | 
			
		||||
  DrawerTitle,
 | 
			
		||||
  DrawerDescription,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,198 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
 | 
			
		||||
import { Check, ChevronRight, Circle } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const DropdownMenu = DropdownMenuPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
 | 
			
		||||
 | 
			
		||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
 | 
			
		||||
 | 
			
		||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
 | 
			
		||||
 | 
			
		||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
 | 
			
		||||
 | 
			
		||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
 | 
			
		||||
 | 
			
		||||
const DropdownMenuSubTrigger = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
 | 
			
		||||
    inset?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, inset, children, ...props }, ref) => (
 | 
			
		||||
  <DropdownMenuPrimitive.SubTrigger
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
 | 
			
		||||
      inset && "pl-8",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    {children}
 | 
			
		||||
    <ChevronRight className="ml-auto h-4 w-4" />
 | 
			
		||||
  </DropdownMenuPrimitive.SubTrigger>
 | 
			
		||||
))
 | 
			
		||||
DropdownMenuSubTrigger.displayName =
 | 
			
		||||
  DropdownMenuPrimitive.SubTrigger.displayName
 | 
			
		||||
 | 
			
		||||
const DropdownMenuSubContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <DropdownMenuPrimitive.SubContent
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
DropdownMenuSubContent.displayName =
 | 
			
		||||
  DropdownMenuPrimitive.SubContent.displayName
 | 
			
		||||
 | 
			
		||||
const DropdownMenuContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DropdownMenuPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
 | 
			
		||||
>(({ className, sideOffset = 4, ...props }, ref) => (
 | 
			
		||||
  <DropdownMenuPrimitive.Portal>
 | 
			
		||||
    <DropdownMenuPrimitive.Content
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      sideOffset={sideOffset}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "z-50 min-w-[8rem] overflow-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",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  </DropdownMenuPrimitive.Portal>
 | 
			
		||||
))
 | 
			
		||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
const DropdownMenuItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DropdownMenuPrimitive.Item>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
 | 
			
		||||
    inset?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, inset, ...props }, ref) => (
 | 
			
		||||
  <DropdownMenuPrimitive.Item
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "relative flex cursor-default select-none items-center 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",
 | 
			
		||||
      inset && "pl-8",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
 | 
			
		||||
 | 
			
		||||
const DropdownMenuCheckboxItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
 | 
			
		||||
>(({ className, children, checked, ...props }, ref) => (
 | 
			
		||||
  <DropdownMenuPrimitive.CheckboxItem
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    checked={checked}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
 | 
			
		||||
      <DropdownMenuPrimitive.ItemIndicator>
 | 
			
		||||
        <Check className="h-4 w-4" />
 | 
			
		||||
      </DropdownMenuPrimitive.ItemIndicator>
 | 
			
		||||
    </span>
 | 
			
		||||
    {children}
 | 
			
		||||
  </DropdownMenuPrimitive.CheckboxItem>
 | 
			
		||||
))
 | 
			
		||||
DropdownMenuCheckboxItem.displayName =
 | 
			
		||||
  DropdownMenuPrimitive.CheckboxItem.displayName
 | 
			
		||||
 | 
			
		||||
const DropdownMenuRadioItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <DropdownMenuPrimitive.RadioItem
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
 | 
			
		||||
      <DropdownMenuPrimitive.ItemIndicator>
 | 
			
		||||
        <Circle className="h-2 w-2 fill-current" />
 | 
			
		||||
      </DropdownMenuPrimitive.ItemIndicator>
 | 
			
		||||
    </span>
 | 
			
		||||
    {children}
 | 
			
		||||
  </DropdownMenuPrimitive.RadioItem>
 | 
			
		||||
))
 | 
			
		||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
 | 
			
		||||
 | 
			
		||||
const DropdownMenuLabel = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DropdownMenuPrimitive.Label>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
 | 
			
		||||
    inset?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, inset, ...props }, ref) => (
 | 
			
		||||
  <DropdownMenuPrimitive.Label
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "px-2 py-1.5 text-sm font-semibold",
 | 
			
		||||
      inset && "pl-8",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
 | 
			
		||||
 | 
			
		||||
const DropdownMenuSeparator = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <DropdownMenuPrimitive.Separator
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("-mx-1 my-1 h-px bg-muted", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
 | 
			
		||||
 | 
			
		||||
const DropdownMenuShortcut = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <span
 | 
			
		||||
      className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  DropdownMenu,
 | 
			
		||||
  DropdownMenuTrigger,
 | 
			
		||||
  DropdownMenuContent,
 | 
			
		||||
  DropdownMenuItem,
 | 
			
		||||
  DropdownMenuCheckboxItem,
 | 
			
		||||
  DropdownMenuRadioItem,
 | 
			
		||||
  DropdownMenuLabel,
 | 
			
		||||
  DropdownMenuSeparator,
 | 
			
		||||
  DropdownMenuShortcut,
 | 
			
		||||
  DropdownMenuGroup,
 | 
			
		||||
  DropdownMenuPortal,
 | 
			
		||||
  DropdownMenuSub,
 | 
			
		||||
  DropdownMenuSubContent,
 | 
			
		||||
  DropdownMenuSubTrigger,
 | 
			
		||||
  DropdownMenuRadioGroup,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,176 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as LabelPrimitive from "@radix-ui/react-label"
 | 
			
		||||
import { Slot } from "@radix-ui/react-slot"
 | 
			
		||||
import {
 | 
			
		||||
  Controller,
 | 
			
		||||
  ControllerProps,
 | 
			
		||||
  FieldPath,
 | 
			
		||||
  FieldValues,
 | 
			
		||||
  FormProvider,
 | 
			
		||||
  useFormContext,
 | 
			
		||||
} from "react-hook-form"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
import { Label } from "@/components/ui/label"
 | 
			
		||||
 | 
			
		||||
const Form = FormProvider
 | 
			
		||||
 | 
			
		||||
type FormFieldContextValue<
 | 
			
		||||
  TFieldValues extends FieldValues = FieldValues,
 | 
			
		||||
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
 | 
			
		||||
> = {
 | 
			
		||||
  name: TName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
 | 
			
		||||
  {} as FormFieldContextValue
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const FormField = <
 | 
			
		||||
  TFieldValues extends FieldValues = FieldValues,
 | 
			
		||||
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
 | 
			
		||||
>({
 | 
			
		||||
  ...props
 | 
			
		||||
}: ControllerProps<TFieldValues, TName>) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <FormFieldContext.Provider value={{ name: props.name }}>
 | 
			
		||||
      <Controller {...props} />
 | 
			
		||||
    </FormFieldContext.Provider>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const useFormField = () => {
 | 
			
		||||
  const fieldContext = React.useContext(FormFieldContext)
 | 
			
		||||
  const itemContext = React.useContext(FormItemContext)
 | 
			
		||||
  const { getFieldState, formState } = useFormContext()
 | 
			
		||||
 | 
			
		||||
  const fieldState = getFieldState(fieldContext.name, formState)
 | 
			
		||||
 | 
			
		||||
  if (!fieldContext) {
 | 
			
		||||
    throw new Error("useFormField should be used within <FormField>")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { id } = itemContext
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    id,
 | 
			
		||||
    name: fieldContext.name,
 | 
			
		||||
    formItemId: `${id}-form-item`,
 | 
			
		||||
    formDescriptionId: `${id}-form-item-description`,
 | 
			
		||||
    formMessageId: `${id}-form-item-message`,
 | 
			
		||||
    ...fieldState,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FormItemContextValue = {
 | 
			
		||||
  id: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const FormItemContext = React.createContext<FormItemContextValue>(
 | 
			
		||||
  {} as FormItemContextValue
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const FormItem = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLDivElement>
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  const id = React.useId()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <FormItemContext.Provider value={{ id }}>
 | 
			
		||||
      <div ref={ref} className={cn("space-y-2", className)} {...props} />
 | 
			
		||||
    </FormItemContext.Provider>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
FormItem.displayName = "FormItem"
 | 
			
		||||
 | 
			
		||||
const FormLabel = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof LabelPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  const { error, formItemId } = useFormField()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Label
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(error && "text-destructive", className)}
 | 
			
		||||
      htmlFor={formItemId}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
FormLabel.displayName = "FormLabel"
 | 
			
		||||
 | 
			
		||||
const FormControl = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof Slot>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof Slot>
 | 
			
		||||
>(({ ...props }, ref) => {
 | 
			
		||||
  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Slot
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      id={formItemId}
 | 
			
		||||
      aria-describedby={
 | 
			
		||||
        !error
 | 
			
		||||
          ? `${formDescriptionId}`
 | 
			
		||||
          : `${formDescriptionId} ${formMessageId}`
 | 
			
		||||
      }
 | 
			
		||||
      aria-invalid={!!error}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
FormControl.displayName = "FormControl"
 | 
			
		||||
 | 
			
		||||
const FormDescription = React.forwardRef<
 | 
			
		||||
  HTMLParagraphElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLParagraphElement>
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  const { formDescriptionId } = useFormField()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <p
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      id={formDescriptionId}
 | 
			
		||||
      className={cn("text-sm text-muted-foreground", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
FormDescription.displayName = "FormDescription"
 | 
			
		||||
 | 
			
		||||
const FormMessage = React.forwardRef<
 | 
			
		||||
  HTMLParagraphElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLParagraphElement>
 | 
			
		||||
>(({ className, children, ...props }, ref) => {
 | 
			
		||||
  const { error, formMessageId } = useFormField()
 | 
			
		||||
  const body = error ? String(error?.message) : children
 | 
			
		||||
 | 
			
		||||
  if (!body) {
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <p
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      id={formMessageId}
 | 
			
		||||
      className={cn("text-sm font-medium text-destructive", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      {body}
 | 
			
		||||
    </p>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
FormMessage.displayName = "FormMessage"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  useFormField,
 | 
			
		||||
  Form,
 | 
			
		||||
  FormItem,
 | 
			
		||||
  FormLabel,
 | 
			
		||||
  FormControl,
 | 
			
		||||
  FormDescription,
 | 
			
		||||
  FormMessage,
 | 
			
		||||
  FormField,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const HoverCard = HoverCardPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const HoverCardTrigger = HoverCardPrimitive.Trigger
 | 
			
		||||
 | 
			
		||||
const HoverCardContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof HoverCardPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
 | 
			
		||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
 | 
			
		||||
  <HoverCardPrimitive.Content
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    align={align}
 | 
			
		||||
    sideOffset={sideOffset}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
export { HoverCard, HoverCardTrigger, HoverCardContent }
 | 
			
		||||
@@ -1,69 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import { OTPInput, OTPInputContext } from "input-otp"
 | 
			
		||||
import { Dot } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const InputOTP = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof OTPInput>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof OTPInput>
 | 
			
		||||
>(({ className, containerClassName, ...props }, ref) => (
 | 
			
		||||
  <OTPInput
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    containerClassName={cn(
 | 
			
		||||
      "flex items-center gap-2 has-[:disabled]:opacity-50",
 | 
			
		||||
      containerClassName
 | 
			
		||||
    )}
 | 
			
		||||
    className={cn("disabled:cursor-not-allowed", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
InputOTP.displayName = "InputOTP"
 | 
			
		||||
 | 
			
		||||
const InputOTPGroup = React.forwardRef<
 | 
			
		||||
  React.ElementRef<"div">,
 | 
			
		||||
  React.ComponentPropsWithoutRef<"div">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div ref={ref} className={cn("flex items-center", className)} {...props} />
 | 
			
		||||
))
 | 
			
		||||
InputOTPGroup.displayName = "InputOTPGroup"
 | 
			
		||||
 | 
			
		||||
const InputOTPSlot = React.forwardRef<
 | 
			
		||||
  React.ElementRef<"div">,
 | 
			
		||||
  React.ComponentPropsWithoutRef<"div"> & { index: number }
 | 
			
		||||
>(({ index, className, ...props }, ref) => {
 | 
			
		||||
  const inputOTPContext = React.useContext(OTPInputContext)
 | 
			
		||||
  const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
 | 
			
		||||
        isActive && "z-10 ring-2 ring-ring ring-offset-background",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      {char}
 | 
			
		||||
      {hasFakeCaret && (
 | 
			
		||||
        <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
 | 
			
		||||
          <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
InputOTPSlot.displayName = "InputOTPSlot"
 | 
			
		||||
 | 
			
		||||
const InputOTPSeparator = React.forwardRef<
 | 
			
		||||
  React.ElementRef<"div">,
 | 
			
		||||
  React.ComponentPropsWithoutRef<"div">
 | 
			
		||||
>(({ ...props }, ref) => (
 | 
			
		||||
  <div ref={ref} role="separator" {...props}>
 | 
			
		||||
    <Dot />
 | 
			
		||||
  </div>
 | 
			
		||||
))
 | 
			
		||||
InputOTPSeparator.displayName = "InputOTPSeparator"
 | 
			
		||||
 | 
			
		||||
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
 | 
			
		||||
  ({ className, type, ...props }, ref) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <input
 | 
			
		||||
        type={type}
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background 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-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
 | 
			
		||||
          className
 | 
			
		||||
        )}
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        {...props}
 | 
			
		||||
      />
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
Input.displayName = "Input"
 | 
			
		||||
 | 
			
		||||
export { Input }
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as LabelPrimitive from "@radix-ui/react-label"
 | 
			
		||||
import { cva, type VariantProps } from "class-variance-authority"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const labelVariants = cva(
 | 
			
		||||
  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const Label = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof LabelPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
 | 
			
		||||
    VariantProps<typeof labelVariants>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <LabelPrimitive.Root
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(labelVariants(), className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
Label.displayName = LabelPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
export { Label }
 | 
			
		||||
@@ -1,234 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as MenubarPrimitive from "@radix-ui/react-menubar"
 | 
			
		||||
import { Check, ChevronRight, Circle } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const MenubarMenu = MenubarPrimitive.Menu
 | 
			
		||||
 | 
			
		||||
const MenubarGroup = MenubarPrimitive.Group
 | 
			
		||||
 | 
			
		||||
const MenubarPortal = MenubarPrimitive.Portal
 | 
			
		||||
 | 
			
		||||
const MenubarSub = MenubarPrimitive.Sub
 | 
			
		||||
 | 
			
		||||
const MenubarRadioGroup = MenubarPrimitive.RadioGroup
 | 
			
		||||
 | 
			
		||||
const Menubar = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof MenubarPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <MenubarPrimitive.Root
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
Menubar.displayName = MenubarPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
const MenubarTrigger = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof MenubarPrimitive.Trigger>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <MenubarPrimitive.Trigger
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
 | 
			
		||||
 | 
			
		||||
const MenubarSubTrigger = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
 | 
			
		||||
    inset?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, inset, children, ...props }, ref) => (
 | 
			
		||||
  <MenubarPrimitive.SubTrigger
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      inset && "pl-8",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    {children}
 | 
			
		||||
    <ChevronRight className="ml-auto h-4 w-4" />
 | 
			
		||||
  </MenubarPrimitive.SubTrigger>
 | 
			
		||||
))
 | 
			
		||||
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
 | 
			
		||||
 | 
			
		||||
const MenubarSubContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof MenubarPrimitive.SubContent>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <MenubarPrimitive.SubContent
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground 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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
 | 
			
		||||
 | 
			
		||||
const MenubarContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof MenubarPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
 | 
			
		||||
>(
 | 
			
		||||
  (
 | 
			
		||||
    { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
 | 
			
		||||
    ref
 | 
			
		||||
  ) => (
 | 
			
		||||
    <MenubarPrimitive.Portal>
 | 
			
		||||
      <MenubarPrimitive.Content
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        align={align}
 | 
			
		||||
        alignOffset={alignOffset}
 | 
			
		||||
        sideOffset={sideOffset}
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "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",
 | 
			
		||||
          className
 | 
			
		||||
        )}
 | 
			
		||||
        {...props}
 | 
			
		||||
      />
 | 
			
		||||
    </MenubarPrimitive.Portal>
 | 
			
		||||
  )
 | 
			
		||||
)
 | 
			
		||||
MenubarContent.displayName = MenubarPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
const MenubarItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof MenubarPrimitive.Item>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
 | 
			
		||||
    inset?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, inset, ...props }, ref) => (
 | 
			
		||||
  <MenubarPrimitive.Item
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      inset && "pl-8",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
MenubarItem.displayName = MenubarPrimitive.Item.displayName
 | 
			
		||||
 | 
			
		||||
const MenubarCheckboxItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
 | 
			
		||||
>(({ className, children, checked, ...props }, ref) => (
 | 
			
		||||
  <MenubarPrimitive.CheckboxItem
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    checked={checked}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
 | 
			
		||||
      <MenubarPrimitive.ItemIndicator>
 | 
			
		||||
        <Check className="h-4 w-4" />
 | 
			
		||||
      </MenubarPrimitive.ItemIndicator>
 | 
			
		||||
    </span>
 | 
			
		||||
    {children}
 | 
			
		||||
  </MenubarPrimitive.CheckboxItem>
 | 
			
		||||
))
 | 
			
		||||
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
 | 
			
		||||
 | 
			
		||||
const MenubarRadioItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof MenubarPrimitive.RadioItem>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <MenubarPrimitive.RadioItem
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
 | 
			
		||||
      <MenubarPrimitive.ItemIndicator>
 | 
			
		||||
        <Circle className="h-2 w-2 fill-current" />
 | 
			
		||||
      </MenubarPrimitive.ItemIndicator>
 | 
			
		||||
    </span>
 | 
			
		||||
    {children}
 | 
			
		||||
  </MenubarPrimitive.RadioItem>
 | 
			
		||||
))
 | 
			
		||||
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
 | 
			
		||||
 | 
			
		||||
const MenubarLabel = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof MenubarPrimitive.Label>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
 | 
			
		||||
    inset?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, inset, ...props }, ref) => (
 | 
			
		||||
  <MenubarPrimitive.Label
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "px-2 py-1.5 text-sm font-semibold",
 | 
			
		||||
      inset && "pl-8",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
 | 
			
		||||
 | 
			
		||||
const MenubarSeparator = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof MenubarPrimitive.Separator>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <MenubarPrimitive.Separator
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("-mx-1 my-1 h-px bg-muted", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
 | 
			
		||||
 | 
			
		||||
const MenubarShortcut = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <span
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "ml-auto text-xs tracking-widest text-muted-foreground",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
MenubarShortcut.displayname = "MenubarShortcut"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Menubar,
 | 
			
		||||
  MenubarMenu,
 | 
			
		||||
  MenubarTrigger,
 | 
			
		||||
  MenubarContent,
 | 
			
		||||
  MenubarItem,
 | 
			
		||||
  MenubarSeparator,
 | 
			
		||||
  MenubarLabel,
 | 
			
		||||
  MenubarCheckboxItem,
 | 
			
		||||
  MenubarRadioGroup,
 | 
			
		||||
  MenubarRadioItem,
 | 
			
		||||
  MenubarPortal,
 | 
			
		||||
  MenubarSubContent,
 | 
			
		||||
  MenubarSubTrigger,
 | 
			
		||||
  MenubarGroup,
 | 
			
		||||
  MenubarSub,
 | 
			
		||||
  MenubarShortcut,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,128 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
 | 
			
		||||
import { cva } from "class-variance-authority"
 | 
			
		||||
import { ChevronDown } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const NavigationMenu = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof NavigationMenuPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <NavigationMenuPrimitive.Root
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "relative z-10 flex max-w-max flex-1 items-center justify-center",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    {children}
 | 
			
		||||
    <NavigationMenuViewport />
 | 
			
		||||
  </NavigationMenuPrimitive.Root>
 | 
			
		||||
))
 | 
			
		||||
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
const NavigationMenuList = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof NavigationMenuPrimitive.List>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <NavigationMenuPrimitive.List
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "group flex flex-1 list-none items-center justify-center space-x-1",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
 | 
			
		||||
 | 
			
		||||
const NavigationMenuItem = NavigationMenuPrimitive.Item
 | 
			
		||||
 | 
			
		||||
const navigationMenuTriggerStyle = cva(
 | 
			
		||||
  "group inline-flex h-10 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-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const NavigationMenuTrigger = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <NavigationMenuPrimitive.Trigger
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(navigationMenuTriggerStyle(), "group", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    {children}{" "}
 | 
			
		||||
    <ChevronDown
 | 
			
		||||
      className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
 | 
			
		||||
      aria-hidden="true"
 | 
			
		||||
    />
 | 
			
		||||
  </NavigationMenuPrimitive.Trigger>
 | 
			
		||||
))
 | 
			
		||||
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
 | 
			
		||||
 | 
			
		||||
const NavigationMenuContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof NavigationMenuPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <NavigationMenuPrimitive.Content
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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 ",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
const NavigationMenuLink = NavigationMenuPrimitive.Link
 | 
			
		||||
 | 
			
		||||
const NavigationMenuViewport = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div className={cn("absolute left-0 top-full flex justify-center")}>
 | 
			
		||||
    <NavigationMenuPrimitive.Viewport
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "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-lg 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)]",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
))
 | 
			
		||||
NavigationMenuViewport.displayName =
 | 
			
		||||
  NavigationMenuPrimitive.Viewport.displayName
 | 
			
		||||
 | 
			
		||||
const NavigationMenuIndicator = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <NavigationMenuPrimitive.Indicator
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
 | 
			
		||||
  </NavigationMenuPrimitive.Indicator>
 | 
			
		||||
))
 | 
			
		||||
NavigationMenuIndicator.displayName =
 | 
			
		||||
  NavigationMenuPrimitive.Indicator.displayName
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  navigationMenuTriggerStyle,
 | 
			
		||||
  NavigationMenu,
 | 
			
		||||
  NavigationMenuList,
 | 
			
		||||
  NavigationMenuItem,
 | 
			
		||||
  NavigationMenuContent,
 | 
			
		||||
  NavigationMenuTrigger,
 | 
			
		||||
  NavigationMenuLink,
 | 
			
		||||
  NavigationMenuIndicator,
 | 
			
		||||
  NavigationMenuViewport,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,117 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
import { ButtonProps, buttonVariants } from "@/components/ui/button"
 | 
			
		||||
 | 
			
		||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
 | 
			
		||||
  <nav
 | 
			
		||||
    role="navigation"
 | 
			
		||||
    aria-label="pagination"
 | 
			
		||||
    className={cn("mx-auto flex w-full justify-center", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
Pagination.displayName = "Pagination"
 | 
			
		||||
 | 
			
		||||
const PaginationContent = React.forwardRef<
 | 
			
		||||
  HTMLUListElement,
 | 
			
		||||
  React.ComponentProps<"ul">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ul
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("flex flex-row items-center gap-1", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
PaginationContent.displayName = "PaginationContent"
 | 
			
		||||
 | 
			
		||||
const PaginationItem = React.forwardRef<
 | 
			
		||||
  HTMLLIElement,
 | 
			
		||||
  React.ComponentProps<"li">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <li ref={ref} className={cn("", className)} {...props} />
 | 
			
		||||
))
 | 
			
		||||
PaginationItem.displayName = "PaginationItem"
 | 
			
		||||
 | 
			
		||||
type PaginationLinkProps = {
 | 
			
		||||
  isActive?: boolean
 | 
			
		||||
} & Pick<ButtonProps, "size"> &
 | 
			
		||||
  React.ComponentProps<"a">
 | 
			
		||||
 | 
			
		||||
const PaginationLink = ({
 | 
			
		||||
  className,
 | 
			
		||||
  isActive,
 | 
			
		||||
  size = "icon",
 | 
			
		||||
  ...props
 | 
			
		||||
}: PaginationLinkProps) => (
 | 
			
		||||
  <a
 | 
			
		||||
    aria-current={isActive ? "page" : undefined}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      buttonVariants({
 | 
			
		||||
        variant: isActive ? "outline" : "ghost",
 | 
			
		||||
        size,
 | 
			
		||||
      }),
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
PaginationLink.displayName = "PaginationLink"
 | 
			
		||||
 | 
			
		||||
const PaginationPrevious = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof PaginationLink>) => (
 | 
			
		||||
  <PaginationLink
 | 
			
		||||
    aria-label="Go to previous page"
 | 
			
		||||
    size="default"
 | 
			
		||||
    className={cn("gap-1 pl-2.5", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <ChevronLeft className="h-4 w-4" />
 | 
			
		||||
    <span>Previous</span>
 | 
			
		||||
  </PaginationLink>
 | 
			
		||||
)
 | 
			
		||||
PaginationPrevious.displayName = "PaginationPrevious"
 | 
			
		||||
 | 
			
		||||
const PaginationNext = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof PaginationLink>) => (
 | 
			
		||||
  <PaginationLink
 | 
			
		||||
    aria-label="Go to next page"
 | 
			
		||||
    size="default"
 | 
			
		||||
    className={cn("gap-1 pr-2.5", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <span>Next</span>
 | 
			
		||||
    <ChevronRight className="h-4 w-4" />
 | 
			
		||||
  </PaginationLink>
 | 
			
		||||
)
 | 
			
		||||
PaginationNext.displayName = "PaginationNext"
 | 
			
		||||
 | 
			
		||||
const PaginationEllipsis = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<"span">) => (
 | 
			
		||||
  <span
 | 
			
		||||
    aria-hidden
 | 
			
		||||
    className={cn("flex h-9 w-9 items-center justify-center", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <MoreHorizontal className="h-4 w-4" />
 | 
			
		||||
    <span className="sr-only">More pages</span>
 | 
			
		||||
  </span>
 | 
			
		||||
)
 | 
			
		||||
PaginationEllipsis.displayName = "PaginationEllipsis"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Pagination,
 | 
			
		||||
  PaginationContent,
 | 
			
		||||
  PaginationEllipsis,
 | 
			
		||||
  PaginationItem,
 | 
			
		||||
  PaginationLink,
 | 
			
		||||
  PaginationNext,
 | 
			
		||||
  PaginationPrevious,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Popover = PopoverPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const PopoverTrigger = PopoverPrimitive.Trigger
 | 
			
		||||
 | 
			
		||||
const PopoverContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof PopoverPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
 | 
			
		||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
 | 
			
		||||
  <PopoverPrimitive.Portal>
 | 
			
		||||
    <PopoverPrimitive.Content
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      align={align}
 | 
			
		||||
      sideOffset={sideOffset}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "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",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  </PopoverPrimitive.Portal>
 | 
			
		||||
))
 | 
			
		||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
export { Popover, PopoverTrigger, PopoverContent }
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as ProgressPrimitive from "@radix-ui/react-progress"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Progress = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ProgressPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
 | 
			
		||||
>(({ className, value, ...props }, ref) => (
 | 
			
		||||
  <ProgressPrimitive.Root
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "relative h-4 w-full overflow-hidden rounded-full bg-secondary",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <ProgressPrimitive.Indicator
 | 
			
		||||
      className="h-full w-full flex-1 bg-primary transition-all"
 | 
			
		||||
      style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
 | 
			
		||||
    />
 | 
			
		||||
  </ProgressPrimitive.Root>
 | 
			
		||||
))
 | 
			
		||||
Progress.displayName = ProgressPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
export { Progress }
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
 | 
			
		||||
import { Circle } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const RadioGroup = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof RadioGroupPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <RadioGroupPrimitive.Root
 | 
			
		||||
      className={cn("grid gap-2", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
      ref={ref}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
const RadioGroupItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof RadioGroupPrimitive.Item>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <RadioGroupPrimitive.Item
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
 | 
			
		||||
        <Circle className="h-2.5 w-2.5 fill-current text-current" />
 | 
			
		||||
      </RadioGroupPrimitive.Indicator>
 | 
			
		||||
    </RadioGroupPrimitive.Item>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
 | 
			
		||||
 | 
			
		||||
export { RadioGroup, RadioGroupItem }
 | 
			
		||||
@@ -1,43 +0,0 @@
 | 
			
		||||
import { GripVertical } from "lucide-react"
 | 
			
		||||
import * as ResizablePrimitive from "react-resizable-panels"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const ResizablePanelGroup = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
 | 
			
		||||
  <ResizablePrimitive.PanelGroup
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const ResizablePanel = ResizablePrimitive.Panel
 | 
			
		||||
 | 
			
		||||
const ResizableHandle = ({
 | 
			
		||||
  withHandle,
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
 | 
			
		||||
  withHandle?: boolean
 | 
			
		||||
}) => (
 | 
			
		||||
  <ResizablePrimitive.PanelResizeHandle
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    {withHandle && (
 | 
			
		||||
      <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
 | 
			
		||||
        <GripVertical className="h-2.5 w-2.5" />
 | 
			
		||||
      </div>
 | 
			
		||||
    )}
 | 
			
		||||
  </ResizablePrimitive.PanelResizeHandle>
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const ScrollArea = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ScrollAreaPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <ScrollAreaPrimitive.Root
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("relative overflow-hidden", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
 | 
			
		||||
      {children}
 | 
			
		||||
    </ScrollAreaPrimitive.Viewport>
 | 
			
		||||
    <ScrollBar />
 | 
			
		||||
    <ScrollAreaPrimitive.Corner />
 | 
			
		||||
  </ScrollAreaPrimitive.Root>
 | 
			
		||||
))
 | 
			
		||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
const ScrollBar = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
 | 
			
		||||
>(({ className, orientation = "vertical", ...props }, ref) => (
 | 
			
		||||
  <ScrollAreaPrimitive.ScrollAreaScrollbar
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    orientation={orientation}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex touch-none select-none transition-colors",
 | 
			
		||||
      orientation === "vertical" &&
 | 
			
		||||
        "h-full w-2.5 border-l border-l-transparent p-[1px]",
 | 
			
		||||
      orientation === "horizontal" &&
 | 
			
		||||
        "h-2.5 flex-col border-t border-t-transparent p-[1px]",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
 | 
			
		||||
  </ScrollAreaPrimitive.ScrollAreaScrollbar>
 | 
			
		||||
))
 | 
			
		||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
 | 
			
		||||
 | 
			
		||||
export { ScrollArea, ScrollBar }
 | 
			
		||||
@@ -1,158 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as SelectPrimitive from "@radix-ui/react-select"
 | 
			
		||||
import { Check, ChevronDown, ChevronUp } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Select = SelectPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const SelectGroup = SelectPrimitive.Group
 | 
			
		||||
 | 
			
		||||
const SelectValue = SelectPrimitive.Value
 | 
			
		||||
 | 
			
		||||
const SelectTrigger = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SelectPrimitive.Trigger>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <SelectPrimitive.Trigger
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    {children}
 | 
			
		||||
    <SelectPrimitive.Icon asChild>
 | 
			
		||||
      <ChevronDown className="h-4 w-4 opacity-50" />
 | 
			
		||||
    </SelectPrimitive.Icon>
 | 
			
		||||
  </SelectPrimitive.Trigger>
 | 
			
		||||
))
 | 
			
		||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
 | 
			
		||||
 | 
			
		||||
const SelectScrollUpButton = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <SelectPrimitive.ScrollUpButton
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex cursor-default items-center justify-center py-1",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <ChevronUp className="h-4 w-4" />
 | 
			
		||||
  </SelectPrimitive.ScrollUpButton>
 | 
			
		||||
))
 | 
			
		||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
 | 
			
		||||
 | 
			
		||||
const SelectScrollDownButton = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <SelectPrimitive.ScrollDownButton
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex cursor-default items-center justify-center py-1",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <ChevronDown className="h-4 w-4" />
 | 
			
		||||
  </SelectPrimitive.ScrollDownButton>
 | 
			
		||||
))
 | 
			
		||||
SelectScrollDownButton.displayName =
 | 
			
		||||
  SelectPrimitive.ScrollDownButton.displayName
 | 
			
		||||
 | 
			
		||||
const SelectContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SelectPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
 | 
			
		||||
>(({ className, children, position = "popper", ...props }, ref) => (
 | 
			
		||||
  <SelectPrimitive.Portal>
 | 
			
		||||
    <SelectPrimitive.Content
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "relative z-50 max-h-96 min-w-[8rem] overflow-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",
 | 
			
		||||
        position === "popper" &&
 | 
			
		||||
          "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      position={position}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <SelectScrollUpButton />
 | 
			
		||||
      <SelectPrimitive.Viewport
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "p-1",
 | 
			
		||||
          position === "popper" &&
 | 
			
		||||
            "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
 | 
			
		||||
        )}
 | 
			
		||||
      >
 | 
			
		||||
        {children}
 | 
			
		||||
      </SelectPrimitive.Viewport>
 | 
			
		||||
      <SelectScrollDownButton />
 | 
			
		||||
    </SelectPrimitive.Content>
 | 
			
		||||
  </SelectPrimitive.Portal>
 | 
			
		||||
))
 | 
			
		||||
SelectContent.displayName = SelectPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
const SelectLabel = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SelectPrimitive.Label>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <SelectPrimitive.Label
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
 | 
			
		||||
 | 
			
		||||
const SelectItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SelectPrimitive.Item>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
 | 
			
		||||
>(({ className, children, ...props }, ref) => (
 | 
			
		||||
  <SelectPrimitive.Item
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "relative flex w-full 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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
 | 
			
		||||
      <SelectPrimitive.ItemIndicator>
 | 
			
		||||
        <Check className="h-4 w-4" />
 | 
			
		||||
      </SelectPrimitive.ItemIndicator>
 | 
			
		||||
    </span>
 | 
			
		||||
 | 
			
		||||
    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
 | 
			
		||||
  </SelectPrimitive.Item>
 | 
			
		||||
))
 | 
			
		||||
SelectItem.displayName = SelectPrimitive.Item.displayName
 | 
			
		||||
 | 
			
		||||
const SelectSeparator = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SelectPrimitive.Separator>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <SelectPrimitive.Separator
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("-mx-1 my-1 h-px bg-muted", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Select,
 | 
			
		||||
  SelectGroup,
 | 
			
		||||
  SelectValue,
 | 
			
		||||
  SelectTrigger,
 | 
			
		||||
  SelectContent,
 | 
			
		||||
  SelectLabel,
 | 
			
		||||
  SelectItem,
 | 
			
		||||
  SelectSeparator,
 | 
			
		||||
  SelectScrollUpButton,
 | 
			
		||||
  SelectScrollDownButton,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Separator = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SeparatorPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
 | 
			
		||||
>(
 | 
			
		||||
  (
 | 
			
		||||
    { className, orientation = "horizontal", decorative = true, ...props },
 | 
			
		||||
    ref
 | 
			
		||||
  ) => (
 | 
			
		||||
    <SeparatorPrimitive.Root
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      decorative={decorative}
 | 
			
		||||
      orientation={orientation}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "shrink-0 bg-border",
 | 
			
		||||
        orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
)
 | 
			
		||||
Separator.displayName = SeparatorPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
export { Separator }
 | 
			
		||||
@@ -1,131 +0,0 @@
 | 
			
		||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
 | 
			
		||||
import { cva, type VariantProps } from "class-variance-authority"
 | 
			
		||||
import { X } from "lucide-react"
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Sheet = SheetPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const SheetTrigger = SheetPrimitive.Trigger
 | 
			
		||||
 | 
			
		||||
const SheetClose = SheetPrimitive.Close
 | 
			
		||||
 | 
			
		||||
const SheetPortal = SheetPrimitive.Portal
 | 
			
		||||
 | 
			
		||||
const SheetOverlay = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SheetPrimitive.Overlay>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <SheetPrimitive.Overlay
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
    ref={ref}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
 | 
			
		||||
 | 
			
		||||
const sheetVariants = cva(
 | 
			
		||||
  "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
 | 
			
		||||
  {
 | 
			
		||||
    variants: {
 | 
			
		||||
      side: {
 | 
			
		||||
        top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
 | 
			
		||||
        bottom:
 | 
			
		||||
          "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
 | 
			
		||||
        left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
 | 
			
		||||
        right:
 | 
			
		||||
          "inset-y-0 right-0 h-full w-3/4  border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    defaultVariants: {
 | 
			
		||||
      side: "right",
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
interface SheetContentProps
 | 
			
		||||
  extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
 | 
			
		||||
  VariantProps<typeof sheetVariants> { }
 | 
			
		||||
 | 
			
		||||
const SheetContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SheetPrimitive.Content>,
 | 
			
		||||
  SheetContentProps
 | 
			
		||||
>(({ side = "right", className, children, ...props }, ref) => (
 | 
			
		||||
  <SheetPortal>
 | 
			
		||||
    <SheetOverlay />
 | 
			
		||||
    <SheetPrimitive.Content
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(sheetVariants({ side }), className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      {children}
 | 
			
		||||
      <SheetPrimitive.Close 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-secondary">
 | 
			
		||||
        <X className="h-4 w-4" />
 | 
			
		||||
        <span className="sr-only">Close</span>
 | 
			
		||||
      </SheetPrimitive.Close>
 | 
			
		||||
    </SheetPrimitive.Content>
 | 
			
		||||
  </SheetPortal>
 | 
			
		||||
))
 | 
			
		||||
SheetContent.displayName = SheetPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
const SheetHeader = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
			
		||||
  <div
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex flex-col space-y-2 text-center sm:text-left",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
SheetHeader.displayName = "SheetHeader"
 | 
			
		||||
 | 
			
		||||
const SheetFooter = ({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
			
		||||
  <div
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
)
 | 
			
		||||
SheetFooter.displayName = "SheetFooter"
 | 
			
		||||
 | 
			
		||||
const SheetTitle = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SheetPrimitive.Title>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <SheetPrimitive.Title
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("text-lg font-semibold text-foreground", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
SheetTitle.displayName = SheetPrimitive.Title.displayName
 | 
			
		||||
 | 
			
		||||
const SheetDescription = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SheetPrimitive.Description>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <SheetPrimitive.Description
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("text-sm text-muted-foreground", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
SheetDescription.displayName = SheetPrimitive.Description.displayName
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Sheet, SheetClose,
 | 
			
		||||
  SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1,761 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import { Slot } from "@radix-ui/react-slot"
 | 
			
		||||
import { VariantProps, cva } from "class-variance-authority"
 | 
			
		||||
import { PanelLeft } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { useIsMobile } from "@/hooks/use-mobile"
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
import { Button } from "@/components/ui/button"
 | 
			
		||||
import { Input } from "@/components/ui/input"
 | 
			
		||||
import { Separator } from "@/components/ui/separator"
 | 
			
		||||
import { Sheet, SheetContent } from "@/components/ui/sheet"
 | 
			
		||||
import { Skeleton } from "@/components/ui/skeleton"
 | 
			
		||||
import {
 | 
			
		||||
  Tooltip,
 | 
			
		||||
  TooltipContent,
 | 
			
		||||
  TooltipProvider,
 | 
			
		||||
  TooltipTrigger,
 | 
			
		||||
} from "@/components/ui/tooltip"
 | 
			
		||||
 | 
			
		||||
const SIDEBAR_COOKIE_NAME = "sidebar:state"
 | 
			
		||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
 | 
			
		||||
const SIDEBAR_WIDTH = "16rem"
 | 
			
		||||
const SIDEBAR_WIDTH_MOBILE = "18rem"
 | 
			
		||||
const SIDEBAR_WIDTH_ICON = "3rem"
 | 
			
		||||
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
 | 
			
		||||
 | 
			
		||||
type SidebarContext = {
 | 
			
		||||
  state: "expanded" | "collapsed"
 | 
			
		||||
  open: boolean
 | 
			
		||||
  setOpen: (open: boolean) => void
 | 
			
		||||
  openMobile: boolean
 | 
			
		||||
  setOpenMobile: (open: boolean) => void
 | 
			
		||||
  isMobile: boolean
 | 
			
		||||
  toggleSidebar: () => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SidebarContext = React.createContext<SidebarContext | null>(null)
 | 
			
		||||
 | 
			
		||||
function useSidebar() {
 | 
			
		||||
  const context = React.useContext(SidebarContext)
 | 
			
		||||
  if (!context) {
 | 
			
		||||
    throw new Error("useSidebar must be used within a SidebarProvider.")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SidebarProvider = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div"> & {
 | 
			
		||||
    defaultOpen?: boolean
 | 
			
		||||
    open?: boolean
 | 
			
		||||
    onOpenChange?: (open: boolean) => void
 | 
			
		||||
  }
 | 
			
		||||
>(
 | 
			
		||||
  (
 | 
			
		||||
    {
 | 
			
		||||
      defaultOpen = true,
 | 
			
		||||
      open: openProp,
 | 
			
		||||
      onOpenChange: setOpenProp,
 | 
			
		||||
      className,
 | 
			
		||||
      style,
 | 
			
		||||
      children,
 | 
			
		||||
      ...props
 | 
			
		||||
    },
 | 
			
		||||
    ref
 | 
			
		||||
  ) => {
 | 
			
		||||
    const isMobile = useIsMobile()
 | 
			
		||||
    const [openMobile, setOpenMobile] = React.useState(false)
 | 
			
		||||
 | 
			
		||||
    // This is the internal state of the sidebar.
 | 
			
		||||
    // We use openProp and setOpenProp for control from outside the component.
 | 
			
		||||
    const [_open, _setOpen] = React.useState(defaultOpen)
 | 
			
		||||
    const open = openProp ?? _open
 | 
			
		||||
    const setOpen = React.useCallback(
 | 
			
		||||
      (value: boolean | ((value: boolean) => boolean)) => {
 | 
			
		||||
        const openState = typeof value === "function" ? value(open) : value
 | 
			
		||||
        if (setOpenProp) {
 | 
			
		||||
          setOpenProp(openState)
 | 
			
		||||
        } else {
 | 
			
		||||
          _setOpen(openState)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // This sets the cookie to keep the sidebar state.
 | 
			
		||||
        document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
 | 
			
		||||
      },
 | 
			
		||||
      [setOpenProp, open]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    // Helper to toggle the sidebar.
 | 
			
		||||
    const toggleSidebar = React.useCallback(() => {
 | 
			
		||||
      return isMobile
 | 
			
		||||
        ? setOpenMobile((open) => !open)
 | 
			
		||||
        : setOpen((open) => !open)
 | 
			
		||||
    }, [isMobile, setOpen, setOpenMobile])
 | 
			
		||||
 | 
			
		||||
    // Adds a keyboard shortcut to toggle the sidebar.
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
      const handleKeyDown = (event: KeyboardEvent) => {
 | 
			
		||||
        if (
 | 
			
		||||
          event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
 | 
			
		||||
          (event.metaKey || event.ctrlKey)
 | 
			
		||||
        ) {
 | 
			
		||||
          event.preventDefault()
 | 
			
		||||
          toggleSidebar()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      window.addEventListener("keydown", handleKeyDown)
 | 
			
		||||
      return () => window.removeEventListener("keydown", handleKeyDown)
 | 
			
		||||
    }, [toggleSidebar])
 | 
			
		||||
 | 
			
		||||
    // We add a state so that we can do data-state="expanded" or "collapsed".
 | 
			
		||||
    // This makes it easier to style the sidebar with Tailwind classes.
 | 
			
		||||
    const state = open ? "expanded" : "collapsed"
 | 
			
		||||
 | 
			
		||||
    const contextValue = React.useMemo<SidebarContext>(
 | 
			
		||||
      () => ({
 | 
			
		||||
        state,
 | 
			
		||||
        open,
 | 
			
		||||
        setOpen,
 | 
			
		||||
        isMobile,
 | 
			
		||||
        openMobile,
 | 
			
		||||
        setOpenMobile,
 | 
			
		||||
        toggleSidebar,
 | 
			
		||||
      }),
 | 
			
		||||
      [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <SidebarContext.Provider value={contextValue}>
 | 
			
		||||
        <TooltipProvider delayDuration={0}>
 | 
			
		||||
          <div
 | 
			
		||||
            style={
 | 
			
		||||
              {
 | 
			
		||||
                "--sidebar-width": SIDEBAR_WIDTH,
 | 
			
		||||
                "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
 | 
			
		||||
                ...style,
 | 
			
		||||
              } as React.CSSProperties
 | 
			
		||||
            }
 | 
			
		||||
            className={cn(
 | 
			
		||||
              "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
 | 
			
		||||
              className
 | 
			
		||||
            )}
 | 
			
		||||
            ref={ref}
 | 
			
		||||
            {...props}
 | 
			
		||||
          >
 | 
			
		||||
            {children}
 | 
			
		||||
          </div>
 | 
			
		||||
        </TooltipProvider>
 | 
			
		||||
      </SidebarContext.Provider>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
SidebarProvider.displayName = "SidebarProvider"
 | 
			
		||||
 | 
			
		||||
const Sidebar = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div"> & {
 | 
			
		||||
    side?: "left" | "right"
 | 
			
		||||
    variant?: "sidebar" | "floating" | "inset"
 | 
			
		||||
    collapsible?: "offcanvas" | "icon" | "none"
 | 
			
		||||
  }
 | 
			
		||||
>(
 | 
			
		||||
  (
 | 
			
		||||
    {
 | 
			
		||||
      side = "left",
 | 
			
		||||
      variant = "sidebar",
 | 
			
		||||
      collapsible = "offcanvas",
 | 
			
		||||
      className,
 | 
			
		||||
      children,
 | 
			
		||||
      ...props
 | 
			
		||||
    },
 | 
			
		||||
    ref
 | 
			
		||||
  ) => {
 | 
			
		||||
    const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
 | 
			
		||||
 | 
			
		||||
    if (collapsible === "none") {
 | 
			
		||||
      return (
 | 
			
		||||
        <div
 | 
			
		||||
          className={cn(
 | 
			
		||||
            "flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
 | 
			
		||||
            className
 | 
			
		||||
          )}
 | 
			
		||||
          ref={ref}
 | 
			
		||||
          {...props}
 | 
			
		||||
        >
 | 
			
		||||
          {children}
 | 
			
		||||
        </div>
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isMobile) {
 | 
			
		||||
      return (
 | 
			
		||||
        <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
 | 
			
		||||
          <SheetContent
 | 
			
		||||
            data-sidebar="sidebar"
 | 
			
		||||
            data-mobile="true"
 | 
			
		||||
            className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
 | 
			
		||||
            style={
 | 
			
		||||
              {
 | 
			
		||||
                "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
 | 
			
		||||
              } as React.CSSProperties
 | 
			
		||||
            }
 | 
			
		||||
            side={side}
 | 
			
		||||
          >
 | 
			
		||||
            <div className="flex h-full w-full flex-col">{children}</div>
 | 
			
		||||
          </SheetContent>
 | 
			
		||||
        </Sheet>
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        className="group peer hidden md:block text-sidebar-foreground"
 | 
			
		||||
        data-state={state}
 | 
			
		||||
        data-collapsible={state === "collapsed" ? collapsible : ""}
 | 
			
		||||
        data-variant={variant}
 | 
			
		||||
        data-side={side}
 | 
			
		||||
      >
 | 
			
		||||
        {/* This is what handles the sidebar gap on desktop */}
 | 
			
		||||
        <div
 | 
			
		||||
          className={cn(
 | 
			
		||||
            "duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear",
 | 
			
		||||
            "group-data-[collapsible=offcanvas]:w-0",
 | 
			
		||||
            "group-data-[side=right]:rotate-180",
 | 
			
		||||
            variant === "floating" || variant === "inset"
 | 
			
		||||
              ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
 | 
			
		||||
              : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
 | 
			
		||||
          )}
 | 
			
		||||
        />
 | 
			
		||||
        <div
 | 
			
		||||
          className={cn(
 | 
			
		||||
            "duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex",
 | 
			
		||||
            side === "left"
 | 
			
		||||
              ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
 | 
			
		||||
              : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
 | 
			
		||||
            // Adjust the padding for floating and inset variants.
 | 
			
		||||
            variant === "floating" || variant === "inset"
 | 
			
		||||
              ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
 | 
			
		||||
              : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
 | 
			
		||||
            className
 | 
			
		||||
          )}
 | 
			
		||||
          {...props}
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            data-sidebar="sidebar"
 | 
			
		||||
            className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
 | 
			
		||||
          >
 | 
			
		||||
            {children}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
Sidebar.displayName = "Sidebar"
 | 
			
		||||
 | 
			
		||||
const SidebarTrigger = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof Button>,
 | 
			
		||||
  React.ComponentProps<typeof Button>
 | 
			
		||||
>(({ className, onClick, ...props }, ref) => {
 | 
			
		||||
  const { toggleSidebar } = useSidebar()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Button
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="trigger"
 | 
			
		||||
      variant="ghost"
 | 
			
		||||
      size="icon"
 | 
			
		||||
      className={cn("h-7 w-7", className)}
 | 
			
		||||
      onClick={(event) => {
 | 
			
		||||
        onClick?.(event)
 | 
			
		||||
        toggleSidebar()
 | 
			
		||||
      }}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <PanelLeft />
 | 
			
		||||
      <span className="sr-only">Toggle Sidebar</span>
 | 
			
		||||
    </Button>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarTrigger.displayName = "SidebarTrigger"
 | 
			
		||||
 | 
			
		||||
const SidebarRail = React.forwardRef<
 | 
			
		||||
  HTMLButtonElement,
 | 
			
		||||
  React.ComponentProps<"button">
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  const { toggleSidebar } = useSidebar()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <button
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="rail"
 | 
			
		||||
      aria-label="Toggle Sidebar"
 | 
			
		||||
      tabIndex={-1}
 | 
			
		||||
      onClick={toggleSidebar}
 | 
			
		||||
      title="Toggle Sidebar"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
 | 
			
		||||
        "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
 | 
			
		||||
        "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
 | 
			
		||||
        "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
 | 
			
		||||
        "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
 | 
			
		||||
        "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarRail.displayName = "SidebarRail"
 | 
			
		||||
 | 
			
		||||
const SidebarInset = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"main">
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <main
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "relative flex min-h-svh flex-1 flex-col bg-background",
 | 
			
		||||
        "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarInset.displayName = "SidebarInset"
 | 
			
		||||
 | 
			
		||||
const SidebarInput = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof Input>,
 | 
			
		||||
  React.ComponentProps<typeof Input>
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <Input
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="input"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarInput.displayName = "SidebarInput"
 | 
			
		||||
 | 
			
		||||
const SidebarHeader = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div">
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="header"
 | 
			
		||||
      className={cn("flex flex-col gap-2 p-2", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarHeader.displayName = "SidebarHeader"
 | 
			
		||||
 | 
			
		||||
const SidebarFooter = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div">
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="footer"
 | 
			
		||||
      className={cn("flex flex-col gap-2 p-2", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarFooter.displayName = "SidebarFooter"
 | 
			
		||||
 | 
			
		||||
const SidebarSeparator = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof Separator>,
 | 
			
		||||
  React.ComponentProps<typeof Separator>
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <Separator
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="separator"
 | 
			
		||||
      className={cn("mx-2 w-auto bg-sidebar-border", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarSeparator.displayName = "SidebarSeparator"
 | 
			
		||||
 | 
			
		||||
const SidebarContent = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div">
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="content"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarContent.displayName = "SidebarContent"
 | 
			
		||||
 | 
			
		||||
const SidebarGroup = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div">
 | 
			
		||||
>(({ className, ...props }, ref) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="group"
 | 
			
		||||
      className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarGroup.displayName = "SidebarGroup"
 | 
			
		||||
 | 
			
		||||
const SidebarGroupLabel = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div"> & { asChild?: boolean }
 | 
			
		||||
>(({ className, asChild = false, ...props }, ref) => {
 | 
			
		||||
  const Comp = asChild ? Slot : "div"
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Comp
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="group-label"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
 | 
			
		||||
        "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarGroupLabel.displayName = "SidebarGroupLabel"
 | 
			
		||||
 | 
			
		||||
const SidebarGroupAction = React.forwardRef<
 | 
			
		||||
  HTMLButtonElement,
 | 
			
		||||
  React.ComponentProps<"button"> & { asChild?: boolean }
 | 
			
		||||
>(({ className, asChild = false, ...props }, ref) => {
 | 
			
		||||
  const Comp = asChild ? Slot : "button"
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Comp
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="group-action"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
 | 
			
		||||
        // Increases the hit area of the button on mobile.
 | 
			
		||||
        "after:absolute after:-inset-2 after:md:hidden",
 | 
			
		||||
        "group-data-[collapsible=icon]:hidden",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarGroupAction.displayName = "SidebarGroupAction"
 | 
			
		||||
 | 
			
		||||
const SidebarGroupContent = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    data-sidebar="group-content"
 | 
			
		||||
    className={cn("w-full text-sm", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
SidebarGroupContent.displayName = "SidebarGroupContent"
 | 
			
		||||
 | 
			
		||||
const SidebarMenu = React.forwardRef<
 | 
			
		||||
  HTMLUListElement,
 | 
			
		||||
  React.ComponentProps<"ul">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ul
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    data-sidebar="menu"
 | 
			
		||||
    className={cn("flex w-full min-w-0 flex-col gap-1", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
SidebarMenu.displayName = "SidebarMenu"
 | 
			
		||||
 | 
			
		||||
const SidebarMenuItem = React.forwardRef<
 | 
			
		||||
  HTMLLIElement,
 | 
			
		||||
  React.ComponentProps<"li">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <li
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    data-sidebar="menu-item"
 | 
			
		||||
    className={cn("group/menu-item relative", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
SidebarMenuItem.displayName = "SidebarMenuItem"
 | 
			
		||||
 | 
			
		||||
const sidebarMenuButtonVariants = cva(
 | 
			
		||||
  "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
 | 
			
		||||
  {
 | 
			
		||||
    variants: {
 | 
			
		||||
      variant: {
 | 
			
		||||
        default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
 | 
			
		||||
        outline:
 | 
			
		||||
          "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
 | 
			
		||||
      },
 | 
			
		||||
      size: {
 | 
			
		||||
        default: "h-8 text-sm",
 | 
			
		||||
        sm: "h-7 text-xs",
 | 
			
		||||
        lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    defaultVariants: {
 | 
			
		||||
      variant: "default",
 | 
			
		||||
      size: "default",
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const SidebarMenuButton = React.forwardRef<
 | 
			
		||||
  HTMLButtonElement,
 | 
			
		||||
  React.ComponentProps<"button"> & {
 | 
			
		||||
    asChild?: boolean
 | 
			
		||||
    isActive?: boolean
 | 
			
		||||
    tooltip?: string | React.ComponentProps<typeof TooltipContent>
 | 
			
		||||
  } & VariantProps<typeof sidebarMenuButtonVariants>
 | 
			
		||||
>(
 | 
			
		||||
  (
 | 
			
		||||
    {
 | 
			
		||||
      asChild = false,
 | 
			
		||||
      isActive = false,
 | 
			
		||||
      variant = "default",
 | 
			
		||||
      size = "default",
 | 
			
		||||
      tooltip,
 | 
			
		||||
      className,
 | 
			
		||||
      ...props
 | 
			
		||||
    },
 | 
			
		||||
    ref
 | 
			
		||||
  ) => {
 | 
			
		||||
    const Comp = asChild ? Slot : "button"
 | 
			
		||||
    const { isMobile, state } = useSidebar()
 | 
			
		||||
 | 
			
		||||
    const button = (
 | 
			
		||||
      <Comp
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        data-sidebar="menu-button"
 | 
			
		||||
        data-size={size}
 | 
			
		||||
        data-active={isActive}
 | 
			
		||||
        className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
 | 
			
		||||
        {...props}
 | 
			
		||||
      />
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if (!tooltip) {
 | 
			
		||||
      return button
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (typeof tooltip === "string") {
 | 
			
		||||
      tooltip = {
 | 
			
		||||
        children: tooltip,
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <Tooltip>
 | 
			
		||||
        <TooltipTrigger asChild>{button}</TooltipTrigger>
 | 
			
		||||
        <TooltipContent
 | 
			
		||||
          side="right"
 | 
			
		||||
          align="center"
 | 
			
		||||
          hidden={state !== "collapsed" || isMobile}
 | 
			
		||||
          {...tooltip}
 | 
			
		||||
        />
 | 
			
		||||
      </Tooltip>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
SidebarMenuButton.displayName = "SidebarMenuButton"
 | 
			
		||||
 | 
			
		||||
const SidebarMenuAction = React.forwardRef<
 | 
			
		||||
  HTMLButtonElement,
 | 
			
		||||
  React.ComponentProps<"button"> & {
 | 
			
		||||
    asChild?: boolean
 | 
			
		||||
    showOnHover?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
 | 
			
		||||
  const Comp = asChild ? Slot : "button"
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Comp
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="menu-action"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
 | 
			
		||||
        // Increases the hit area of the button on mobile.
 | 
			
		||||
        "after:absolute after:-inset-2 after:md:hidden",
 | 
			
		||||
        "peer-data-[size=sm]/menu-button:top-1",
 | 
			
		||||
        "peer-data-[size=default]/menu-button:top-1.5",
 | 
			
		||||
        "peer-data-[size=lg]/menu-button:top-2.5",
 | 
			
		||||
        "group-data-[collapsible=icon]:hidden",
 | 
			
		||||
        showOnHover &&
 | 
			
		||||
          "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarMenuAction.displayName = "SidebarMenuAction"
 | 
			
		||||
 | 
			
		||||
const SidebarMenuBadge = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    data-sidebar="menu-badge"
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none",
 | 
			
		||||
      "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
 | 
			
		||||
      "peer-data-[size=sm]/menu-button:top-1",
 | 
			
		||||
      "peer-data-[size=default]/menu-button:top-1.5",
 | 
			
		||||
      "peer-data-[size=lg]/menu-button:top-2.5",
 | 
			
		||||
      "group-data-[collapsible=icon]:hidden",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
SidebarMenuBadge.displayName = "SidebarMenuBadge"
 | 
			
		||||
 | 
			
		||||
const SidebarMenuSkeleton = React.forwardRef<
 | 
			
		||||
  HTMLDivElement,
 | 
			
		||||
  React.ComponentProps<"div"> & {
 | 
			
		||||
    showIcon?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ className, showIcon = false, ...props }, ref) => {
 | 
			
		||||
  // Random width between 50 to 90%.
 | 
			
		||||
  const width = React.useMemo(() => {
 | 
			
		||||
    return `${Math.floor(Math.random() * 40) + 50}%`
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="menu-skeleton"
 | 
			
		||||
      className={cn("rounded-md h-8 flex gap-2 px-2 items-center", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      {showIcon && (
 | 
			
		||||
        <Skeleton
 | 
			
		||||
          className="size-4 rounded-md"
 | 
			
		||||
          data-sidebar="menu-skeleton-icon"
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      <Skeleton
 | 
			
		||||
        className="h-4 flex-1 max-w-[--skeleton-width]"
 | 
			
		||||
        data-sidebar="menu-skeleton-text"
 | 
			
		||||
        style={
 | 
			
		||||
          {
 | 
			
		||||
            "--skeleton-width": width,
 | 
			
		||||
          } as React.CSSProperties
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
 | 
			
		||||
 | 
			
		||||
const SidebarMenuSub = React.forwardRef<
 | 
			
		||||
  HTMLUListElement,
 | 
			
		||||
  React.ComponentProps<"ul">
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ul
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    data-sidebar="menu-sub"
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
 | 
			
		||||
      "group-data-[collapsible=icon]:hidden",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
SidebarMenuSub.displayName = "SidebarMenuSub"
 | 
			
		||||
 | 
			
		||||
const SidebarMenuSubItem = React.forwardRef<
 | 
			
		||||
  HTMLLIElement,
 | 
			
		||||
  React.ComponentProps<"li">
 | 
			
		||||
>(({ ...props }, ref) => <li ref={ref} {...props} />)
 | 
			
		||||
SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
 | 
			
		||||
 | 
			
		||||
const SidebarMenuSubButton = React.forwardRef<
 | 
			
		||||
  HTMLAnchorElement,
 | 
			
		||||
  React.ComponentProps<"a"> & {
 | 
			
		||||
    asChild?: boolean
 | 
			
		||||
    size?: "sm" | "md"
 | 
			
		||||
    isActive?: boolean
 | 
			
		||||
  }
 | 
			
		||||
>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
 | 
			
		||||
  const Comp = asChild ? Slot : "a"
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Comp
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      data-sidebar="menu-sub-button"
 | 
			
		||||
      data-size={size}
 | 
			
		||||
      data-active={isActive}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
 | 
			
		||||
        "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
 | 
			
		||||
        size === "sm" && "text-xs",
 | 
			
		||||
        size === "md" && "text-sm",
 | 
			
		||||
        "group-data-[collapsible=icon]:hidden",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Sidebar,
 | 
			
		||||
  SidebarContent,
 | 
			
		||||
  SidebarFooter,
 | 
			
		||||
  SidebarGroup,
 | 
			
		||||
  SidebarGroupAction,
 | 
			
		||||
  SidebarGroupContent,
 | 
			
		||||
  SidebarGroupLabel,
 | 
			
		||||
  SidebarHeader,
 | 
			
		||||
  SidebarInput,
 | 
			
		||||
  SidebarInset,
 | 
			
		||||
  SidebarMenu,
 | 
			
		||||
  SidebarMenuAction,
 | 
			
		||||
  SidebarMenuBadge,
 | 
			
		||||
  SidebarMenuButton,
 | 
			
		||||
  SidebarMenuItem,
 | 
			
		||||
  SidebarMenuSkeleton,
 | 
			
		||||
  SidebarMenuSub,
 | 
			
		||||
  SidebarMenuSubButton,
 | 
			
		||||
  SidebarMenuSubItem,
 | 
			
		||||
  SidebarProvider,
 | 
			
		||||
  SidebarRail,
 | 
			
		||||
  SidebarSeparator,
 | 
			
		||||
  SidebarTrigger,
 | 
			
		||||
  useSidebar,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
function Skeleton({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLDivElement>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className={cn("animate-pulse rounded-md bg-muted", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { Skeleton }
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as SliderPrimitive from "@radix-ui/react-slider"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Slider = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SliderPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <SliderPrimitive.Root
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "relative flex w-full touch-none select-none items-center",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
 | 
			
		||||
      <SliderPrimitive.Range className="absolute h-full bg-primary" />
 | 
			
		||||
    </SliderPrimitive.Track>
 | 
			
		||||
    <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
 | 
			
		||||
  </SliderPrimitive.Root>
 | 
			
		||||
))
 | 
			
		||||
Slider.displayName = SliderPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
export { Slider }
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
import { useTheme } from "next-themes"
 | 
			
		||||
import { Toaster as Sonner, toast } from "sonner"
 | 
			
		||||
 | 
			
		||||
type ToasterProps = React.ComponentProps<typeof Sonner>
 | 
			
		||||
 | 
			
		||||
const Toaster = ({ ...props }: ToasterProps) => {
 | 
			
		||||
  const { theme = "system" } = useTheme()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Sonner
 | 
			
		||||
      theme={theme as ToasterProps["theme"]}
 | 
			
		||||
      className="toaster group"
 | 
			
		||||
      toastOptions={{
 | 
			
		||||
        classNames: {
 | 
			
		||||
          toast:
 | 
			
		||||
            "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
 | 
			
		||||
          description: "group-[.toast]:text-muted-foreground",
 | 
			
		||||
          actionButton:
 | 
			
		||||
            "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
 | 
			
		||||
          cancelButton:
 | 
			
		||||
            "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
 | 
			
		||||
        },
 | 
			
		||||
      }}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { Toaster, toast }
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as SwitchPrimitives from "@radix-ui/react-switch"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Switch = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof SwitchPrimitives.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <SwitchPrimitives.Root
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
    ref={ref}
 | 
			
		||||
  >
 | 
			
		||||
    <SwitchPrimitives.Thumb
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
  </SwitchPrimitives.Root>
 | 
			
		||||
))
 | 
			
		||||
Switch.displayName = SwitchPrimitives.Root.displayName
 | 
			
		||||
 | 
			
		||||
export { Switch }
 | 
			
		||||
@@ -1,117 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Table = React.forwardRef<
 | 
			
		||||
  HTMLTableElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div className="relative w-full overflow-auto">
 | 
			
		||||
    <table
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn("w-full caption-bottom text-sm", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
))
 | 
			
		||||
Table.displayName = "Table"
 | 
			
		||||
 | 
			
		||||
const TableHeader = React.forwardRef<
 | 
			
		||||
  HTMLTableSectionElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableSectionElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
 | 
			
		||||
))
 | 
			
		||||
TableHeader.displayName = "TableHeader"
 | 
			
		||||
 | 
			
		||||
const TableBody = React.forwardRef<
 | 
			
		||||
  HTMLTableSectionElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableSectionElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <tbody
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("[&_tr:last-child]:border-0", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableBody.displayName = "TableBody"
 | 
			
		||||
 | 
			
		||||
const TableFooter = React.forwardRef<
 | 
			
		||||
  HTMLTableSectionElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableSectionElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <tfoot
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableFooter.displayName = "TableFooter"
 | 
			
		||||
 | 
			
		||||
const TableRow = React.forwardRef<
 | 
			
		||||
  HTMLTableRowElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableRowElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <tr
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableRow.displayName = "TableRow"
 | 
			
		||||
 | 
			
		||||
const TableHead = React.forwardRef<
 | 
			
		||||
  HTMLTableCellElement,
 | 
			
		||||
  React.ThHTMLAttributes<HTMLTableCellElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <th
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableHead.displayName = "TableHead"
 | 
			
		||||
 | 
			
		||||
const TableCell = React.forwardRef<
 | 
			
		||||
  HTMLTableCellElement,
 | 
			
		||||
  React.TdHTMLAttributes<HTMLTableCellElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <td
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableCell.displayName = "TableCell"
 | 
			
		||||
 | 
			
		||||
const TableCaption = React.forwardRef<
 | 
			
		||||
  HTMLTableCaptionElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableCaptionElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <caption
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("mt-4 text-sm text-muted-foreground", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableCaption.displayName = "TableCaption"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Table,
 | 
			
		||||
  TableHeader,
 | 
			
		||||
  TableBody,
 | 
			
		||||
  TableFooter,
 | 
			
		||||
  TableHead,
 | 
			
		||||
  TableRow,
 | 
			
		||||
  TableCell,
 | 
			
		||||
  TableCaption,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,53 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Tabs = TabsPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const TabsList = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof TabsPrimitive.List>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <TabsPrimitive.List
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TabsList.displayName = TabsPrimitive.List.displayName
 | 
			
		||||
 | 
			
		||||
const TabsTrigger = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof TabsPrimitive.Trigger>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <TabsPrimitive.Trigger
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
 | 
			
		||||
 | 
			
		||||
const TabsContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof TabsPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <TabsPrimitive.Content
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TabsContent.displayName = TabsPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
export interface TextareaProps
 | 
			
		||||
  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
 | 
			
		||||
 | 
			
		||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
 | 
			
		||||
  ({ className, ...props }, ref) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <textarea
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
 | 
			
		||||
          className
 | 
			
		||||
        )}
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        {...props}
 | 
			
		||||
      />
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
Textarea.displayName = "Textarea"
 | 
			
		||||
 | 
			
		||||
export { Textarea }
 | 
			
		||||
@@ -1,127 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as ToastPrimitives from "@radix-ui/react-toast"
 | 
			
		||||
import { cva, type VariantProps } from "class-variance-authority"
 | 
			
		||||
import { X } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const ToastProvider = ToastPrimitives.Provider
 | 
			
		||||
 | 
			
		||||
const ToastViewport = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ToastPrimitives.Viewport>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ToastPrimitives.Viewport
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
 | 
			
		||||
 | 
			
		||||
const toastVariants = cva(
 | 
			
		||||
  "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
 | 
			
		||||
  {
 | 
			
		||||
    variants: {
 | 
			
		||||
      variant: {
 | 
			
		||||
        default: "border bg-background text-foreground",
 | 
			
		||||
        destructive:
 | 
			
		||||
          "destructive group border-destructive bg-destructive text-destructive-foreground",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    defaultVariants: {
 | 
			
		||||
      variant: "default",
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const Toast = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ToastPrimitives.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
 | 
			
		||||
    VariantProps<typeof toastVariants>
 | 
			
		||||
>(({ className, variant, ...props }, ref) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <ToastPrimitives.Root
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(toastVariants({ variant }), className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
Toast.displayName = ToastPrimitives.Root.displayName
 | 
			
		||||
 | 
			
		||||
const ToastAction = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ToastPrimitives.Action>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ToastPrimitives.Action
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
ToastAction.displayName = ToastPrimitives.Action.displayName
 | 
			
		||||
 | 
			
		||||
const ToastClose = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ToastPrimitives.Close>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ToastPrimitives.Close
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    toast-close=""
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <X className="h-4 w-4" />
 | 
			
		||||
  </ToastPrimitives.Close>
 | 
			
		||||
))
 | 
			
		||||
ToastClose.displayName = ToastPrimitives.Close.displayName
 | 
			
		||||
 | 
			
		||||
const ToastTitle = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ToastPrimitives.Title>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ToastPrimitives.Title
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("text-sm font-semibold", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
ToastTitle.displayName = ToastPrimitives.Title.displayName
 | 
			
		||||
 | 
			
		||||
const ToastDescription = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ToastPrimitives.Description>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <ToastPrimitives.Description
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("text-sm opacity-90", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
ToastDescription.displayName = ToastPrimitives.Description.displayName
 | 
			
		||||
 | 
			
		||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
 | 
			
		||||
 | 
			
		||||
type ToastActionElement = React.ReactElement<typeof ToastAction>
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  type ToastProps,
 | 
			
		||||
  type ToastActionElement,
 | 
			
		||||
  ToastProvider,
 | 
			
		||||
  ToastViewport,
 | 
			
		||||
  Toast,
 | 
			
		||||
  ToastTitle,
 | 
			
		||||
  ToastDescription,
 | 
			
		||||
  ToastClose,
 | 
			
		||||
  ToastAction,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
import { useToast } from "@/hooks/use-toast"
 | 
			
		||||
import {
 | 
			
		||||
  Toast,
 | 
			
		||||
  ToastClose,
 | 
			
		||||
  ToastDescription,
 | 
			
		||||
  ToastProvider,
 | 
			
		||||
  ToastTitle,
 | 
			
		||||
  ToastViewport,
 | 
			
		||||
} from "@/components/ui/toast"
 | 
			
		||||
 | 
			
		||||
export function Toaster() {
 | 
			
		||||
  const { toasts } = useToast()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <ToastProvider>
 | 
			
		||||
      {toasts.map(function ({ id, title, description, action, ...props }) {
 | 
			
		||||
        return (
 | 
			
		||||
          <Toast key={id} {...props}>
 | 
			
		||||
            <div className="grid gap-1">
 | 
			
		||||
              {title && <ToastTitle>{title}</ToastTitle>}
 | 
			
		||||
              {description && (
 | 
			
		||||
                <ToastDescription>{description}</ToastDescription>
 | 
			
		||||
              )}
 | 
			
		||||
            </div>
 | 
			
		||||
            {action}
 | 
			
		||||
            <ToastClose />
 | 
			
		||||
          </Toast>
 | 
			
		||||
        )
 | 
			
		||||
      })}
 | 
			
		||||
      <ToastViewport />
 | 
			
		||||
    </ToastProvider>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@@ -1,59 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
 | 
			
		||||
import { type VariantProps } from "class-variance-authority"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
import { toggleVariants } from "@/components/ui/toggle"
 | 
			
		||||
 | 
			
		||||
const ToggleGroupContext = React.createContext<
 | 
			
		||||
  VariantProps<typeof toggleVariants>
 | 
			
		||||
>({
 | 
			
		||||
  size: "default",
 | 
			
		||||
  variant: "default",
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const ToggleGroup = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ToggleGroupPrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
 | 
			
		||||
    VariantProps<typeof toggleVariants>
 | 
			
		||||
>(({ className, variant, size, children, ...props }, ref) => (
 | 
			
		||||
  <ToggleGroupPrimitive.Root
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("flex items-center justify-center gap-1", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  >
 | 
			
		||||
    <ToggleGroupContext.Provider value={{ variant, size }}>
 | 
			
		||||
      {children}
 | 
			
		||||
    </ToggleGroupContext.Provider>
 | 
			
		||||
  </ToggleGroupPrimitive.Root>
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
const ToggleGroupItem = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof ToggleGroupPrimitive.Item>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
 | 
			
		||||
    VariantProps<typeof toggleVariants>
 | 
			
		||||
>(({ className, children, variant, size, ...props }, ref) => {
 | 
			
		||||
  const context = React.useContext(ToggleGroupContext)
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <ToggleGroupPrimitive.Item
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        toggleVariants({
 | 
			
		||||
          variant: context.variant || variant,
 | 
			
		||||
          size: context.size || size,
 | 
			
		||||
        }),
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      {children}
 | 
			
		||||
    </ToggleGroupPrimitive.Item>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
 | 
			
		||||
 | 
			
		||||
export { ToggleGroup, ToggleGroupItem }
 | 
			
		||||
@@ -1,43 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
 | 
			
		||||
import { cva, type VariantProps } from "class-variance-authority"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const toggleVariants = cva(
 | 
			
		||||
  "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
 | 
			
		||||
  {
 | 
			
		||||
    variants: {
 | 
			
		||||
      variant: {
 | 
			
		||||
        default: "bg-transparent",
 | 
			
		||||
        outline:
 | 
			
		||||
          "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
 | 
			
		||||
      },
 | 
			
		||||
      size: {
 | 
			
		||||
        default: "h-10 px-3",
 | 
			
		||||
        sm: "h-9 px-2.5",
 | 
			
		||||
        lg: "h-11 px-5",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    defaultVariants: {
 | 
			
		||||
      variant: "default",
 | 
			
		||||
      size: "default",
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const Toggle = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof TogglePrimitive.Root>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
 | 
			
		||||
    VariantProps<typeof toggleVariants>
 | 
			
		||||
>(({ className, variant, size, ...props }, ref) => (
 | 
			
		||||
  <TogglePrimitive.Root
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(toggleVariants({ variant, size, className }))}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
Toggle.displayName = TogglePrimitive.Root.displayName
 | 
			
		||||
 | 
			
		||||
export { Toggle, toggleVariants }
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const TooltipProvider = TooltipPrimitive.Provider
 | 
			
		||||
 | 
			
		||||
const Tooltip = TooltipPrimitive.Root
 | 
			
		||||
 | 
			
		||||
const TooltipTrigger = TooltipPrimitive.Trigger
 | 
			
		||||
 | 
			
		||||
const TooltipContent = React.forwardRef<
 | 
			
		||||
  React.ElementRef<typeof TooltipPrimitive.Content>,
 | 
			
		||||
  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
 | 
			
		||||
>(({ className, sideOffset = 4, ...props }, ref) => (
 | 
			
		||||
  <TooltipPrimitive.Content
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    sideOffset={sideOffset}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
 | 
			
		||||
 | 
			
		||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
import { useToast, toast } from "@/hooks/use-toast";
 | 
			
		||||
 | 
			
		||||
export { useToast, toast };
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
 | 
			
		||||
const MOBILE_BREAKPOINT = 768
 | 
			
		||||
 | 
			
		||||
export function useIsMobile() {
 | 
			
		||||
  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
 | 
			
		||||
 | 
			
		||||
  React.useEffect(() => {
 | 
			
		||||
    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
 | 
			
		||||
    const onChange = () => {
 | 
			
		||||
      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
 | 
			
		||||
    }
 | 
			
		||||
    mql.addEventListener("change", onChange)
 | 
			
		||||
    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
 | 
			
		||||
    return () => mql.removeEventListener("change", onChange)
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  return !!isMobile
 | 
			
		||||
}
 | 
			
		||||
@@ -1,191 +0,0 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
 | 
			
		||||
import type {
 | 
			
		||||
  ToastActionElement,
 | 
			
		||||
  ToastProps,
 | 
			
		||||
} from "@/components/ui/toast"
 | 
			
		||||
 | 
			
		||||
const TOAST_LIMIT = 1
 | 
			
		||||
const TOAST_REMOVE_DELAY = 1000000
 | 
			
		||||
 | 
			
		||||
type ToasterToast = ToastProps & {
 | 
			
		||||
  id: string
 | 
			
		||||
  title?: React.ReactNode
 | 
			
		||||
  description?: React.ReactNode
 | 
			
		||||
  action?: ToastActionElement
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const actionTypes = {
 | 
			
		||||
  ADD_TOAST: "ADD_TOAST",
 | 
			
		||||
  UPDATE_TOAST: "UPDATE_TOAST",
 | 
			
		||||
  DISMISS_TOAST: "DISMISS_TOAST",
 | 
			
		||||
  REMOVE_TOAST: "REMOVE_TOAST",
 | 
			
		||||
} as const
 | 
			
		||||
 | 
			
		||||
let count = 0
 | 
			
		||||
 | 
			
		||||
function genId() {
 | 
			
		||||
  count = (count + 1) % Number.MAX_SAFE_INTEGER
 | 
			
		||||
  return count.toString()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ActionType = typeof actionTypes
 | 
			
		||||
 | 
			
		||||
type Action =
 | 
			
		||||
  | {
 | 
			
		||||
      type: ActionType["ADD_TOAST"]
 | 
			
		||||
      toast: ToasterToast
 | 
			
		||||
    }
 | 
			
		||||
  | {
 | 
			
		||||
      type: ActionType["UPDATE_TOAST"]
 | 
			
		||||
      toast: Partial<ToasterToast>
 | 
			
		||||
    }
 | 
			
		||||
  | {
 | 
			
		||||
      type: ActionType["DISMISS_TOAST"]
 | 
			
		||||
      toastId?: ToasterToast["id"]
 | 
			
		||||
    }
 | 
			
		||||
  | {
 | 
			
		||||
      type: ActionType["REMOVE_TOAST"]
 | 
			
		||||
      toastId?: ToasterToast["id"]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
interface State {
 | 
			
		||||
  toasts: ToasterToast[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
 | 
			
		||||
 | 
			
		||||
const addToRemoveQueue = (toastId: string) => {
 | 
			
		||||
  if (toastTimeouts.has(toastId)) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const timeout = setTimeout(() => {
 | 
			
		||||
    toastTimeouts.delete(toastId)
 | 
			
		||||
    dispatch({
 | 
			
		||||
      type: "REMOVE_TOAST",
 | 
			
		||||
      toastId: toastId,
 | 
			
		||||
    })
 | 
			
		||||
  }, TOAST_REMOVE_DELAY)
 | 
			
		||||
 | 
			
		||||
  toastTimeouts.set(toastId, timeout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const reducer = (state: State, action: Action): State => {
 | 
			
		||||
  switch (action.type) {
 | 
			
		||||
    case "ADD_TOAST":
 | 
			
		||||
      return {
 | 
			
		||||
        ...state,
 | 
			
		||||
        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    case "UPDATE_TOAST":
 | 
			
		||||
      return {
 | 
			
		||||
        ...state,
 | 
			
		||||
        toasts: state.toasts.map((t) =>
 | 
			
		||||
          t.id === action.toast.id ? { ...t, ...action.toast } : t
 | 
			
		||||
        ),
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    case "DISMISS_TOAST": {
 | 
			
		||||
      const { toastId } = action
 | 
			
		||||
 | 
			
		||||
      // ! Side effects ! - This could be extracted into a dismissToast() action,
 | 
			
		||||
      // but I'll keep it here for simplicity
 | 
			
		||||
      if (toastId) {
 | 
			
		||||
        addToRemoveQueue(toastId)
 | 
			
		||||
      } else {
 | 
			
		||||
        state.toasts.forEach((toast) => {
 | 
			
		||||
          addToRemoveQueue(toast.id)
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        ...state,
 | 
			
		||||
        toasts: state.toasts.map((t) =>
 | 
			
		||||
          t.id === toastId || toastId === undefined
 | 
			
		||||
            ? {
 | 
			
		||||
                ...t,
 | 
			
		||||
                open: false,
 | 
			
		||||
              }
 | 
			
		||||
            : t
 | 
			
		||||
        ),
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    case "REMOVE_TOAST":
 | 
			
		||||
      if (action.toastId === undefined) {
 | 
			
		||||
        return {
 | 
			
		||||
          ...state,
 | 
			
		||||
          toasts: [],
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
        ...state,
 | 
			
		||||
        toasts: state.toasts.filter((t) => t.id !== action.toastId),
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const listeners: Array<(state: State) => void> = []
 | 
			
		||||
 | 
			
		||||
let memoryState: State = { toasts: [] }
 | 
			
		||||
 | 
			
		||||
function dispatch(action: Action) {
 | 
			
		||||
  memoryState = reducer(memoryState, action)
 | 
			
		||||
  listeners.forEach((listener) => {
 | 
			
		||||
    listener(memoryState)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Toast = Omit<ToasterToast, "id">
 | 
			
		||||
 | 
			
		||||
function toast({ ...props }: Toast) {
 | 
			
		||||
  const id = genId()
 | 
			
		||||
 | 
			
		||||
  const update = (props: ToasterToast) =>
 | 
			
		||||
    dispatch({
 | 
			
		||||
      type: "UPDATE_TOAST",
 | 
			
		||||
      toast: { ...props, id },
 | 
			
		||||
    })
 | 
			
		||||
  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
 | 
			
		||||
 | 
			
		||||
  dispatch({
 | 
			
		||||
    type: "ADD_TOAST",
 | 
			
		||||
    toast: {
 | 
			
		||||
      ...props,
 | 
			
		||||
      id,
 | 
			
		||||
      open: true,
 | 
			
		||||
      onOpenChange: (open) => {
 | 
			
		||||
        if (!open) dismiss()
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    id: id,
 | 
			
		||||
    dismiss,
 | 
			
		||||
    update,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function useToast() {
 | 
			
		||||
  const [state, setState] = React.useState<State>(memoryState)
 | 
			
		||||
 | 
			
		||||
  React.useEffect(() => {
 | 
			
		||||
    listeners.push(setState)
 | 
			
		||||
    return () => {
 | 
			
		||||
      const index = listeners.indexOf(setState)
 | 
			
		||||
      if (index > -1) {
 | 
			
		||||
        listeners.splice(index, 1)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [state])
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    ...state,
 | 
			
		||||
    toast,
 | 
			
		||||
    dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { useToast, toast }
 | 
			
		||||
							
								
								
									
										101
									
								
								src/index.css
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								src/index.css
									
									
									
									
									
								
							@@ -1,101 +0,0 @@
 | 
			
		||||
@tailwind base;
 | 
			
		||||
@tailwind components;
 | 
			
		||||
@tailwind utilities;
 | 
			
		||||
 | 
			
		||||
@layer base {
 | 
			
		||||
  :root {
 | 
			
		||||
    --background: 0 0% 100%;
 | 
			
		||||
    --foreground: 222.2 84% 4.9%;
 | 
			
		||||
 | 
			
		||||
    --card: 0 0% 100%;
 | 
			
		||||
    --card-foreground: 222.2 84% 4.9%;
 | 
			
		||||
 | 
			
		||||
    --popover: 0 0% 100%;
 | 
			
		||||
    --popover-foreground: 222.2 84% 4.9%;
 | 
			
		||||
 | 
			
		||||
    --primary: 222.2 47.4% 11.2%;
 | 
			
		||||
    --primary-foreground: 210 40% 98%;
 | 
			
		||||
 | 
			
		||||
    --secondary: 210 40% 96.1%;
 | 
			
		||||
    --secondary-foreground: 222.2 47.4% 11.2%;
 | 
			
		||||
 | 
			
		||||
    --muted: 210 40% 96.1%;
 | 
			
		||||
    --muted-foreground: 215.4 16.3% 46.9%;
 | 
			
		||||
 | 
			
		||||
    --accent: 210 40% 96.1%;
 | 
			
		||||
    --accent-foreground: 222.2 47.4% 11.2%;
 | 
			
		||||
 | 
			
		||||
    --destructive: 0 84.2% 60.2%;
 | 
			
		||||
    --destructive-foreground: 210 40% 98%;
 | 
			
		||||
 | 
			
		||||
    --border: 214.3 31.8% 91.4%;
 | 
			
		||||
    --input: 214.3 31.8% 91.4%;
 | 
			
		||||
    --ring: 222.2 84% 4.9%;
 | 
			
		||||
 | 
			
		||||
    --radius: 0.5rem;
 | 
			
		||||
 | 
			
		||||
    --sidebar-background: 0 0% 98%;
 | 
			
		||||
 | 
			
		||||
    --sidebar-foreground: 240 5.3% 26.1%;
 | 
			
		||||
 | 
			
		||||
    --sidebar-primary: 240 5.9% 10%;
 | 
			
		||||
 | 
			
		||||
    --sidebar-primary-foreground: 0 0% 98%;
 | 
			
		||||
 | 
			
		||||
    --sidebar-accent: 240 4.8% 95.9%;
 | 
			
		||||
 | 
			
		||||
    --sidebar-accent-foreground: 240 5.9% 10%;
 | 
			
		||||
 | 
			
		||||
    --sidebar-border: 220 13% 91%;
 | 
			
		||||
 | 
			
		||||
    --sidebar-ring: 217.2 91.2% 59.8%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .dark {
 | 
			
		||||
    --background: 222.2 84% 4.9%;
 | 
			
		||||
    --foreground: 210 40% 98%;
 | 
			
		||||
 | 
			
		||||
    --card: 222.2 84% 4.9%;
 | 
			
		||||
    --card-foreground: 210 40% 98%;
 | 
			
		||||
 | 
			
		||||
    --popover: 222.2 84% 4.9%;
 | 
			
		||||
    --popover-foreground: 210 40% 98%;
 | 
			
		||||
 | 
			
		||||
    --primary: 210 40% 98%;
 | 
			
		||||
    --primary-foreground: 222.2 47.4% 11.2%;
 | 
			
		||||
 | 
			
		||||
    --secondary: 217.2 32.6% 17.5%;
 | 
			
		||||
    --secondary-foreground: 210 40% 98%;
 | 
			
		||||
 | 
			
		||||
    --muted: 217.2 32.6% 17.5%;
 | 
			
		||||
    --muted-foreground: 215 20.2% 65.1%;
 | 
			
		||||
 | 
			
		||||
    --accent: 217.2 32.6% 17.5%;
 | 
			
		||||
    --accent-foreground: 210 40% 98%;
 | 
			
		||||
 | 
			
		||||
    --destructive: 0 62.8% 30.6%;
 | 
			
		||||
    --destructive-foreground: 210 40% 98%;
 | 
			
		||||
 | 
			
		||||
    --border: 217.2 32.6% 17.5%;
 | 
			
		||||
    --input: 217.2 32.6% 17.5%;
 | 
			
		||||
    --ring: 212.7 26.8% 83.9%;
 | 
			
		||||
    --sidebar-background: 240 5.9% 10%;
 | 
			
		||||
    --sidebar-foreground: 240 4.8% 95.9%;
 | 
			
		||||
    --sidebar-primary: 224.3 76.3% 48%;
 | 
			
		||||
    --sidebar-primary-foreground: 0 0% 100%;
 | 
			
		||||
    --sidebar-accent: 240 3.7% 15.9%;
 | 
			
		||||
    --sidebar-accent-foreground: 240 4.8% 95.9%;
 | 
			
		||||
    --sidebar-border: 240 3.7% 15.9%;
 | 
			
		||||
    --sidebar-ring: 217.2 91.2% 59.8%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@layer base {
 | 
			
		||||
  * {
 | 
			
		||||
    @apply border-border;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  body {
 | 
			
		||||
    @apply bg-background text-foreground;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
import { clsx, type ClassValue } from "clsx"
 | 
			
		||||
import { twMerge } from "tailwind-merge"
 | 
			
		||||
 | 
			
		||||
export function cn(...inputs: ClassValue[]) {
 | 
			
		||||
  return twMerge(clsx(inputs))
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
import { createRoot } from 'react-dom/client'
 | 
			
		||||
import App from './App.tsx'
 | 
			
		||||
import './index.css'
 | 
			
		||||
 | 
			
		||||
createRoot(document.getElementById("root")!).render(<App />);
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
import React, { useEffect } from 'react';
 | 
			
		||||
import { useNavigate, useSearchParams } from 'react-router-dom';
 | 
			
		||||
import { toast } from '@/hooks/use-toast';
 | 
			
		||||
 | 
			
		||||
const GiteaOAuthCallback = () => {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const [searchParams] = useSearchParams();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const handleOAuthCallback = async () => {
 | 
			
		||||
      // Get the code from the URL parameters
 | 
			
		||||
      const code = searchParams.get('code');
 | 
			
		||||
      
 | 
			
		||||
      if (!code) {
 | 
			
		||||
        toast({
 | 
			
		||||
          title: "Error",
 | 
			
		||||
          description: "No authorization code received from Gitea",
 | 
			
		||||
          variant: "destructive",
 | 
			
		||||
        });
 | 
			
		||||
        navigate('/');
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        // Send the code to our backend to exchange for a JWT token
 | 
			
		||||
        const response = await fetch('/admin/api/login/oauth/gitea/final', {
 | 
			
		||||
          method: 'POST',
 | 
			
		||||
          headers: {
 | 
			
		||||
            'Content-Type': 'application/json',
 | 
			
		||||
          },
 | 
			
		||||
          body: JSON.stringify({ code }),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const data = await response.json();
 | 
			
		||||
 | 
			
		||||
        if (response.ok && data.token) {
 | 
			
		||||
          // Store the token in localStorage
 | 
			
		||||
          localStorage.setItem('adminToken', data.token);
 | 
			
		||||
          
 | 
			
		||||
          toast({
 | 
			
		||||
            title: "Success",
 | 
			
		||||
            description: data.message || "Login successful",
 | 
			
		||||
          });
 | 
			
		||||
          
 | 
			
		||||
          // Redirect to the admin dashboard
 | 
			
		||||
          window.location.href = '/admin';
 | 
			
		||||
        } else {
 | 
			
		||||
          toast({
 | 
			
		||||
            title: "Error",
 | 
			
		||||
            description: data.error || "Login failed",
 | 
			
		||||
            variant: "destructive",
 | 
			
		||||
          });
 | 
			
		||||
          navigate('/');
 | 
			
		||||
        }
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        toast({
 | 
			
		||||
          title: "Error",
 | 
			
		||||
          description: "Network error. Please try again.",
 | 
			
		||||
          variant: "destructive",
 | 
			
		||||
        });
 | 
			
		||||
        navigate('/');
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    handleOAuthCallback();
 | 
			
		||||
  }, [navigate, searchParams]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100">
 | 
			
		||||
      <div className="text-center">
 | 
			
		||||
        <div className="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-slate-600 mb-4"></div>
 | 
			
		||||
        <h2 className="text-2xl font-semibold text-slate-800">Processing Gitea Login</h2>
 | 
			
		||||
        <p className="text-slate-600 mt-2">Please wait while we complete your authentication...</p>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default GiteaOAuthCallback;
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
 | 
			
		||||
import React, { useState, useEffect } from 'react';
 | 
			
		||||
import LoginPage from '@/components/LoginPage';
 | 
			
		||||
import AdminDashboard from '@/components/AdminDashboard';
 | 
			
		||||
 | 
			
		||||
const Index = () => {
 | 
			
		||||
  const [token, setToken] = useState<string | null>(null);
 | 
			
		||||
 | 
			
		||||
  // Check for existing token on component mount
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const savedToken = localStorage.getItem('adminToken');
 | 
			
		||||
    if (savedToken) {
 | 
			
		||||
      // Check if token is expired (JWT tokens contain expiration info)
 | 
			
		||||
      try {
 | 
			
		||||
        const payload = JSON.parse(atob(savedToken.split('.')[1]));
 | 
			
		||||
        const currentTime = Math.floor(Date.now() / 1000);
 | 
			
		||||
        
 | 
			
		||||
        if (payload.exp && payload.exp > currentTime) {
 | 
			
		||||
          setToken(savedToken);
 | 
			
		||||
        } else {
 | 
			
		||||
          // Token expired, remove it
 | 
			
		||||
          localStorage.removeItem('adminToken');
 | 
			
		||||
        }
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        // Invalid token format, remove it
 | 
			
		||||
        localStorage.removeItem('adminToken');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const handleLogin = (newToken: string) => {
 | 
			
		||||
    setToken(newToken);
 | 
			
		||||
    localStorage.setItem('adminToken', newToken);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleLogout = () => {
 | 
			
		||||
    setToken(null);
 | 
			
		||||
    localStorage.removeItem('adminToken');
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (token) {
 | 
			
		||||
    return <AdminDashboard token={token} onLogout={handleLogout} />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return <LoginPage onLogin={handleLogin} />;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Index;
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
import { useLocation } from "react-router-dom";
 | 
			
		||||
import { useEffect } from "react";
 | 
			
		||||
 | 
			
		||||
const NotFound = () => {
 | 
			
		||||
  const location = useLocation();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    console.error(
 | 
			
		||||
      "404 Error: User attempted to access non-existent route:",
 | 
			
		||||
      location.pathname
 | 
			
		||||
    );
 | 
			
		||||
  }, [location.pathname]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="min-h-screen flex items-center justify-center bg-gray-100">
 | 
			
		||||
      <div className="text-center">
 | 
			
		||||
        <h1 className="text-4xl font-bold mb-4">404</h1>
 | 
			
		||||
        <p className="text-xl text-gray-600 mb-4">Oops! Page not found</p>
 | 
			
		||||
        <a href="/" className="text-blue-500 hover:text-blue-700 underline">
 | 
			
		||||
          Return to Home
 | 
			
		||||
        </a>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default NotFound;
 | 
			
		||||
							
								
								
									
										1
									
								
								src/vite-env.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/vite-env.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1 +0,0 @@
 | 
			
		||||
/// <reference types="vite/client" />
 | 
			
		||||
@@ -1,96 +0,0 @@
 | 
			
		||||
import type { Config } from "tailwindcss";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
	darkMode: ["class"],
 | 
			
		||||
	content: [
 | 
			
		||||
		"./pages/**/*.{ts,tsx}",
 | 
			
		||||
		"./components/**/*.{ts,tsx}",
 | 
			
		||||
		"./app/**/*.{ts,tsx}",
 | 
			
		||||
		"./src/**/*.{ts,tsx}",
 | 
			
		||||
	],
 | 
			
		||||
	prefix: "",
 | 
			
		||||
	theme: {
 | 
			
		||||
		container: {
 | 
			
		||||
			center: true,
 | 
			
		||||
			padding: '2rem',
 | 
			
		||||
			screens: {
 | 
			
		||||
				'2xl': '1400px'
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		extend: {
 | 
			
		||||
			colors: {
 | 
			
		||||
				border: 'hsl(var(--border))',
 | 
			
		||||
				input: 'hsl(var(--input))',
 | 
			
		||||
				ring: 'hsl(var(--ring))',
 | 
			
		||||
				background: 'hsl(var(--background))',
 | 
			
		||||
				foreground: 'hsl(var(--foreground))',
 | 
			
		||||
				primary: {
 | 
			
		||||
					DEFAULT: 'hsl(var(--primary))',
 | 
			
		||||
					foreground: 'hsl(var(--primary-foreground))'
 | 
			
		||||
				},
 | 
			
		||||
				secondary: {
 | 
			
		||||
					DEFAULT: 'hsl(var(--secondary))',
 | 
			
		||||
					foreground: 'hsl(var(--secondary-foreground))'
 | 
			
		||||
				},
 | 
			
		||||
				destructive: {
 | 
			
		||||
					DEFAULT: 'hsl(var(--destructive))',
 | 
			
		||||
					foreground: 'hsl(var(--destructive-foreground))'
 | 
			
		||||
				},
 | 
			
		||||
				muted: {
 | 
			
		||||
					DEFAULT: 'hsl(var(--muted))',
 | 
			
		||||
					foreground: 'hsl(var(--muted-foreground))'
 | 
			
		||||
				},
 | 
			
		||||
				accent: {
 | 
			
		||||
					DEFAULT: 'hsl(var(--accent))',
 | 
			
		||||
					foreground: 'hsl(var(--accent-foreground))'
 | 
			
		||||
				},
 | 
			
		||||
				popover: {
 | 
			
		||||
					DEFAULT: 'hsl(var(--popover))',
 | 
			
		||||
					foreground: 'hsl(var(--popover-foreground))'
 | 
			
		||||
				},
 | 
			
		||||
				card: {
 | 
			
		||||
					DEFAULT: 'hsl(var(--card))',
 | 
			
		||||
					foreground: 'hsl(var(--card-foreground))'
 | 
			
		||||
				},
 | 
			
		||||
				sidebar: {
 | 
			
		||||
					DEFAULT: 'hsl(var(--sidebar-background))',
 | 
			
		||||
					foreground: 'hsl(var(--sidebar-foreground))',
 | 
			
		||||
					primary: 'hsl(var(--sidebar-primary))',
 | 
			
		||||
					'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
 | 
			
		||||
					accent: 'hsl(var(--sidebar-accent))',
 | 
			
		||||
					'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
 | 
			
		||||
					border: 'hsl(var(--sidebar-border))',
 | 
			
		||||
					ring: 'hsl(var(--sidebar-ring))'
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			borderRadius: {
 | 
			
		||||
				lg: 'var(--radius)',
 | 
			
		||||
				md: 'calc(var(--radius) - 2px)',
 | 
			
		||||
				sm: 'calc(var(--radius) - 4px)'
 | 
			
		||||
			},
 | 
			
		||||
			keyframes: {
 | 
			
		||||
				'accordion-down': {
 | 
			
		||||
					from: {
 | 
			
		||||
						height: '0'
 | 
			
		||||
					},
 | 
			
		||||
					to: {
 | 
			
		||||
						height: 'var(--radix-accordion-content-height)'
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				'accordion-up': {
 | 
			
		||||
					from: {
 | 
			
		||||
						height: 'var(--radix-accordion-content-height)'
 | 
			
		||||
					},
 | 
			
		||||
					to: {
 | 
			
		||||
						height: '0'
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			animation: {
 | 
			
		||||
				'accordion-down': 'accordion-down 0.2s ease-out',
 | 
			
		||||
				'accordion-up': 'accordion-up 0.2s ease-out'
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	plugins: [require("tailwindcss-animate")],
 | 
			
		||||
} satisfies Config;
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "ES2020",
 | 
			
		||||
    "useDefineForClassFields": true,
 | 
			
		||||
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
 | 
			
		||||
    /* Bundler mode */
 | 
			
		||||
    "moduleResolution": "bundler",
 | 
			
		||||
    "allowImportingTsExtensions": true,
 | 
			
		||||
    "isolatedModules": true,
 | 
			
		||||
    "moduleDetection": "force",
 | 
			
		||||
    "noEmit": true,
 | 
			
		||||
    "jsx": "react-jsx",
 | 
			
		||||
 | 
			
		||||
    /* Linting */
 | 
			
		||||
    "strict": false,
 | 
			
		||||
    "noUnusedLocals": false,
 | 
			
		||||
    "noUnusedParameters": false,
 | 
			
		||||
    "noImplicitAny": false,
 | 
			
		||||
    "noFallthroughCasesInSwitch": false,
 | 
			
		||||
 | 
			
		||||
    "baseUrl": ".",
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@/*": ["./src/*"]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "include": ["src"]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "files": [],
 | 
			
		||||
  "references": [
 | 
			
		||||
    { "path": "./tsconfig.app.json" },
 | 
			
		||||
    { "path": "./tsconfig.node.json" }
 | 
			
		||||
  ],
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "baseUrl": ".",
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@/*": ["./src/*"]
 | 
			
		||||
    },
 | 
			
		||||
    "noImplicitAny": false,
 | 
			
		||||
    "noUnusedParameters": false,
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "allowJs": true,
 | 
			
		||||
    "noUnusedLocals": false,
 | 
			
		||||
    "strictNullChecks": false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "ES2022",
 | 
			
		||||
    "lib": ["ES2023"],
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
 | 
			
		||||
    /* Bundler mode */
 | 
			
		||||
    "moduleResolution": "bundler",
 | 
			
		||||
    "allowImportingTsExtensions": true,
 | 
			
		||||
    "isolatedModules": true,
 | 
			
		||||
    "moduleDetection": "force",
 | 
			
		||||
    "noEmit": true,
 | 
			
		||||
 | 
			
		||||
    /* Linting */
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "noUnusedLocals": false,
 | 
			
		||||
    "noUnusedParameters": false,
 | 
			
		||||
    "noFallthroughCasesInSwitch": true
 | 
			
		||||
  },
 | 
			
		||||
  "include": ["vite.config.ts"]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								vite.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vite.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
import { defineConfig } from 'vite';
 | 
			
		||||
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
  root: './',
 | 
			
		||||
  base: '/admin/',
 | 
			
		||||
  server: {
 | 
			
		||||
    host: '::',
 | 
			
		||||
    port: 8080,
 | 
			
		||||
    // Enable CORS for API requests
 | 
			
		||||
    cors: true,
 | 
			
		||||
  },
 | 
			
		||||
  build: {
 | 
			
		||||
    outDir: 'dist',
 | 
			
		||||
    assetsDir: 'assets',
 | 
			
		||||
    rollupOptions: {
 | 
			
		||||
      input: {
 | 
			
		||||
        main: './index.html',
 | 
			
		||||
        callback: './oauth-callback.html'
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
import { defineConfig } from "vite";
 | 
			
		||||
import react from "@vitejs/plugin-react-swc";
 | 
			
		||||
import path from "path";
 | 
			
		||||
 | 
			
		||||
// https://vitejs.dev/config/
 | 
			
		||||
export default defineConfig(({ mode }) => ({
 | 
			
		||||
  base: "/admin/",
 | 
			
		||||
  server: {
 | 
			
		||||
    host: "::",
 | 
			
		||||
    port: 8080,
 | 
			
		||||
  },
 | 
			
		||||
  plugins: [
 | 
			
		||||
    react()
 | 
			
		||||
  ].filter(Boolean),
 | 
			
		||||
  resolve: {
 | 
			
		||||
    alias: {
 | 
			
		||||
      "@": path.resolve(__dirname, "./src"),
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
}));
 | 
			
		||||
		Reference in New Issue
	
	Block a user