dronesplace commited on
Commit
2acde71
·
verified ·
1 Parent(s): c072b49

Initial upload

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. .gitignore +24 -0
  3. README.md +73 -11
  4. bun.lockb +3 -0
  5. components.json +20 -0
  6. deploy_space.bat +29 -0
  7. eslint.config.js +29 -0
  8. git +0 -0
  9. index.html +24 -19
  10. main +0 -0
  11. package-lock.json +0 -0
  12. package.json +90 -0
  13. postcss.config.js +6 -0
  14. public/favicon.ico +0 -0
  15. public/placeholder.svg +1 -0
  16. public/robots.txt +14 -0
  17. src/App.css +42 -0
  18. src/App.tsx +27 -0
  19. src/components/WalletProvider.tsx +104 -0
  20. src/components/dashboard/CopyStrategySelector.tsx +126 -0
  21. src/components/dashboard/Dashboard.tsx +182 -0
  22. src/components/dashboard/PerformanceTracker.tsx +235 -0
  23. src/components/dashboard/RiskManagement.tsx +261 -0
  24. src/components/dashboard/TargetWalletManager.tsx +167 -0
  25. src/components/dashboard/TradeLog.tsx +276 -0
  26. src/components/ui/accordion.tsx +56 -0
  27. src/components/ui/alert-dialog.tsx +139 -0
  28. src/components/ui/alert.tsx +59 -0
  29. src/components/ui/aspect-ratio.tsx +5 -0
  30. src/components/ui/avatar.tsx +48 -0
  31. src/components/ui/badge.tsx +36 -0
  32. src/components/ui/breadcrumb.tsx +115 -0
  33. src/components/ui/button.tsx +59 -0
  34. src/components/ui/calendar.tsx +64 -0
  35. src/components/ui/card.tsx +79 -0
  36. src/components/ui/carousel.tsx +260 -0
  37. src/components/ui/chart.tsx +363 -0
  38. src/components/ui/checkbox.tsx +28 -0
  39. src/components/ui/collapsible.tsx +9 -0
  40. src/components/ui/command.tsx +153 -0
  41. src/components/ui/context-menu.tsx +198 -0
  42. src/components/ui/dialog.tsx +120 -0
  43. src/components/ui/drawer.tsx +116 -0
  44. src/components/ui/dropdown-menu.tsx +198 -0
  45. src/components/ui/form.tsx +176 -0
  46. src/components/ui/hover-card.tsx +27 -0
  47. src/components/ui/input-otp.tsx +69 -0
  48. src/components/ui/input.tsx +22 -0
  49. src/components/ui/label.tsx +24 -0
  50. src/components/ui/menubar.tsx +234 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ bun.lockb filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
README.md CHANGED
@@ -1,11 +1,73 @@
1
- ---
2
- title: Solana
3
- emoji: 🏆
4
- colorFrom: gray
5
- colorTo: indigo
6
- sdk: static
7
- pinned: false
8
- short_description: sol
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Welcome to your Lovable project
2
+
3
+ ## Project info
4
+
5
+ **URL**: https://lovable.dev/projects/29510f8d-7090-4a57-b9bb-fcc8a350ca19
6
+
7
+ ## How can I edit this code?
8
+
9
+ There are several ways of editing your application.
10
+
11
+ **Use Lovable**
12
+
13
+ Simply visit the [Lovable Project](https://lovable.dev/projects/29510f8d-7090-4a57-b9bb-fcc8a350ca19) and start prompting.
14
+
15
+ Changes made via Lovable will be committed automatically to this repo.
16
+
17
+ **Use your preferred IDE**
18
+
19
+ If you want to work locally using your own IDE, you can clone this repo and push changes. Pushed changes will also be reflected in Lovable.
20
+
21
+ The only requirement is having Node.js & npm installed - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
22
+
23
+ Follow these steps:
24
+
25
+ ```sh
26
+ # Step 1: Clone the repository using the project's Git URL.
27
+ git clone <YOUR_GIT_URL>
28
+
29
+ # Step 2: Navigate to the project directory.
30
+ cd <YOUR_PROJECT_NAME>
31
+
32
+ # Step 3: Install the necessary dependencies.
33
+ npm i
34
+
35
+ # Step 4: Start the development server with auto-reloading and an instant preview.
36
+ npm run dev
37
+ ```
38
+
39
+ **Edit a file directly in GitHub**
40
+
41
+ - Navigate to the desired file(s).
42
+ - Click the "Edit" button (pencil icon) at the top right of the file view.
43
+ - Make your changes and commit the changes.
44
+
45
+ **Use GitHub Codespaces**
46
+
47
+ - Navigate to the main page of your repository.
48
+ - Click on the "Code" button (green button) near the top right.
49
+ - Select the "Codespaces" tab.
50
+ - Click on "New codespace" to launch a new Codespace environment.
51
+ - Edit files directly within the Codespace and commit and push your changes once you're done.
52
+
53
+ ## What technologies are used for this project?
54
+
55
+ This project is built with:
56
+
57
+ - Vite
58
+ - TypeScript
59
+ - React
60
+ - shadcn-ui
61
+ - Tailwind CSS
62
+
63
+ ## How can I deploy this project?
64
+
65
+ Simply open [Lovable](https://lovable.dev/projects/29510f8d-7090-4a57-b9bb-fcc8a350ca19) and click on Share -> Publish.
66
+
67
+ ## Can I connect a custom domain to my Lovable project?
68
+
69
+ Yes, you can!
70
+
71
+ To connect a domain, navigate to Project > Settings > Domains and click Connect Domain.
72
+
73
+ Read more here: [Setting up a custom domain](https://docs.lovable.dev/tips-tricks/custom-domain#step-by-step-guide)
bun.lockb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:570b2bffe5052345e4a55eddc0077d6cef9b44f618123e9d746f4230bab0f298
3
+ size 197327
components.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "src/index.css",
9
+ "baseColor": "slate",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ }
20
+ }
deploy_space.bat ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ REM =====================================================
3
+ REM Hugging Face Space Deployment Script (Windows)
4
+ REM =====================================================
5
+
6
+ REM Step 1: Ask user for Hugging Face write-access token
7
+ set /p HF_TOKEN="Enter your Hugging Face write-access token: "
8
+
9
+ REM Step 2: Ask for Space name and SDK type
10
+ set /p SPACE_NAME="Enter the name of the Space (e.g., solana): "
11
+ set /p SPACE_SDK="Enter Space SDK (gradio / streamlit / docker / static): "
12
+
13
+ REM Step 3: Ask for your HF username (from hf auth whoami)
14
+ set /p HF_USER="Enter your Hugging Face username (from hf auth whoami): "
15
+
16
+ REM Step 4: Create the Space repo
17
+ hf repo create %HF_USER%/%SPACE_NAME% --repo-type space --space-sdk %SPACE_SDK% --token %HF_TOKEN%
18
+
19
+ REM Step 5: Upload the local folder to the Space
20
+ hf upload %HF_USER%/%SPACE_NAME% . . --repo-type space --commit-message "Initial upload" --token %HF_TOKEN%
21
+
22
+ REM Step 6: Print the deployed Space URL
23
+ echo.
24
+ echo =====================================================
25
+ echo Deployment complete!
26
+ echo Your Hugging Face Space is at:
27
+ echo https://huggingface.co/spaces/%HF_USER%/%SPACE_NAME%
28
+ echo =====================================================
29
+ pause
eslint.config.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from "@eslint/js";
2
+ import globals from "globals";
3
+ import reactHooks from "eslint-plugin-react-hooks";
4
+ import reactRefresh from "eslint-plugin-react-refresh";
5
+ import tseslint from "typescript-eslint";
6
+
7
+ export default tseslint.config(
8
+ { ignores: ["dist"] },
9
+ {
10
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
+ files: ["**/*.{ts,tsx}"],
12
+ languageOptions: {
13
+ ecmaVersion: 2020,
14
+ globals: globals.browser,
15
+ },
16
+ plugins: {
17
+ "react-hooks": reactHooks,
18
+ "react-refresh": reactRefresh,
19
+ },
20
+ rules: {
21
+ ...reactHooks.configs.recommended.rules,
22
+ "react-refresh/only-export-components": [
23
+ "warn",
24
+ { allowConstantExport: true },
25
+ ],
26
+ "@typescript-eslint/no-unused-vars": "off",
27
+ },
28
+ }
29
+ );
git ADDED
File without changes
index.html CHANGED
@@ -1,19 +1,24 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Solana Copy Trading Bot</title>
7
+ <meta name="description" content="Automated copy trading bot for Solana blockchain with real-time monitoring and risk management" />
8
+ <meta name="author" content="Solana Copy Trading" />
9
+
10
+ <meta property="og:title" content="Solana Copy Trading Bot" />
11
+ <meta property="og:description" content="Automated copy trading bot for Solana blockchain with real-time monitoring and risk management" />
12
+ <meta property="og:type" content="website" />
13
+ <meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
14
+
15
+ <meta name="twitter:card" content="summary_large_image" />
16
+ <meta name="twitter:site" content="@lovable_dev" />
17
+ <meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
18
+ </head>
19
+
20
+ <body>
21
+ <div id="root"></div>
22
+ <script type="module" src="/src/main.tsx"></script>
23
+ </body>
24
+ </html>
main ADDED
File without changes
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "vite_react_shadcn_ts",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "build:dev": "vite build --mode development",
10
+ "lint": "eslint .",
11
+ "preview": "vite preview"
12
+ },
13
+ "dependencies": {
14
+ "@hookform/resolvers": "^3.10.0",
15
+ "@radix-ui/react-accordion": "^1.2.11",
16
+ "@radix-ui/react-alert-dialog": "^1.1.14",
17
+ "@radix-ui/react-aspect-ratio": "^1.1.7",
18
+ "@radix-ui/react-avatar": "^1.1.10",
19
+ "@radix-ui/react-checkbox": "^1.3.2",
20
+ "@radix-ui/react-collapsible": "^1.1.11",
21
+ "@radix-ui/react-context-menu": "^2.2.15",
22
+ "@radix-ui/react-dialog": "^1.1.14",
23
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
24
+ "@radix-ui/react-hover-card": "^1.1.14",
25
+ "@radix-ui/react-label": "^2.1.7",
26
+ "@radix-ui/react-menubar": "^1.1.15",
27
+ "@radix-ui/react-navigation-menu": "^1.2.13",
28
+ "@radix-ui/react-popover": "^1.1.14",
29
+ "@radix-ui/react-progress": "^1.1.7",
30
+ "@radix-ui/react-radio-group": "^1.3.7",
31
+ "@radix-ui/react-scroll-area": "^1.2.9",
32
+ "@radix-ui/react-select": "^2.2.5",
33
+ "@radix-ui/react-separator": "^1.1.7",
34
+ "@radix-ui/react-slider": "^1.3.5",
35
+ "@radix-ui/react-slot": "^1.2.3",
36
+ "@radix-ui/react-switch": "^1.2.5",
37
+ "@radix-ui/react-tabs": "^1.1.12",
38
+ "@radix-ui/react-toast": "^1.2.14",
39
+ "@radix-ui/react-toggle": "^1.1.9",
40
+ "@radix-ui/react-toggle-group": "^1.1.10",
41
+ "@radix-ui/react-tooltip": "^1.2.7",
42
+ "@solana/wallet-adapter-base": "^0.9.27",
43
+ "@solana/wallet-adapter-react": "^0.15.39",
44
+ "@solana/wallet-adapter-react-ui": "^0.9.39",
45
+ "@solana/wallet-adapter-wallets": "^0.19.37",
46
+ "@solana/web3.js": "^1.98.4",
47
+ "@supabase/supabase-js": "^2.53.0",
48
+ "@tanstack/react-query": "^5.83.0",
49
+ "bs58": "^6.0.0",
50
+ "class-variance-authority": "^0.7.1",
51
+ "clsx": "^2.1.1",
52
+ "cmdk": "^1.1.1",
53
+ "date-fns": "^3.6.0",
54
+ "embla-carousel-react": "^8.6.0",
55
+ "input-otp": "^1.4.2",
56
+ "lucide-react": "^0.462.0",
57
+ "next-themes": "^0.3.0",
58
+ "react": "^18.3.1",
59
+ "react-day-picker": "^8.10.1",
60
+ "react-dom": "^18.3.1",
61
+ "react-hook-form": "^7.61.1",
62
+ "react-resizable-panels": "^2.1.9",
63
+ "react-router-dom": "^6.30.1",
64
+ "recharts": "^2.15.4",
65
+ "sonner": "^1.7.4",
66
+ "tailwind-merge": "^2.6.0",
67
+ "tailwindcss-animate": "^1.0.7",
68
+ "vaul": "^0.9.9",
69
+ "zod": "^3.25.76"
70
+ },
71
+ "devDependencies": {
72
+ "@eslint/js": "^9.32.0",
73
+ "@tailwindcss/typography": "^0.5.16",
74
+ "@types/node": "^22.16.5",
75
+ "@types/react": "^18.3.23",
76
+ "@types/react-dom": "^18.3.7",
77
+ "@vitejs/plugin-react-swc": "^3.11.0",
78
+ "autoprefixer": "^10.4.21",
79
+ "eslint": "^9.32.0",
80
+ "eslint-plugin-react-hooks": "^5.2.0",
81
+ "eslint-plugin-react-refresh": "^0.4.20",
82
+ "globals": "^15.15.0",
83
+ "lovable-tagger": "^1.1.8",
84
+ "postcss": "^8.5.6",
85
+ "tailwindcss": "^3.4.17",
86
+ "typescript": "^5.8.3",
87
+ "typescript-eslint": "^8.38.0",
88
+ "vite": "^5.4.19"
89
+ }
90
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
public/favicon.ico ADDED
public/placeholder.svg ADDED
public/robots.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ User-agent: Googlebot
2
+ Allow: /
3
+
4
+ User-agent: Bingbot
5
+ Allow: /
6
+
7
+ User-agent: Twitterbot
8
+ Allow: /
9
+
10
+ User-agent: facebookexternalhit
11
+ Allow: /
12
+
13
+ User-agent: *
14
+ Allow: /
src/App.css ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ transition: filter 300ms;
13
+ }
14
+ .logo:hover {
15
+ filter: drop-shadow(0 0 2em #646cffaa);
16
+ }
17
+ .logo.react:hover {
18
+ filter: drop-shadow(0 0 2em #61dafbaa);
19
+ }
20
+
21
+ @keyframes logo-spin {
22
+ from {
23
+ transform: rotate(0deg);
24
+ }
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
30
+ @media (prefers-reduced-motion: no-preference) {
31
+ a:nth-of-type(2) .logo {
32
+ animation: logo-spin infinite 20s linear;
33
+ }
34
+ }
35
+
36
+ .card {
37
+ padding: 2em;
38
+ }
39
+
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
src/App.tsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Toaster } from "@/components/ui/toaster";
2
+ import { Toaster as Sonner } from "@/components/ui/sonner";
3
+ import { TooltipProvider } from "@/components/ui/tooltip";
4
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5
+ import { BrowserRouter, Routes, Route } from "react-router-dom";
6
+ import Index from "./pages/Index";
7
+ import NotFound from "./pages/NotFound";
8
+
9
+ const queryClient = new QueryClient();
10
+
11
+ const App = () => (
12
+ <QueryClientProvider client={queryClient}>
13
+ <TooltipProvider>
14
+ <Toaster />
15
+ <Sonner />
16
+ <BrowserRouter>
17
+ <Routes>
18
+ <Route path="/" element={<Index />} />
19
+ {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
20
+ <Route path="*" element={<NotFound />} />
21
+ </Routes>
22
+ </BrowserRouter>
23
+ </TooltipProvider>
24
+ </QueryClientProvider>
25
+ );
26
+
27
+ export default App;
src/components/WalletProvider.tsx ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { FC, ReactNode, useMemo, useCallback, useEffect } from 'react';
2
+ import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
3
+ import { WalletAdapterNetwork, WalletError } from '@solana/wallet-adapter-base';
4
+ import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
5
+ import {
6
+ PhantomWalletAdapter,
7
+ SolflareWalletAdapter,
8
+ TorusWalletAdapter,
9
+ } from '@solana/wallet-adapter-wallets';
10
+ import { clusterApiUrl } from '@solana/web3.js';
11
+ import { toast } from 'sonner';
12
+
13
+ // Import the CSS for wallet modal
14
+ import '@solana/wallet-adapter-react-ui/styles.css';
15
+
16
+ interface WalletContextProviderProps {
17
+ children: ReactNode;
18
+ }
19
+
20
+ export const WalletContextProvider: FC<WalletContextProviderProps> = ({ children }) => {
21
+ // Use devnet for development and testing
22
+ const network = WalletAdapterNetwork.Devnet;
23
+ const endpoint = useMemo(() => clusterApiUrl(network), [network]);
24
+
25
+ // Debug wallet detection
26
+ useEffect(() => {
27
+ console.log('Checking wallet availability...');
28
+ console.log('window.solana:', typeof window !== 'undefined' ? (window as any).solana : 'window unavailable');
29
+ console.log('window.phantom:', typeof window !== 'undefined' ? (window as any).phantom : 'window.phantom unavailable');
30
+
31
+ if (typeof window !== 'undefined') {
32
+ // Check if Phantom is installed
33
+ const windowAny = window as any;
34
+ const isPhantomInstalled = windowAny.solana?.isPhantom || windowAny.phantom?.solana?.isPhantom;
35
+ console.log('Phantom detected:', isPhantomInstalled);
36
+
37
+ if (!isPhantomInstalled) {
38
+ console.warn('Phantom wallet not detected. Please install Phantom wallet extension.');
39
+ }
40
+ }
41
+ }, []);
42
+
43
+ const wallets = useMemo(
44
+ () => {
45
+ console.log('Initializing wallet adapters...');
46
+ return [
47
+ new PhantomWalletAdapter(),
48
+ new SolflareWalletAdapter(),
49
+ new TorusWalletAdapter(),
50
+ ];
51
+ },
52
+ []
53
+ );
54
+
55
+ const onError = useCallback(
56
+ (error: WalletError) => {
57
+ console.error('Wallet error details:', {
58
+ name: error.name,
59
+ message: error.message,
60
+ stack: error.stack
61
+ });
62
+
63
+ if (error.name === 'WalletNotReadyError') {
64
+ console.log('WalletNotReadyError - checking if Phantom is properly installed...');
65
+
66
+ // Give user clear instructions
67
+ toast.error('Wallet Not Ready', {
68
+ description: 'Please install Phantom wallet extension and refresh the page.',
69
+ duration: 6000,
70
+ action: {
71
+ label: 'Install Phantom',
72
+ onClick: () => window.open('https://phantom.app/', '_blank'),
73
+ },
74
+ });
75
+ } else if (error.name === 'WalletConnectionError') {
76
+ toast.error('Connection Failed', {
77
+ description: 'Please try connecting again or refresh the page.',
78
+ duration: 4000,
79
+ });
80
+ } else {
81
+ toast.error('Wallet Error', {
82
+ description: error.message || 'An unexpected error occurred.',
83
+ duration: 4000,
84
+ });
85
+ }
86
+ },
87
+ []
88
+ );
89
+
90
+ return (
91
+ <ConnectionProvider endpoint={endpoint}>
92
+ <WalletProvider
93
+ wallets={wallets}
94
+ onError={onError}
95
+ autoConnect={false}
96
+ localStorageKey="walletAdapter"
97
+ >
98
+ <WalletModalProvider>
99
+ {children}
100
+ </WalletModalProvider>
101
+ </WalletProvider>
102
+ </ConnectionProvider>
103
+ );
104
+ };
src/components/dashboard/CopyStrategySelector.tsx ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
3
+ import { Label } from '@/components/ui/label';
4
+ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
5
+ import { Badge } from '@/components/ui/badge';
6
+ import { TrendingUp, DollarSign, Shield, Zap } from 'lucide-react';
7
+
8
+ interface CopyStrategyProps {
9
+ selectedStrategy: string;
10
+ setSelectedStrategy: (strategy: string) => void;
11
+ }
12
+
13
+ const strategies = [
14
+ {
15
+ id: 'proportional',
16
+ name: 'Proportional Allocation',
17
+ description: 'Copy trades proportionally based on your wallet size vs target wallet',
18
+ icon: TrendingUp,
19
+ badge: 'Recommended',
20
+ badgeVariant: 'default' as const,
21
+ example: 'Target trades $100, you have $1000 (10x), you trade $10'
22
+ },
23
+ {
24
+ id: 'fixed',
25
+ name: 'Fixed Amount',
26
+ description: 'Trade a fixed amount for every copied trade regardless of target size',
27
+ icon: DollarSign,
28
+ badge: 'Simple',
29
+ badgeVariant: 'secondary' as const,
30
+ example: 'Every trade executes with your preset amount (e.g., $50)'
31
+ },
32
+ {
33
+ id: 'risk-adjusted',
34
+ name: 'Risk-Adjusted Copying',
35
+ description: 'Adjust trade size based on target wallet performance and risk metrics',
36
+ icon: Shield,
37
+ badge: 'Advanced',
38
+ badgeVariant: 'outline' as const,
39
+ example: 'Higher performing wallets get larger allocations'
40
+ },
41
+ {
42
+ id: 'momentum',
43
+ name: 'Momentum Based',
44
+ description: 'Increase allocation size during winning streaks, decrease during losses',
45
+ icon: Zap,
46
+ badge: 'Dynamic',
47
+ badgeVariant: 'outline' as const,
48
+ example: 'Trade size adjusts based on recent performance trends'
49
+ }
50
+ ];
51
+
52
+ export const CopyStrategySelector: React.FC<CopyStrategyProps> = ({
53
+ selectedStrategy,
54
+ setSelectedStrategy
55
+ }) => {
56
+ return (
57
+ <Card className="bg-gradient-card border-border/50">
58
+ <CardHeader>
59
+ <CardTitle className="flex items-center gap-2">
60
+ <TrendingUp className="h-5 w-5 text-primary" />
61
+ Copy Strategy
62
+ </CardTitle>
63
+ </CardHeader>
64
+ <CardContent>
65
+ <RadioGroup
66
+ value={selectedStrategy}
67
+ onValueChange={setSelectedStrategy}
68
+ className="space-y-4"
69
+ >
70
+ {strategies.map((strategy, index) => {
71
+ const IconComponent = strategy.icon;
72
+ return (
73
+ <div
74
+ key={strategy.id}
75
+ className={`p-4 rounded-lg border cursor-pointer transition-all duration-300 animate-fade-in ${
76
+ selectedStrategy === strategy.id
77
+ ? 'border-primary bg-primary/5 shadow-lg'
78
+ : 'border-border bg-secondary/50 hover:border-primary/50'
79
+ }`}
80
+ style={{ animationDelay: `${index * 0.1}s` }}
81
+ onClick={() => setSelectedStrategy(strategy.id)}
82
+ >
83
+ <div className="flex items-start space-x-3">
84
+ <RadioGroupItem
85
+ value={strategy.id}
86
+ id={strategy.id}
87
+ className="mt-1"
88
+ />
89
+ <div className="flex-1 space-y-2">
90
+ <div className="flex items-center gap-2">
91
+ <IconComponent className="h-4 w-4 text-primary" />
92
+ <Label
93
+ htmlFor={strategy.id}
94
+ className="font-medium cursor-pointer"
95
+ >
96
+ {strategy.name}
97
+ </Label>
98
+ <Badge variant={strategy.badgeVariant}>
99
+ {strategy.badge}
100
+ </Badge>
101
+ </div>
102
+ <p className="text-sm text-muted-foreground">
103
+ {strategy.description}
104
+ </p>
105
+ <div className="bg-muted/50 p-2 rounded text-xs">
106
+ <span className="font-medium">Example: </span>
107
+ {strategy.example}
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ );
113
+ })}
114
+ </RadioGroup>
115
+
116
+ {!selectedStrategy && (
117
+ <div className="mt-4 p-3 bg-warning/10 border border-warning/20 rounded-lg">
118
+ <p className="text-sm text-warning-foreground">
119
+ Please select a copy strategy to continue
120
+ </p>
121
+ </div>
122
+ )}
123
+ </CardContent>
124
+ </Card>
125
+ );
126
+ };
src/components/dashboard/Dashboard.tsx ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
3
+ import { Button } from '@/components/ui/button';
4
+ import { Input } from '@/components/ui/input';
5
+ import { Label } from '@/components/ui/label';
6
+ import { Badge } from '@/components/ui/badge';
7
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
8
+ import { WalletButton } from '@/components/ui/wallet-button';
9
+ import { TargetWalletManager } from './TargetWalletManager';
10
+ import { CopyStrategySelector } from './CopyStrategySelector';
11
+ import { RiskManagement } from './RiskManagement';
12
+ import { PerformanceTracker } from './PerformanceTracker';
13
+ import { TradeLog } from './TradeLog';
14
+ import {
15
+ TrendingUp,
16
+ TrendingDown,
17
+ DollarSign,
18
+ Activity,
19
+ Target,
20
+ Shield,
21
+ BarChart3,
22
+ Settings,
23
+ Play,
24
+ Pause
25
+ } from 'lucide-react';
26
+
27
+ export const Dashboard: React.FC = () => {
28
+ const [isActive, setIsActive] = useState(false);
29
+ const [targetWallets, setTargetWallets] = useState<string[]>([]);
30
+ const [selectedStrategy, setSelectedStrategy] = useState<string>('');
31
+
32
+ const stats = {
33
+ totalPnL: 1247.89,
34
+ todayPnL: 156.23,
35
+ totalTrades: 42,
36
+ successRate: 68.5,
37
+ activeCopies: 3
38
+ };
39
+
40
+ return (
41
+ <div className="min-h-screen bg-background p-6">
42
+ <div className="max-w-7xl mx-auto space-y-6">
43
+ {/* Header */}
44
+ <div className="flex items-center justify-between">
45
+ <div>
46
+ <h1 className="text-3xl font-bold bg-gradient-primary bg-clip-text text-transparent">
47
+ Solana Copy Trading Bot
48
+ </h1>
49
+ <p className="text-muted-foreground">
50
+ Automated trading on Solana devnet
51
+ </p>
52
+ </div>
53
+ <div className="flex items-center gap-4">
54
+ <WalletButton variant="hero" />
55
+ <Button
56
+ onClick={() => setIsActive(!isActive)}
57
+ variant={isActive ? "destructive" : "default"}
58
+ className="gap-2 animate-pulse-glow"
59
+ >
60
+ {isActive ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
61
+ {isActive ? 'Stop Trading' : 'Start Trading'}
62
+ </Button>
63
+ </div>
64
+ </div>
65
+
66
+ {/* Stats Overview */}
67
+ <div className="grid grid-cols-1 md:grid-cols-5 gap-4">
68
+ <Card className="bg-gradient-card border-border/50">
69
+ <CardContent className="p-4">
70
+ <div className="flex items-center gap-2">
71
+ <DollarSign className="h-5 w-5 text-profit" />
72
+ <div>
73
+ <p className="text-sm text-muted-foreground">Total P&L</p>
74
+ <p className={`text-lg font-bold ${stats.totalPnL >= 0 ? 'text-profit' : 'text-loss'}`}>
75
+ ${stats.totalPnL.toFixed(2)}
76
+ </p>
77
+ </div>
78
+ </div>
79
+ </CardContent>
80
+ </Card>
81
+
82
+ <Card className="bg-gradient-card border-border/50">
83
+ <CardContent className="p-4">
84
+ <div className="flex items-center gap-2">
85
+ <TrendingUp className="h-5 w-5 text-info" />
86
+ <div>
87
+ <p className="text-sm text-muted-foreground">Today P&L</p>
88
+ <p className={`text-lg font-bold ${stats.todayPnL >= 0 ? 'text-profit' : 'text-loss'}`}>
89
+ ${stats.todayPnL.toFixed(2)}
90
+ </p>
91
+ </div>
92
+ </div>
93
+ </CardContent>
94
+ </Card>
95
+
96
+ <Card className="bg-gradient-card border-border/50">
97
+ <CardContent className="p-4">
98
+ <div className="flex items-center gap-2">
99
+ <Activity className="h-5 w-5 text-warning" />
100
+ <div>
101
+ <p className="text-sm text-muted-foreground">Total Trades</p>
102
+ <p className="text-lg font-bold">{stats.totalTrades}</p>
103
+ </div>
104
+ </div>
105
+ </CardContent>
106
+ </Card>
107
+
108
+ <Card className="bg-gradient-card border-border/50">
109
+ <CardContent className="p-4">
110
+ <div className="flex items-center gap-2">
111
+ <BarChart3 className="h-5 w-5 text-accent" />
112
+ <div>
113
+ <p className="text-sm text-muted-foreground">Success Rate</p>
114
+ <p className="text-lg font-bold text-profit">{stats.successRate}%</p>
115
+ </div>
116
+ </div>
117
+ </CardContent>
118
+ </Card>
119
+
120
+ <Card className="bg-gradient-card border-border/50">
121
+ <CardContent className="p-4">
122
+ <div className="flex items-center gap-2">
123
+ <Target className="h-5 w-5 text-primary" />
124
+ <div>
125
+ <p className="text-sm text-muted-foreground">Active Copies</p>
126
+ <p className="text-lg font-bold">{stats.activeCopies}</p>
127
+ </div>
128
+ </div>
129
+ </CardContent>
130
+ </Card>
131
+ </div>
132
+
133
+ {/* Main Content */}
134
+ <Tabs defaultValue="setup" className="space-y-6">
135
+ <TabsList className="grid w-full grid-cols-4">
136
+ <TabsTrigger value="setup" className="gap-2">
137
+ <Settings className="h-4 w-4" />
138
+ Setup
139
+ </TabsTrigger>
140
+ <TabsTrigger value="performance" className="gap-2">
141
+ <BarChart3 className="h-4 w-4" />
142
+ Performance
143
+ </TabsTrigger>
144
+ <TabsTrigger value="trades" className="gap-2">
145
+ <Activity className="h-4 w-4" />
146
+ Trade Log
147
+ </TabsTrigger>
148
+ <TabsTrigger value="risk" className="gap-2">
149
+ <Shield className="h-4 w-4" />
150
+ Risk
151
+ </TabsTrigger>
152
+ </TabsList>
153
+
154
+ <TabsContent value="setup" className="space-y-6">
155
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
156
+ <TargetWalletManager
157
+ wallets={targetWallets}
158
+ setWallets={setTargetWallets}
159
+ />
160
+ <CopyStrategySelector
161
+ selectedStrategy={selectedStrategy}
162
+ setSelectedStrategy={setSelectedStrategy}
163
+ />
164
+ </div>
165
+ </TabsContent>
166
+
167
+ <TabsContent value="performance">
168
+ <PerformanceTracker />
169
+ </TabsContent>
170
+
171
+ <TabsContent value="trades">
172
+ <TradeLog />
173
+ </TabsContent>
174
+
175
+ <TabsContent value="risk">
176
+ <RiskManagement />
177
+ </TabsContent>
178
+ </Tabs>
179
+ </div>
180
+ </div>
181
+ );
182
+ };
src/components/dashboard/PerformanceTracker.tsx ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
3
+ import { Badge } from '@/components/ui/badge';
4
+ import {
5
+ BarChart,
6
+ Bar,
7
+ XAxis,
8
+ YAxis,
9
+ CartesianGrid,
10
+ Tooltip,
11
+ ResponsiveContainer,
12
+ LineChart,
13
+ Line,
14
+ PieChart,
15
+ Pie,
16
+ Cell
17
+ } from 'recharts';
18
+ import {
19
+ TrendingUp,
20
+ TrendingDown,
21
+ Activity,
22
+ Target,
23
+ DollarSign,
24
+ Percent
25
+ } from 'lucide-react';
26
+
27
+ // Mock data for demonstration
28
+ const performanceData = [
29
+ { date: '2024-01-01', profit: 120, loss: -30, trades: 8 },
30
+ { date: '2024-01-02', profit: 85, loss: -45, trades: 12 },
31
+ { date: '2024-01-03', profit: 95, loss: -20, trades: 6 },
32
+ { date: '2024-01-04', profit: 140, loss: -60, trades: 15 },
33
+ { date: '2024-01-05', profit: 110, loss: -25, trades: 9 },
34
+ { date: '2024-01-06', profit: 165, loss: -40, trades: 11 },
35
+ { date: '2024-01-07', profit: 190, loss: -55, trades: 14 }
36
+ ];
37
+
38
+ const portfolioData = [
39
+ { name: 'SOL', value: 45, color: '#00D4AA' },
40
+ { name: 'USDC', value: 30, color: '#3B82F6' },
41
+ { name: 'RAY', value: 15, color: '#8B5CF6' },
42
+ { name: 'Others', value: 10, color: '#F59E0B' }
43
+ ];
44
+
45
+ const tradingPairs = [
46
+ { pair: 'SOL/USDC', trades: 24, pnl: 340.50, winRate: 75 },
47
+ { pair: 'RAY/SOL', trades: 18, pnl: -125.30, winRate: 44 },
48
+ { pair: 'ORCA/USDC', trades: 12, pnl: 89.20, winRate: 67 },
49
+ { pair: 'BONK/SOL', trades: 8, pnl: 156.80, winRate: 88 }
50
+ ];
51
+
52
+ export const PerformanceTracker: React.FC = () => {
53
+ const totalPnL = performanceData.reduce((acc, day) => acc + day.profit + day.loss, 0);
54
+ const totalTrades = performanceData.reduce((acc, day) => acc + day.trades, 0);
55
+ const winningTrades = Math.floor(totalTrades * 0.685); // 68.5% win rate
56
+
57
+ return (
58
+ <div className="space-y-6">
59
+ {/* Performance Overview */}
60
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
61
+ <Card className="bg-gradient-card border-border/50">
62
+ <CardHeader>
63
+ <CardTitle className="flex items-center gap-2">
64
+ <TrendingUp className="h-5 w-5 text-primary" />
65
+ Daily P&L Trend
66
+ </CardTitle>
67
+ </CardHeader>
68
+ <CardContent>
69
+ <ResponsiveContainer width="100%" height={300}>
70
+ <BarChart data={performanceData}>
71
+ <CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
72
+ <XAxis
73
+ dataKey="date"
74
+ stroke="hsl(var(--muted-foreground))"
75
+ fontSize={12}
76
+ tickFormatter={(value) => new Date(value).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
77
+ />
78
+ <YAxis stroke="hsl(var(--muted-foreground))" fontSize={12} />
79
+ <Tooltip
80
+ contentStyle={{
81
+ backgroundColor: 'hsl(var(--card))',
82
+ border: '1px solid hsl(var(--border))',
83
+ borderRadius: 'var(--radius)'
84
+ }}
85
+ />
86
+ <Bar dataKey="profit" fill="hsl(var(--profit))" />
87
+ <Bar dataKey="loss" fill="hsl(var(--loss))" />
88
+ </BarChart>
89
+ </ResponsiveContainer>
90
+ </CardContent>
91
+ </Card>
92
+
93
+ <Card className="bg-gradient-card border-border/50">
94
+ <CardHeader>
95
+ <CardTitle className="flex items-center gap-2">
96
+ <Activity className="h-5 w-5 text-primary" />
97
+ Portfolio Allocation
98
+ </CardTitle>
99
+ </CardHeader>
100
+ <CardContent>
101
+ <ResponsiveContainer width="100%" height={300}>
102
+ <PieChart>
103
+ <Pie
104
+ data={portfolioData}
105
+ cx="50%"
106
+ cy="50%"
107
+ outerRadius={80}
108
+ dataKey="value"
109
+ >
110
+ {portfolioData.map((entry, index) => (
111
+ <Cell key={`cell-${index}`} fill={entry.color} />
112
+ ))}
113
+ </Pie>
114
+ <Tooltip
115
+ contentStyle={{
116
+ backgroundColor: 'hsl(var(--card))',
117
+ border: '1px solid hsl(var(--border))',
118
+ borderRadius: 'var(--radius)'
119
+ }}
120
+ />
121
+ </PieChart>
122
+ </ResponsiveContainer>
123
+ <div className="grid grid-cols-2 gap-2 mt-4">
124
+ {portfolioData.map((item) => (
125
+ <div key={item.name} className="flex items-center gap-2">
126
+ <div
127
+ className="w-3 h-3 rounded-full"
128
+ style={{ backgroundColor: item.color }}
129
+ />
130
+ <span className="text-sm">{item.name}</span>
131
+ <span className="text-sm text-muted-foreground">{item.value}%</span>
132
+ </div>
133
+ ))}
134
+ </div>
135
+ </CardContent>
136
+ </Card>
137
+ </div>
138
+
139
+ {/* Trading Pairs Performance */}
140
+ <Card className="bg-gradient-card border-border/50">
141
+ <CardHeader>
142
+ <CardTitle className="flex items-center gap-2">
143
+ <Target className="h-5 w-5 text-primary" />
144
+ Trading Pairs Performance
145
+ </CardTitle>
146
+ </CardHeader>
147
+ <CardContent>
148
+ <div className="space-y-4">
149
+ {tradingPairs.map((pair, index) => (
150
+ <div
151
+ key={pair.pair}
152
+ className="flex items-center justify-between p-4 rounded-lg border bg-secondary/30 animate-fade-in"
153
+ style={{ animationDelay: `${index * 0.1}s` }}
154
+ >
155
+ <div className="flex items-center gap-4">
156
+ <div className="font-mono font-semibold">{pair.pair}</div>
157
+ <Badge variant="outline" className="text-xs">
158
+ {pair.trades} trades
159
+ </Badge>
160
+ </div>
161
+ <div className="flex items-center gap-6">
162
+ <div className="text-right">
163
+ <div className={`font-bold ${pair.pnl >= 0 ? 'text-profit' : 'text-loss'}`}>
164
+ {pair.pnl >= 0 ? '+' : ''}${pair.pnl.toFixed(2)}
165
+ </div>
166
+ <div className="text-xs text-muted-foreground">P&L</div>
167
+ </div>
168
+ <div className="text-right">
169
+ <div className={`font-bold ${pair.winRate >= 60 ? 'text-profit' : pair.winRate >= 40 ? 'text-warning' : 'text-loss'}`}>
170
+ {pair.winRate}%
171
+ </div>
172
+ <div className="text-xs text-muted-foreground">Win Rate</div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ ))}
177
+ </div>
178
+ </CardContent>
179
+ </Card>
180
+
181
+ {/* Summary Stats */}
182
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
183
+ <Card className="bg-gradient-card border-border/50">
184
+ <CardContent className="p-4">
185
+ <div className="flex items-center gap-2">
186
+ <DollarSign className="h-5 w-5 text-profit" />
187
+ <div>
188
+ <p className="text-sm text-muted-foreground">Total P&L</p>
189
+ <p className={`text-lg font-bold ${totalPnL >= 0 ? 'text-profit' : 'text-loss'}`}>
190
+ {totalPnL >= 0 ? '+' : ''}${totalPnL.toFixed(2)}
191
+ </p>
192
+ </div>
193
+ </div>
194
+ </CardContent>
195
+ </Card>
196
+
197
+ <Card className="bg-gradient-card border-border/50">
198
+ <CardContent className="p-4">
199
+ <div className="flex items-center gap-2">
200
+ <Activity className="h-5 w-5 text-info" />
201
+ <div>
202
+ <p className="text-sm text-muted-foreground">Total Trades</p>
203
+ <p className="text-lg font-bold">{totalTrades}</p>
204
+ </div>
205
+ </div>
206
+ </CardContent>
207
+ </Card>
208
+
209
+ <Card className="bg-gradient-card border-border/50">
210
+ <CardContent className="p-4">
211
+ <div className="flex items-center gap-2">
212
+ <TrendingUp className="h-5 w-5 text-profit" />
213
+ <div>
214
+ <p className="text-sm text-muted-foreground">Winning Trades</p>
215
+ <p className="text-lg font-bold text-profit">{winningTrades}</p>
216
+ </div>
217
+ </div>
218
+ </CardContent>
219
+ </Card>
220
+
221
+ <Card className="bg-gradient-card border-border/50">
222
+ <CardContent className="p-4">
223
+ <div className="flex items-center gap-2">
224
+ <Percent className="h-5 w-5 text-accent" />
225
+ <div>
226
+ <p className="text-sm text-muted-foreground">Win Rate</p>
227
+ <p className="text-lg font-bold text-profit">68.5%</p>
228
+ </div>
229
+ </div>
230
+ </CardContent>
231
+ </Card>
232
+ </div>
233
+ </div>
234
+ );
235
+ };
src/components/dashboard/RiskManagement.tsx ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
3
+ import { Label } from '@/components/ui/label';
4
+ import { Input } from '@/components/ui/input';
5
+ import { Switch } from '@/components/ui/switch';
6
+ import { Slider } from '@/components/ui/slider';
7
+ import { Button } from '@/components/ui/button';
8
+ import { Shield, AlertTriangle, TrendingDown, TrendingUp, DollarSign } from 'lucide-react';
9
+ import { useToast } from '@/hooks/use-toast';
10
+
11
+ export const RiskManagement: React.FC = () => {
12
+ const [settings, setSettings] = useState({
13
+ stopLossEnabled: true,
14
+ stopLossPercentage: [5],
15
+ takeProfitEnabled: true,
16
+ takeProfitPercentage: [15],
17
+ maxPositionSize: [20],
18
+ maxDailyLoss: [100],
19
+ enableTrailingStop: false,
20
+ trailingStopPercentage: [3],
21
+ maxConcurrentTrades: [5]
22
+ });
23
+
24
+ const { toast } = useToast();
25
+
26
+ const handleSaveSettings = () => {
27
+ toast({
28
+ title: "Risk Settings Saved",
29
+ description: "Your risk management preferences have been updated",
30
+ });
31
+ };
32
+
33
+ const handleResetToDefaults = () => {
34
+ setSettings({
35
+ stopLossEnabled: true,
36
+ stopLossPercentage: [5],
37
+ takeProfitEnabled: true,
38
+ takeProfitPercentage: [15],
39
+ maxPositionSize: [20],
40
+ maxDailyLoss: [100],
41
+ enableTrailingStop: false,
42
+ trailingStopPercentage: [3],
43
+ maxConcurrentTrades: [5]
44
+ });
45
+ toast({
46
+ title: "Settings Reset",
47
+ description: "Risk management settings have been reset to defaults",
48
+ });
49
+ };
50
+
51
+ return (
52
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
53
+ <Card className="bg-gradient-card border-border/50">
54
+ <CardHeader>
55
+ <CardTitle className="flex items-center gap-2">
56
+ <Shield className="h-5 w-5 text-primary" />
57
+ Stop Loss & Take Profit
58
+ </CardTitle>
59
+ </CardHeader>
60
+ <CardContent className="space-y-6">
61
+ {/* Stop Loss */}
62
+ <div className="space-y-3">
63
+ <div className="flex items-center justify-between">
64
+ <Label className="flex items-center gap-2">
65
+ <TrendingDown className="h-4 w-4 text-loss" />
66
+ Stop Loss
67
+ </Label>
68
+ <Switch
69
+ checked={settings.stopLossEnabled}
70
+ onCheckedChange={(checked) =>
71
+ setSettings(prev => ({ ...prev, stopLossEnabled: checked }))
72
+ }
73
+ />
74
+ </div>
75
+ {settings.stopLossEnabled && (
76
+ <div className="space-y-2">
77
+ <div className="flex items-center justify-between text-sm">
78
+ <span>Percentage Loss Limit</span>
79
+ <span className="text-loss font-mono">
80
+ -{settings.stopLossPercentage[0]}%
81
+ </span>
82
+ </div>
83
+ <Slider
84
+ value={settings.stopLossPercentage}
85
+ onValueChange={(value) =>
86
+ setSettings(prev => ({ ...prev, stopLossPercentage: value }))
87
+ }
88
+ max={20}
89
+ min={1}
90
+ step={0.5}
91
+ className="py-2"
92
+ />
93
+ </div>
94
+ )}
95
+ </div>
96
+
97
+ {/* Take Profit */}
98
+ <div className="space-y-3">
99
+ <div className="flex items-center justify-between">
100
+ <Label className="flex items-center gap-2">
101
+ <TrendingUp className="h-4 w-4 text-profit" />
102
+ Take Profit
103
+ </Label>
104
+ <Switch
105
+ checked={settings.takeProfitEnabled}
106
+ onCheckedChange={(checked) =>
107
+ setSettings(prev => ({ ...prev, takeProfitEnabled: checked }))
108
+ }
109
+ />
110
+ </div>
111
+ {settings.takeProfitEnabled && (
112
+ <div className="space-y-2">
113
+ <div className="flex items-center justify-between text-sm">
114
+ <span>Percentage Profit Target</span>
115
+ <span className="text-profit font-mono">
116
+ +{settings.takeProfitPercentage[0]}%
117
+ </span>
118
+ </div>
119
+ <Slider
120
+ value={settings.takeProfitPercentage}
121
+ onValueChange={(value) =>
122
+ setSettings(prev => ({ ...prev, takeProfitPercentage: value }))
123
+ }
124
+ max={50}
125
+ min={5}
126
+ step={1}
127
+ className="py-2"
128
+ />
129
+ </div>
130
+ )}
131
+ </div>
132
+
133
+ {/* Trailing Stop */}
134
+ <div className="space-y-3">
135
+ <div className="flex items-center justify-between">
136
+ <Label className="flex items-center gap-2">
137
+ <AlertTriangle className="h-4 w-4 text-warning" />
138
+ Trailing Stop
139
+ </Label>
140
+ <Switch
141
+ checked={settings.enableTrailingStop}
142
+ onCheckedChange={(checked) =>
143
+ setSettings(prev => ({ ...prev, enableTrailingStop: checked }))
144
+ }
145
+ />
146
+ </div>
147
+ {settings.enableTrailingStop && (
148
+ <div className="space-y-2">
149
+ <div className="flex items-center justify-between text-sm">
150
+ <span>Trailing Distance</span>
151
+ <span className="text-warning font-mono">
152
+ {settings.trailingStopPercentage[0]}%
153
+ </span>
154
+ </div>
155
+ <Slider
156
+ value={settings.trailingStopPercentage}
157
+ onValueChange={(value) =>
158
+ setSettings(prev => ({ ...prev, trailingStopPercentage: value }))
159
+ }
160
+ max={10}
161
+ min={1}
162
+ step={0.5}
163
+ className="py-2"
164
+ />
165
+ </div>
166
+ )}
167
+ </div>
168
+ </CardContent>
169
+ </Card>
170
+
171
+ <Card className="bg-gradient-card border-border/50">
172
+ <CardHeader>
173
+ <CardTitle className="flex items-center gap-2">
174
+ <DollarSign className="h-5 w-5 text-primary" />
175
+ Position & Risk Limits
176
+ </CardTitle>
177
+ </CardHeader>
178
+ <CardContent className="space-y-6">
179
+ {/* Max Position Size */}
180
+ <div className="space-y-3">
181
+ <Label>Maximum Position Size</Label>
182
+ <div className="space-y-2">
183
+ <div className="flex items-center justify-between text-sm">
184
+ <span>Per Trade (% of wallet)</span>
185
+ <span className="font-mono">{settings.maxPositionSize[0]}%</span>
186
+ </div>
187
+ <Slider
188
+ value={settings.maxPositionSize}
189
+ onValueChange={(value) =>
190
+ setSettings(prev => ({ ...prev, maxPositionSize: value }))
191
+ }
192
+ max={50}
193
+ min={1}
194
+ step={1}
195
+ className="py-2"
196
+ />
197
+ </div>
198
+ </div>
199
+
200
+ {/* Max Daily Loss */}
201
+ <div className="space-y-3">
202
+ <Label>Daily Loss Limit</Label>
203
+ <div className="space-y-2">
204
+ <div className="flex items-center justify-between text-sm">
205
+ <span>Maximum Daily Loss ($)</span>
206
+ <span className="font-mono text-loss">
207
+ ${settings.maxDailyLoss[0]}
208
+ </span>
209
+ </div>
210
+ <Slider
211
+ value={settings.maxDailyLoss}
212
+ onValueChange={(value) =>
213
+ setSettings(prev => ({ ...prev, maxDailyLoss: value }))
214
+ }
215
+ max={1000}
216
+ min={10}
217
+ step={10}
218
+ className="py-2"
219
+ />
220
+ </div>
221
+ </div>
222
+
223
+ {/* Max Concurrent Trades */}
224
+ <div className="space-y-3">
225
+ <Label>Concurrent Trades Limit</Label>
226
+ <div className="space-y-2">
227
+ <div className="flex items-center justify-between text-sm">
228
+ <span>Maximum Active Trades</span>
229
+ <span className="font-mono">{settings.maxConcurrentTrades[0]}</span>
230
+ </div>
231
+ <Slider
232
+ value={settings.maxConcurrentTrades}
233
+ onValueChange={(value) =>
234
+ setSettings(prev => ({ ...prev, maxConcurrentTrades: value }))
235
+ }
236
+ max={20}
237
+ min={1}
238
+ step={1}
239
+ className="py-2"
240
+ />
241
+ </div>
242
+ </div>
243
+
244
+ {/* Action Buttons */}
245
+ <div className="flex gap-3 pt-4">
246
+ <Button onClick={handleSaveSettings} className="flex-1">
247
+ Save Settings
248
+ </Button>
249
+ <Button
250
+ variant="outline"
251
+ onClick={handleResetToDefaults}
252
+ className="flex-1"
253
+ >
254
+ Reset to Defaults
255
+ </Button>
256
+ </div>
257
+ </CardContent>
258
+ </Card>
259
+ </div>
260
+ );
261
+ };
src/components/dashboard/TargetWalletManager.tsx ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
3
+ import { Button } from '@/components/ui/button';
4
+ import { Input } from '@/components/ui/input';
5
+ import { Label } from '@/components/ui/label';
6
+ import { Badge } from '@/components/ui/badge';
7
+ import { Plus, Trash2, Target, Activity, DollarSign } from 'lucide-react';
8
+ import { useToast } from '@/hooks/use-toast';
9
+
10
+ interface TargetWalletManagerProps {
11
+ wallets: string[];
12
+ setWallets: (wallets: string[]) => void;
13
+ }
14
+
15
+ export const TargetWalletManager: React.FC<TargetWalletManagerProps> = ({
16
+ wallets,
17
+ setWallets
18
+ }) => {
19
+ const [newWallet, setNewWallet] = useState('');
20
+ const { toast } = useToast();
21
+
22
+ const addWallet = () => {
23
+ if (!newWallet.trim()) {
24
+ toast({
25
+ title: "Invalid Address",
26
+ description: "Please enter a valid Solana wallet address",
27
+ variant: "destructive"
28
+ });
29
+ return;
30
+ }
31
+
32
+ // Basic validation for Solana address (should be 32-44 characters)
33
+ if (newWallet.length < 32 || newWallet.length > 44) {
34
+ toast({
35
+ title: "Invalid Address Format",
36
+ description: "Solana addresses should be 32-44 characters long",
37
+ variant: "destructive"
38
+ });
39
+ return;
40
+ }
41
+
42
+ if (wallets.includes(newWallet)) {
43
+ toast({
44
+ title: "Wallet Already Added",
45
+ description: "This wallet address is already in your target list",
46
+ variant: "destructive"
47
+ });
48
+ return;
49
+ }
50
+
51
+ setWallets([...wallets, newWallet]);
52
+ setNewWallet('');
53
+
54
+ toast({
55
+ title: "Wallet Added",
56
+ description: "Target wallet successfully added to monitoring list",
57
+ });
58
+ };
59
+
60
+ const removeWallet = (wallet: string) => {
61
+ setWallets(wallets.filter(w => w !== wallet));
62
+ toast({
63
+ title: "Wallet Removed",
64
+ description: "Target wallet removed from monitoring list",
65
+ });
66
+ };
67
+
68
+ // Mock data for demonstration
69
+ const walletStats = {
70
+ [wallets[0]]: {
71
+ totalTrades: 156,
72
+ profitLoss: 2341.56,
73
+ successRate: 72.4,
74
+ lastActive: '2 minutes ago'
75
+ }
76
+ };
77
+
78
+ return (
79
+ <Card className="bg-gradient-card border-border/50">
80
+ <CardHeader>
81
+ <CardTitle className="flex items-center gap-2">
82
+ <Target className="h-5 w-5 text-primary" />
83
+ Target Wallets
84
+ </CardTitle>
85
+ </CardHeader>
86
+ <CardContent className="space-y-4">
87
+ <div className="space-y-2">
88
+ <Label htmlFor="wallet-address">Add Target Wallet</Label>
89
+ <div className="flex gap-2">
90
+ <Input
91
+ id="wallet-address"
92
+ placeholder="Enter Solana wallet address..."
93
+ value={newWallet}
94
+ onChange={(e) => setNewWallet(e.target.value)}
95
+ className="flex-1"
96
+ />
97
+ <Button onClick={addWallet} className="gap-2">
98
+ <Plus className="h-4 w-4" />
99
+ Add
100
+ </Button>
101
+ </div>
102
+ </div>
103
+
104
+ <div className="space-y-3">
105
+ {wallets.length === 0 ? (
106
+ <div className="text-center py-8 text-muted-foreground">
107
+ <Target className="h-12 w-12 mx-auto mb-3 opacity-50" />
108
+ <p>No target wallets added yet</p>
109
+ <p className="text-sm">Add wallet addresses to start copying trades</p>
110
+ </div>
111
+ ) : (
112
+ wallets.map((wallet, index) => (
113
+ <div
114
+ key={wallet}
115
+ className="p-4 rounded-lg border bg-secondary/50 animate-fade-in"
116
+ style={{ animationDelay: `${index * 0.1}s` }}
117
+ >
118
+ <div className="flex items-start justify-between mb-3">
119
+ <div className="flex-1 min-w-0">
120
+ <div className="flex items-center gap-2 mb-1">
121
+ <Badge variant="outline" className="text-xs">
122
+ #{index + 1}
123
+ </Badge>
124
+ <Badge variant="secondary" className="text-xs">
125
+ Active
126
+ </Badge>
127
+ </div>
128
+ <p className="font-mono text-sm break-all">
129
+ {wallet.slice(0, 8)}...{wallet.slice(-8)}
130
+ </p>
131
+ </div>
132
+ <Button
133
+ variant="ghost"
134
+ size="sm"
135
+ onClick={() => removeWallet(wallet)}
136
+ className="text-destructive hover:text-destructive"
137
+ >
138
+ <Trash2 className="h-4 w-4" />
139
+ </Button>
140
+ </div>
141
+
142
+ {/* Mock stats for demonstration */}
143
+ {walletStats[wallet] && (
144
+ <div className="grid grid-cols-2 gap-4 text-xs">
145
+ <div className="flex items-center gap-1">
146
+ <Activity className="h-3 w-3 text-info" />
147
+ <span>{walletStats[wallet].totalTrades} trades</span>
148
+ </div>
149
+ <div className="flex items-center gap-1">
150
+ <DollarSign className="h-3 w-3 text-profit" />
151
+ <span className="text-profit">
152
+ ${walletStats[wallet].profitLoss.toFixed(2)}
153
+ </span>
154
+ </div>
155
+ <div className="text-muted-foreground col-span-2">
156
+ Last active: {walletStats[wallet].lastActive}
157
+ </div>
158
+ </div>
159
+ )}
160
+ </div>
161
+ ))
162
+ )}
163
+ </div>
164
+ </CardContent>
165
+ </Card>
166
+ );
167
+ };
src/components/dashboard/TradeLog.tsx ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
3
+ import { Badge } from '@/components/ui/badge';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Input } from '@/components/ui/input';
6
+ import {
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from '@/components/ui/table';
14
+ import {
15
+ Activity,
16
+ Download,
17
+ Search,
18
+ TrendingUp,
19
+ TrendingDown,
20
+ ExternalLink,
21
+ Clock
22
+ } from 'lucide-react';
23
+
24
+ // Mock trade data
25
+ const mockTrades = [
26
+ {
27
+ id: '1',
28
+ timestamp: new Date('2024-01-07T14:30:00Z'),
29
+ targetWallet: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
30
+ pair: 'SOL/USDC',
31
+ type: 'BUY',
32
+ amount: 2.5,
33
+ price: 98.45,
34
+ pnl: 156.80,
35
+ status: 'completed',
36
+ signature: '2uJYB9jKzZi8YMFQWyJ8UvBdJLnVcP9q3HrXj6K4T7N8L9R1'
37
+ },
38
+ {
39
+ id: '2',
40
+ timestamp: new Date('2024-01-07T13:15:00Z'),
41
+ targetWallet: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
42
+ pair: 'RAY/SOL',
43
+ type: 'SELL',
44
+ amount: 150,
45
+ price: 0.045,
46
+ pnl: -23.40,
47
+ status: 'completed',
48
+ signature: '3vKYC0jLzZi9YNGRXyK9VvCeKMoWdQ0r4IsYk7L5U8O9M0S2'
49
+ },
50
+ {
51
+ id: '3',
52
+ timestamp: new Date('2024-01-07T12:45:00Z'),
53
+ targetWallet: 'BQWf2RqVVJ8ZfP4HuRr6nF2jN8Y5K3L9R1T7U4W6X2B5M9S8',
54
+ pair: 'ORCA/USDC',
55
+ type: 'BUY',
56
+ amount: 85.2,
57
+ price: 1.23,
58
+ pnl: 45.60,
59
+ status: 'completed',
60
+ signature: '4wLYD1kMzZj0YOHSYyL0WwDfLNpXeR1s5JtZl8M6V9P0N1T3'
61
+ },
62
+ {
63
+ id: '4',
64
+ timestamp: new Date('2024-01-07T11:20:00Z'),
65
+ targetWallet: 'BQWf2RqVVJ8ZfP4HuRr6nF2jN8Y5K3L9R1T7U4W6X2B5M9S8',
66
+ pair: 'SOL/USDC',
67
+ type: 'BUY',
68
+ amount: 1.8,
69
+ price: 97.20,
70
+ pnl: 0,
71
+ status: 'pending',
72
+ signature: '5xMYE2lNzZk1YPITZyM1XxEgMOqYfS2t6KuAm9N7W0Q1O2U4'
73
+ },
74
+ {
75
+ id: '5',
76
+ timestamp: new Date('2024-01-07T10:05:00Z'),
77
+ targetWallet: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
78
+ pair: 'BONK/SOL',
79
+ type: 'SELL',
80
+ amount: 1000000,
81
+ price: 0.0000089,
82
+ pnl: 78.90,
83
+ status: 'completed',
84
+ signature: '6yNYF3mOzZl2YQJUAyN2YyFhNPrZgT3u7LvBn0O8X1R2P3V5'
85
+ }
86
+ ];
87
+
88
+ export const TradeLog: React.FC = () => {
89
+ const [searchTerm, setSearchTerm] = useState('');
90
+
91
+ const filteredTrades = mockTrades.filter(trade =>
92
+ trade.pair.toLowerCase().includes(searchTerm.toLowerCase()) ||
93
+ trade.targetWallet.toLowerCase().includes(searchTerm.toLowerCase()) ||
94
+ trade.type.toLowerCase().includes(searchTerm.toLowerCase())
95
+ );
96
+
97
+ const handleExportTrades = () => {
98
+ // Create CSV content
99
+ const csvContent = [
100
+ ['Timestamp', 'Target Wallet', 'Pair', 'Type', 'Amount', 'Price', 'P&L', 'Status', 'Signature'],
101
+ ...filteredTrades.map(trade => [
102
+ trade.timestamp.toISOString(),
103
+ trade.targetWallet,
104
+ trade.pair,
105
+ trade.type,
106
+ trade.amount.toString(),
107
+ trade.price.toString(),
108
+ trade.pnl.toString(),
109
+ trade.status,
110
+ trade.signature
111
+ ])
112
+ ].map(row => row.join(',')).join('\n');
113
+
114
+ // Create and download file
115
+ const blob = new Blob([csvContent], { type: 'text/csv' });
116
+ const url = window.URL.createObjectURL(blob);
117
+ const a = document.createElement('a');
118
+ a.href = url;
119
+ a.download = `trade-log-${new Date().toISOString().split('T')[0]}.csv`;
120
+ document.body.appendChild(a);
121
+ a.click();
122
+ document.body.removeChild(a);
123
+ window.URL.revokeObjectURL(url);
124
+ };
125
+
126
+ const getStatusBadge = (status: string) => {
127
+ switch (status) {
128
+ case 'completed':
129
+ return <Badge variant="default" className="bg-profit text-profit-foreground">Completed</Badge>;
130
+ case 'pending':
131
+ return <Badge variant="secondary" className="bg-warning text-warning-foreground">Pending</Badge>;
132
+ case 'failed':
133
+ return <Badge variant="destructive">Failed</Badge>;
134
+ default:
135
+ return <Badge variant="outline">{status}</Badge>;
136
+ }
137
+ };
138
+
139
+ const getPnLDisplay = (pnl: number, status: string) => {
140
+ if (status === 'pending') {
141
+ return <span className="text-muted-foreground">--</span>;
142
+ }
143
+
144
+ return (
145
+ <span className={pnl >= 0 ? 'text-profit' : 'text-loss'}>
146
+ {pnl >= 0 ? '+' : ''}${pnl.toFixed(2)}
147
+ </span>
148
+ );
149
+ };
150
+
151
+ return (
152
+ <Card className="bg-gradient-card border-border/50">
153
+ <CardHeader>
154
+ <div className="flex items-center justify-between">
155
+ <CardTitle className="flex items-center gap-2">
156
+ <Activity className="h-5 w-5 text-primary" />
157
+ Trade Log
158
+ </CardTitle>
159
+ <div className="flex items-center gap-2">
160
+ <div className="relative">
161
+ <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
162
+ <Input
163
+ placeholder="Search trades..."
164
+ value={searchTerm}
165
+ onChange={(e) => setSearchTerm(e.target.value)}
166
+ className="pl-8 w-64"
167
+ />
168
+ </div>
169
+ <Button onClick={handleExportTrades} variant="outline" className="gap-2">
170
+ <Download className="h-4 w-4" />
171
+ Export CSV
172
+ </Button>
173
+ </div>
174
+ </div>
175
+ </CardHeader>
176
+ <CardContent>
177
+ <div className="overflow-x-auto">
178
+ <Table>
179
+ <TableHeader>
180
+ <TableRow>
181
+ <TableHead>Time</TableHead>
182
+ <TableHead>Target Wallet</TableHead>
183
+ <TableHead>Pair</TableHead>
184
+ <TableHead>Type</TableHead>
185
+ <TableHead>Amount</TableHead>
186
+ <TableHead>Price</TableHead>
187
+ <TableHead>P&L</TableHead>
188
+ <TableHead>Status</TableHead>
189
+ <TableHead>Action</TableHead>
190
+ </TableRow>
191
+ </TableHeader>
192
+ <TableBody>
193
+ {filteredTrades.map((trade, index) => (
194
+ <TableRow
195
+ key={trade.id}
196
+ className="animate-fade-in"
197
+ style={{ animationDelay: `${index * 0.05}s` }}
198
+ >
199
+ <TableCell>
200
+ <div className="flex items-center gap-1 text-sm">
201
+ <Clock className="h-3 w-3 text-muted-foreground" />
202
+ {trade.timestamp.toLocaleTimeString('en-US', {
203
+ hour: '2-digit',
204
+ minute: '2-digit'
205
+ })}
206
+ </div>
207
+ <div className="text-xs text-muted-foreground">
208
+ {trade.timestamp.toLocaleDateString('en-US', {
209
+ month: 'short',
210
+ day: 'numeric'
211
+ })}
212
+ </div>
213
+ </TableCell>
214
+ <TableCell>
215
+ <div className="font-mono text-xs">
216
+ {trade.targetWallet.slice(0, 6)}...{trade.targetWallet.slice(-4)}
217
+ </div>
218
+ </TableCell>
219
+ <TableCell>
220
+ <span className="font-medium">{trade.pair}</span>
221
+ </TableCell>
222
+ <TableCell>
223
+ <Badge
224
+ variant={trade.type === 'BUY' ? 'default' : 'secondary'}
225
+ className={trade.type === 'BUY' ? 'bg-profit text-profit-foreground' : 'bg-loss text-loss-foreground'}
226
+ >
227
+ {trade.type === 'BUY' ? (
228
+ <TrendingUp className="h-3 w-3 mr-1" />
229
+ ) : (
230
+ <TrendingDown className="h-3 w-3 mr-1" />
231
+ )}
232
+ {trade.type}
233
+ </Badge>
234
+ </TableCell>
235
+ <TableCell>
236
+ <span className="font-mono text-sm">{trade.amount.toLocaleString()}</span>
237
+ </TableCell>
238
+ <TableCell>
239
+ <span className="font-mono text-sm">${trade.price.toFixed(4)}</span>
240
+ </TableCell>
241
+ <TableCell className="font-bold">
242
+ {getPnLDisplay(trade.pnl, trade.status)}
243
+ </TableCell>
244
+ <TableCell>
245
+ {getStatusBadge(trade.status)}
246
+ </TableCell>
247
+ <TableCell>
248
+ <Button
249
+ variant="ghost"
250
+ size="sm"
251
+ className="gap-1"
252
+ onClick={() => window.open(`https://explorer.solana.com/tx/${trade.signature}?cluster=devnet`, '_blank')}
253
+ >
254
+ <ExternalLink className="h-3 w-3" />
255
+ View
256
+ </Button>
257
+ </TableCell>
258
+ </TableRow>
259
+ ))}
260
+ </TableBody>
261
+ </Table>
262
+ </div>
263
+
264
+ {filteredTrades.length === 0 && (
265
+ <div className="text-center py-8 text-muted-foreground">
266
+ <Activity className="h-12 w-12 mx-auto mb-3 opacity-50" />
267
+ <p>No trades found</p>
268
+ <p className="text-sm">
269
+ {searchTerm ? 'Try adjusting your search terms' : 'Start trading to see your trade history'}
270
+ </p>
271
+ </div>
272
+ )}
273
+ </CardContent>
274
+ </Card>
275
+ );
276
+ };
src/components/ui/accordion.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
3
+ import { ChevronDown } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Accordion = AccordionPrimitive.Root
8
+
9
+ const AccordionItem = React.forwardRef<
10
+ React.ElementRef<typeof AccordionPrimitive.Item>,
11
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
12
+ >(({ className, ...props }, ref) => (
13
+ <AccordionPrimitive.Item
14
+ ref={ref}
15
+ className={cn("border-b", className)}
16
+ {...props}
17
+ />
18
+ ))
19
+ AccordionItem.displayName = "AccordionItem"
20
+
21
+ const AccordionTrigger = React.forwardRef<
22
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
23
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
24
+ >(({ className, children, ...props }, ref) => (
25
+ <AccordionPrimitive.Header className="flex">
26
+ <AccordionPrimitive.Trigger
27
+ ref={ref}
28
+ className={cn(
29
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
36
+ </AccordionPrimitive.Trigger>
37
+ </AccordionPrimitive.Header>
38
+ ))
39
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
40
+
41
+ const AccordionContent = React.forwardRef<
42
+ React.ElementRef<typeof AccordionPrimitive.Content>,
43
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
44
+ >(({ className, children, ...props }, ref) => (
45
+ <AccordionPrimitive.Content
46
+ ref={ref}
47
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
48
+ {...props}
49
+ >
50
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
51
+ </AccordionPrimitive.Content>
52
+ ))
53
+
54
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
55
+
56
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
src/components/ui/alert-dialog.tsx ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
3
+
4
+ import { cn } from "@/lib/utils"
5
+ import { buttonVariants } from "@/components/ui/button"
6
+
7
+ const AlertDialog = AlertDialogPrimitive.Root
8
+
9
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
10
+
11
+ const AlertDialogPortal = AlertDialogPrimitive.Portal
12
+
13
+ const AlertDialogOverlay = React.forwardRef<
14
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
15
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
16
+ >(({ className, ...props }, ref) => (
17
+ <AlertDialogPrimitive.Overlay
18
+ className={cn(
19
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
20
+ className
21
+ )}
22
+ {...props}
23
+ ref={ref}
24
+ />
25
+ ))
26
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
27
+
28
+ const AlertDialogContent = React.forwardRef<
29
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
30
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
31
+ >(({ className, ...props }, ref) => (
32
+ <AlertDialogPortal>
33
+ <AlertDialogOverlay />
34
+ <AlertDialogPrimitive.Content
35
+ ref={ref}
36
+ className={cn(
37
+ "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",
38
+ className
39
+ )}
40
+ {...props}
41
+ />
42
+ </AlertDialogPortal>
43
+ ))
44
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
45
+
46
+ const AlertDialogHeader = ({
47
+ className,
48
+ ...props
49
+ }: React.HTMLAttributes<HTMLDivElement>) => (
50
+ <div
51
+ className={cn(
52
+ "flex flex-col space-y-2 text-center sm:text-left",
53
+ className
54
+ )}
55
+ {...props}
56
+ />
57
+ )
58
+ AlertDialogHeader.displayName = "AlertDialogHeader"
59
+
60
+ const AlertDialogFooter = ({
61
+ className,
62
+ ...props
63
+ }: React.HTMLAttributes<HTMLDivElement>) => (
64
+ <div
65
+ className={cn(
66
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
67
+ className
68
+ )}
69
+ {...props}
70
+ />
71
+ )
72
+ AlertDialogFooter.displayName = "AlertDialogFooter"
73
+
74
+ const AlertDialogTitle = React.forwardRef<
75
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
76
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
77
+ >(({ className, ...props }, ref) => (
78
+ <AlertDialogPrimitive.Title
79
+ ref={ref}
80
+ className={cn("text-lg font-semibold", className)}
81
+ {...props}
82
+ />
83
+ ))
84
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
85
+
86
+ const AlertDialogDescription = React.forwardRef<
87
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
88
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
89
+ >(({ className, ...props }, ref) => (
90
+ <AlertDialogPrimitive.Description
91
+ ref={ref}
92
+ className={cn("text-sm text-muted-foreground", className)}
93
+ {...props}
94
+ />
95
+ ))
96
+ AlertDialogDescription.displayName =
97
+ AlertDialogPrimitive.Description.displayName
98
+
99
+ const AlertDialogAction = React.forwardRef<
100
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
101
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
102
+ >(({ className, ...props }, ref) => (
103
+ <AlertDialogPrimitive.Action
104
+ ref={ref}
105
+ className={cn(buttonVariants(), className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
110
+
111
+ const AlertDialogCancel = React.forwardRef<
112
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
113
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
114
+ >(({ className, ...props }, ref) => (
115
+ <AlertDialogPrimitive.Cancel
116
+ ref={ref}
117
+ className={cn(
118
+ buttonVariants({ variant: "outline" }),
119
+ "mt-2 sm:mt-0",
120
+ className
121
+ )}
122
+ {...props}
123
+ />
124
+ ))
125
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
126
+
127
+ export {
128
+ AlertDialog,
129
+ AlertDialogPortal,
130
+ AlertDialogOverlay,
131
+ AlertDialogTrigger,
132
+ AlertDialogContent,
133
+ AlertDialogHeader,
134
+ AlertDialogFooter,
135
+ AlertDialogTitle,
136
+ AlertDialogDescription,
137
+ AlertDialogAction,
138
+ AlertDialogCancel,
139
+ }
src/components/ui/alert.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "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",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-background text-foreground",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ const Alert = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(({ className, variant, ...props }, ref) => (
26
+ <div
27
+ ref={ref}
28
+ role="alert"
29
+ className={cn(alertVariants({ variant }), className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ Alert.displayName = "Alert"
34
+
35
+ const AlertTitle = React.forwardRef<
36
+ HTMLParagraphElement,
37
+ React.HTMLAttributes<HTMLHeadingElement>
38
+ >(({ className, ...props }, ref) => (
39
+ <h5
40
+ ref={ref}
41
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
42
+ {...props}
43
+ />
44
+ ))
45
+ AlertTitle.displayName = "AlertTitle"
46
+
47
+ const AlertDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
52
+ ref={ref}
53
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ AlertDescription.displayName = "AlertDescription"
58
+
59
+ export { Alert, AlertTitle, AlertDescription }
src/components/ui/aspect-ratio.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2
+
3
+ const AspectRatio = AspectRatioPrimitive.Root
4
+
5
+ export { AspectRatio }
src/components/ui/avatar.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Avatar = React.forwardRef<
7
+ React.ElementRef<typeof AvatarPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
9
+ >(({ className, ...props }, ref) => (
10
+ <AvatarPrimitive.Root
11
+ ref={ref}
12
+ className={cn(
13
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ ))
19
+ Avatar.displayName = AvatarPrimitive.Root.displayName
20
+
21
+ const AvatarImage = React.forwardRef<
22
+ React.ElementRef<typeof AvatarPrimitive.Image>,
23
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
24
+ >(({ className, ...props }, ref) => (
25
+ <AvatarPrimitive.Image
26
+ ref={ref}
27
+ className={cn("aspect-square h-full w-full", className)}
28
+ {...props}
29
+ />
30
+ ))
31
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
32
+
33
+ const AvatarFallback = React.forwardRef<
34
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
35
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
36
+ >(({ className, ...props }, ref) => (
37
+ <AvatarPrimitive.Fallback
38
+ ref={ref}
39
+ className={cn(
40
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
41
+ className
42
+ )}
43
+ {...props}
44
+ />
45
+ ))
46
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47
+
48
+ export { Avatar, AvatarImage, AvatarFallback }
src/components/ui/badge.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "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",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
src/components/ui/breadcrumb.tsx ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode
11
+ }
12
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
13
+ Breadcrumb.displayName = "Breadcrumb"
14
+
15
+ const BreadcrumbList = React.forwardRef<
16
+ HTMLOListElement,
17
+ React.ComponentPropsWithoutRef<"ol">
18
+ >(({ className, ...props }, ref) => (
19
+ <ol
20
+ ref={ref}
21
+ className={cn(
22
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ BreadcrumbList.displayName = "BreadcrumbList"
29
+
30
+ const BreadcrumbItem = React.forwardRef<
31
+ HTMLLIElement,
32
+ React.ComponentPropsWithoutRef<"li">
33
+ >(({ className, ...props }, ref) => (
34
+ <li
35
+ ref={ref}
36
+ className={cn("inline-flex items-center gap-1.5", className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ BreadcrumbItem.displayName = "BreadcrumbItem"
41
+
42
+ const BreadcrumbLink = React.forwardRef<
43
+ HTMLAnchorElement,
44
+ React.ComponentPropsWithoutRef<"a"> & {
45
+ asChild?: boolean
46
+ }
47
+ >(({ asChild, className, ...props }, ref) => {
48
+ const Comp = asChild ? Slot : "a"
49
+
50
+ return (
51
+ <Comp
52
+ ref={ref}
53
+ className={cn("transition-colors hover:text-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ })
58
+ BreadcrumbLink.displayName = "BreadcrumbLink"
59
+
60
+ const BreadcrumbPage = React.forwardRef<
61
+ HTMLSpanElement,
62
+ React.ComponentPropsWithoutRef<"span">
63
+ >(({ className, ...props }, ref) => (
64
+ <span
65
+ ref={ref}
66
+ role="link"
67
+ aria-disabled="true"
68
+ aria-current="page"
69
+ className={cn("font-normal text-foreground", className)}
70
+ {...props}
71
+ />
72
+ ))
73
+ BreadcrumbPage.displayName = "BreadcrumbPage"
74
+
75
+ const BreadcrumbSeparator = ({
76
+ children,
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<"li">) => (
80
+ <li
81
+ role="presentation"
82
+ aria-hidden="true"
83
+ className={cn("[&>svg]:size-3.5", className)}
84
+ {...props}
85
+ >
86
+ {children ?? <ChevronRight />}
87
+ </li>
88
+ )
89
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90
+
91
+ const BreadcrumbEllipsis = ({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"span">) => (
95
+ <span
96
+ role="presentation"
97
+ aria-hidden="true"
98
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
99
+ {...props}
100
+ >
101
+ <MoreHorizontal className="h-4 w-4" />
102
+ <span className="sr-only">More</span>
103
+ </span>
104
+ )
105
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106
+
107
+ export {
108
+ Breadcrumb,
109
+ BreadcrumbList,
110
+ BreadcrumbItem,
111
+ BreadcrumbLink,
112
+ BreadcrumbPage,
113
+ BreadcrumbSeparator,
114
+ BreadcrumbEllipsis,
115
+ }
src/components/ui/button.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-all duration-300 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",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90 hover:scale-105",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90 hover:scale-105",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground hover:scale-105",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 hover:scale-105",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground hover:scale-105",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ hero: "bg-gradient-primary text-primary-foreground hover:opacity-90 hover:scale-105 shadow-lg",
22
+ profit: "bg-profit text-white hover:bg-profit/90 hover:scale-105",
23
+ loss: "bg-loss text-white hover:bg-loss/90 hover:scale-105",
24
+ },
25
+ size: {
26
+ default: "h-10 px-4 py-2",
27
+ sm: "h-9 rounded-md px-3",
28
+ lg: "h-11 rounded-md px-8",
29
+ icon: "h-10 w-10",
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ variant: "default",
34
+ size: "default",
35
+ },
36
+ }
37
+ )
38
+
39
+ export interface ButtonProps
40
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
41
+ VariantProps<typeof buttonVariants> {
42
+ asChild?: boolean
43
+ }
44
+
45
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
46
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
47
+ const Comp = asChild ? Slot : "button"
48
+ return (
49
+ <Comp
50
+ className={cn(buttonVariants({ variant, size, className }))}
51
+ ref={ref}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+ )
57
+ Button.displayName = "Button"
58
+
59
+ export { Button, buttonVariants }
src/components/ui/calendar.tsx ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { ChevronLeft, ChevronRight } from "lucide-react";
3
+ import { DayPicker } from "react-day-picker";
4
+
5
+ import { cn } from "@/lib/utils";
6
+ import { buttonVariants } from "@/components/ui/button";
7
+
8
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>;
9
+
10
+ function Calendar({
11
+ className,
12
+ classNames,
13
+ showOutsideDays = true,
14
+ ...props
15
+ }: CalendarProps) {
16
+ return (
17
+ <DayPicker
18
+ showOutsideDays={showOutsideDays}
19
+ className={cn("p-3", className)}
20
+ classNames={{
21
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
22
+ month: "space-y-4",
23
+ caption: "flex justify-center pt-1 relative items-center",
24
+ caption_label: "text-sm font-medium",
25
+ nav: "space-x-1 flex items-center",
26
+ nav_button: cn(
27
+ buttonVariants({ variant: "outline" }),
28
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
29
+ ),
30
+ nav_button_previous: "absolute left-1",
31
+ nav_button_next: "absolute right-1",
32
+ table: "w-full border-collapse space-y-1",
33
+ head_row: "flex",
34
+ head_cell:
35
+ "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
36
+ row: "flex w-full mt-2",
37
+ 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",
38
+ day: cn(
39
+ buttonVariants({ variant: "ghost" }),
40
+ "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
41
+ ),
42
+ day_range_end: "day-range-end",
43
+ day_selected:
44
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
45
+ day_today: "bg-accent text-accent-foreground",
46
+ day_outside:
47
+ "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
48
+ day_disabled: "text-muted-foreground opacity-50",
49
+ day_range_middle:
50
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
51
+ day_hidden: "invisible",
52
+ ...classNames,
53
+ }}
54
+ components={{
55
+ IconLeft: ({ ..._props }) => <ChevronLeft className="h-4 w-4" />,
56
+ IconRight: ({ ..._props }) => <ChevronRight className="h-4 w-4" />,
57
+ }}
58
+ {...props}
59
+ />
60
+ );
61
+ }
62
+ Calendar.displayName = "Calendar";
63
+
64
+ export { Calendar };
src/components/ui/card.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLParagraphElement,
34
+ React.HTMLAttributes<HTMLHeadingElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <h3
37
+ ref={ref}
38
+ className={cn(
39
+ "text-2xl font-semibold leading-none tracking-tight",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ CardTitle.displayName = "CardTitle"
46
+
47
+ const CardDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <p
52
+ ref={ref}
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ CardDescription.displayName = "CardDescription"
58
+
59
+ const CardContent = React.forwardRef<
60
+ HTMLDivElement,
61
+ React.HTMLAttributes<HTMLDivElement>
62
+ >(({ className, ...props }, ref) => (
63
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
64
+ ))
65
+ CardContent.displayName = "CardContent"
66
+
67
+ const CardFooter = React.forwardRef<
68
+ HTMLDivElement,
69
+ React.HTMLAttributes<HTMLDivElement>
70
+ >(({ className, ...props }, ref) => (
71
+ <div
72
+ ref={ref}
73
+ className={cn("flex items-center p-6 pt-0", className)}
74
+ {...props}
75
+ />
76
+ ))
77
+ CardFooter.displayName = "CardFooter"
78
+
79
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
src/components/ui/carousel.tsx ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import useEmblaCarousel, {
3
+ type UseEmblaCarouselType,
4
+ } from "embla-carousel-react"
5
+ import { ArrowLeft, ArrowRight } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+ import { Button } from "@/components/ui/button"
9
+
10
+ type CarouselApi = UseEmblaCarouselType[1]
11
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
12
+ type CarouselOptions = UseCarouselParameters[0]
13
+ type CarouselPlugin = UseCarouselParameters[1]
14
+
15
+ type CarouselProps = {
16
+ opts?: CarouselOptions
17
+ plugins?: CarouselPlugin
18
+ orientation?: "horizontal" | "vertical"
19
+ setApi?: (api: CarouselApi) => void
20
+ }
21
+
22
+ type CarouselContextProps = {
23
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
24
+ api: ReturnType<typeof useEmblaCarousel>[1]
25
+ scrollPrev: () => void
26
+ scrollNext: () => void
27
+ canScrollPrev: boolean
28
+ canScrollNext: boolean
29
+ } & CarouselProps
30
+
31
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null)
32
+
33
+ function useCarousel() {
34
+ const context = React.useContext(CarouselContext)
35
+
36
+ if (!context) {
37
+ throw new Error("useCarousel must be used within a <Carousel />")
38
+ }
39
+
40
+ return context
41
+ }
42
+
43
+ const Carousel = React.forwardRef<
44
+ HTMLDivElement,
45
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
46
+ >(
47
+ (
48
+ {
49
+ orientation = "horizontal",
50
+ opts,
51
+ setApi,
52
+ plugins,
53
+ className,
54
+ children,
55
+ ...props
56
+ },
57
+ ref
58
+ ) => {
59
+ const [carouselRef, api] = useEmblaCarousel(
60
+ {
61
+ ...opts,
62
+ axis: orientation === "horizontal" ? "x" : "y",
63
+ },
64
+ plugins
65
+ )
66
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
67
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
68
+
69
+ const onSelect = React.useCallback((api: CarouselApi) => {
70
+ if (!api) {
71
+ return
72
+ }
73
+
74
+ setCanScrollPrev(api.canScrollPrev())
75
+ setCanScrollNext(api.canScrollNext())
76
+ }, [])
77
+
78
+ const scrollPrev = React.useCallback(() => {
79
+ api?.scrollPrev()
80
+ }, [api])
81
+
82
+ const scrollNext = React.useCallback(() => {
83
+ api?.scrollNext()
84
+ }, [api])
85
+
86
+ const handleKeyDown = React.useCallback(
87
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
88
+ if (event.key === "ArrowLeft") {
89
+ event.preventDefault()
90
+ scrollPrev()
91
+ } else if (event.key === "ArrowRight") {
92
+ event.preventDefault()
93
+ scrollNext()
94
+ }
95
+ },
96
+ [scrollPrev, scrollNext]
97
+ )
98
+
99
+ React.useEffect(() => {
100
+ if (!api || !setApi) {
101
+ return
102
+ }
103
+
104
+ setApi(api)
105
+ }, [api, setApi])
106
+
107
+ React.useEffect(() => {
108
+ if (!api) {
109
+ return
110
+ }
111
+
112
+ onSelect(api)
113
+ api.on("reInit", onSelect)
114
+ api.on("select", onSelect)
115
+
116
+ return () => {
117
+ api?.off("select", onSelect)
118
+ }
119
+ }, [api, onSelect])
120
+
121
+ return (
122
+ <CarouselContext.Provider
123
+ value={{
124
+ carouselRef,
125
+ api: api,
126
+ opts,
127
+ orientation:
128
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
129
+ scrollPrev,
130
+ scrollNext,
131
+ canScrollPrev,
132
+ canScrollNext,
133
+ }}
134
+ >
135
+ <div
136
+ ref={ref}
137
+ onKeyDownCapture={handleKeyDown}
138
+ className={cn("relative", className)}
139
+ role="region"
140
+ aria-roledescription="carousel"
141
+ {...props}
142
+ >
143
+ {children}
144
+ </div>
145
+ </CarouselContext.Provider>
146
+ )
147
+ }
148
+ )
149
+ Carousel.displayName = "Carousel"
150
+
151
+ const CarouselContent = React.forwardRef<
152
+ HTMLDivElement,
153
+ React.HTMLAttributes<HTMLDivElement>
154
+ >(({ className, ...props }, ref) => {
155
+ const { carouselRef, orientation } = useCarousel()
156
+
157
+ return (
158
+ <div ref={carouselRef} className="overflow-hidden">
159
+ <div
160
+ ref={ref}
161
+ className={cn(
162
+ "flex",
163
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
164
+ className
165
+ )}
166
+ {...props}
167
+ />
168
+ </div>
169
+ )
170
+ })
171
+ CarouselContent.displayName = "CarouselContent"
172
+
173
+ const CarouselItem = React.forwardRef<
174
+ HTMLDivElement,
175
+ React.HTMLAttributes<HTMLDivElement>
176
+ >(({ className, ...props }, ref) => {
177
+ const { orientation } = useCarousel()
178
+
179
+ return (
180
+ <div
181
+ ref={ref}
182
+ role="group"
183
+ aria-roledescription="slide"
184
+ className={cn(
185
+ "min-w-0 shrink-0 grow-0 basis-full",
186
+ orientation === "horizontal" ? "pl-4" : "pt-4",
187
+ className
188
+ )}
189
+ {...props}
190
+ />
191
+ )
192
+ })
193
+ CarouselItem.displayName = "CarouselItem"
194
+
195
+ const CarouselPrevious = React.forwardRef<
196
+ HTMLButtonElement,
197
+ React.ComponentProps<typeof Button>
198
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
199
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
200
+
201
+ return (
202
+ <Button
203
+ ref={ref}
204
+ variant={variant}
205
+ size={size}
206
+ className={cn(
207
+ "absolute h-8 w-8 rounded-full",
208
+ orientation === "horizontal"
209
+ ? "-left-12 top-1/2 -translate-y-1/2"
210
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
211
+ className
212
+ )}
213
+ disabled={!canScrollPrev}
214
+ onClick={scrollPrev}
215
+ {...props}
216
+ >
217
+ <ArrowLeft className="h-4 w-4" />
218
+ <span className="sr-only">Previous slide</span>
219
+ </Button>
220
+ )
221
+ })
222
+ CarouselPrevious.displayName = "CarouselPrevious"
223
+
224
+ const CarouselNext = React.forwardRef<
225
+ HTMLButtonElement,
226
+ React.ComponentProps<typeof Button>
227
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
228
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
229
+
230
+ return (
231
+ <Button
232
+ ref={ref}
233
+ variant={variant}
234
+ size={size}
235
+ className={cn(
236
+ "absolute h-8 w-8 rounded-full",
237
+ orientation === "horizontal"
238
+ ? "-right-12 top-1/2 -translate-y-1/2"
239
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
240
+ className
241
+ )}
242
+ disabled={!canScrollNext}
243
+ onClick={scrollNext}
244
+ {...props}
245
+ >
246
+ <ArrowRight className="h-4 w-4" />
247
+ <span className="sr-only">Next slide</span>
248
+ </Button>
249
+ )
250
+ })
251
+ CarouselNext.displayName = "CarouselNext"
252
+
253
+ export {
254
+ type CarouselApi,
255
+ Carousel,
256
+ CarouselContent,
257
+ CarouselItem,
258
+ CarouselPrevious,
259
+ CarouselNext,
260
+ }
src/components/ui/chart.tsx ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as RechartsPrimitive from "recharts"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ // Format: { THEME_NAME: CSS_SELECTOR }
7
+ const THEMES = { light: "", dark: ".dark" } as const
8
+
9
+ export type ChartConfig = {
10
+ [k in string]: {
11
+ label?: React.ReactNode
12
+ icon?: React.ComponentType
13
+ } & (
14
+ | { color?: string; theme?: never }
15
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
16
+ )
17
+ }
18
+
19
+ type ChartContextProps = {
20
+ config: ChartConfig
21
+ }
22
+
23
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
24
+
25
+ function useChart() {
26
+ const context = React.useContext(ChartContext)
27
+
28
+ if (!context) {
29
+ throw new Error("useChart must be used within a <ChartContainer />")
30
+ }
31
+
32
+ return context
33
+ }
34
+
35
+ const ChartContainer = React.forwardRef<
36
+ HTMLDivElement,
37
+ React.ComponentProps<"div"> & {
38
+ config: ChartConfig
39
+ children: React.ComponentProps<
40
+ typeof RechartsPrimitive.ResponsiveContainer
41
+ >["children"]
42
+ }
43
+ >(({ id, className, children, config, ...props }, ref) => {
44
+ const uniqueId = React.useId()
45
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
46
+
47
+ return (
48
+ <ChartContext.Provider value={{ config }}>
49
+ <div
50
+ data-chart={chartId}
51
+ ref={ref}
52
+ className={cn(
53
+ "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",
54
+ className
55
+ )}
56
+ {...props}
57
+ >
58
+ <ChartStyle id={chartId} config={config} />
59
+ <RechartsPrimitive.ResponsiveContainer>
60
+ {children}
61
+ </RechartsPrimitive.ResponsiveContainer>
62
+ </div>
63
+ </ChartContext.Provider>
64
+ )
65
+ })
66
+ ChartContainer.displayName = "Chart"
67
+
68
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
69
+ const colorConfig = Object.entries(config).filter(
70
+ ([_, config]) => config.theme || config.color
71
+ )
72
+
73
+ if (!colorConfig.length) {
74
+ return null
75
+ }
76
+
77
+ return (
78
+ <style
79
+ dangerouslySetInnerHTML={{
80
+ __html: Object.entries(THEMES)
81
+ .map(
82
+ ([theme, prefix]) => `
83
+ ${prefix} [data-chart=${id}] {
84
+ ${colorConfig
85
+ .map(([key, itemConfig]) => {
86
+ const color =
87
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
88
+ itemConfig.color
89
+ return color ? ` --color-${key}: ${color};` : null
90
+ })
91
+ .join("\n")}
92
+ }
93
+ `
94
+ )
95
+ .join("\n"),
96
+ }}
97
+ />
98
+ )
99
+ }
100
+
101
+ const ChartTooltip = RechartsPrimitive.Tooltip
102
+
103
+ const ChartTooltipContent = React.forwardRef<
104
+ HTMLDivElement,
105
+ React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
106
+ React.ComponentProps<"div"> & {
107
+ hideLabel?: boolean
108
+ hideIndicator?: boolean
109
+ indicator?: "line" | "dot" | "dashed"
110
+ nameKey?: string
111
+ labelKey?: string
112
+ }
113
+ >(
114
+ (
115
+ {
116
+ active,
117
+ payload,
118
+ className,
119
+ indicator = "dot",
120
+ hideLabel = false,
121
+ hideIndicator = false,
122
+ label,
123
+ labelFormatter,
124
+ labelClassName,
125
+ formatter,
126
+ color,
127
+ nameKey,
128
+ labelKey,
129
+ },
130
+ ref
131
+ ) => {
132
+ const { config } = useChart()
133
+
134
+ const tooltipLabel = React.useMemo(() => {
135
+ if (hideLabel || !payload?.length) {
136
+ return null
137
+ }
138
+
139
+ const [item] = payload
140
+ const key = `${labelKey || item.dataKey || item.name || "value"}`
141
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
142
+ const value =
143
+ !labelKey && typeof label === "string"
144
+ ? config[label as keyof typeof config]?.label || label
145
+ : itemConfig?.label
146
+
147
+ if (labelFormatter) {
148
+ return (
149
+ <div className={cn("font-medium", labelClassName)}>
150
+ {labelFormatter(value, payload)}
151
+ </div>
152
+ )
153
+ }
154
+
155
+ if (!value) {
156
+ return null
157
+ }
158
+
159
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
160
+ }, [
161
+ label,
162
+ labelFormatter,
163
+ payload,
164
+ hideLabel,
165
+ labelClassName,
166
+ config,
167
+ labelKey,
168
+ ])
169
+
170
+ if (!active || !payload?.length) {
171
+ return null
172
+ }
173
+
174
+ const nestLabel = payload.length === 1 && indicator !== "dot"
175
+
176
+ return (
177
+ <div
178
+ ref={ref}
179
+ className={cn(
180
+ "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",
181
+ className
182
+ )}
183
+ >
184
+ {!nestLabel ? tooltipLabel : null}
185
+ <div className="grid gap-1.5">
186
+ {payload.map((item, index) => {
187
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
188
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
189
+ const indicatorColor = color || item.payload.fill || item.color
190
+
191
+ return (
192
+ <div
193
+ key={item.dataKey}
194
+ className={cn(
195
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
196
+ indicator === "dot" && "items-center"
197
+ )}
198
+ >
199
+ {formatter && item?.value !== undefined && item.name ? (
200
+ formatter(item.value, item.name, item, index, item.payload)
201
+ ) : (
202
+ <>
203
+ {itemConfig?.icon ? (
204
+ <itemConfig.icon />
205
+ ) : (
206
+ !hideIndicator && (
207
+ <div
208
+ className={cn(
209
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
210
+ {
211
+ "h-2.5 w-2.5": indicator === "dot",
212
+ "w-1": indicator === "line",
213
+ "w-0 border-[1.5px] border-dashed bg-transparent":
214
+ indicator === "dashed",
215
+ "my-0.5": nestLabel && indicator === "dashed",
216
+ }
217
+ )}
218
+ style={
219
+ {
220
+ "--color-bg": indicatorColor,
221
+ "--color-border": indicatorColor,
222
+ } as React.CSSProperties
223
+ }
224
+ />
225
+ )
226
+ )}
227
+ <div
228
+ className={cn(
229
+ "flex flex-1 justify-between leading-none",
230
+ nestLabel ? "items-end" : "items-center"
231
+ )}
232
+ >
233
+ <div className="grid gap-1.5">
234
+ {nestLabel ? tooltipLabel : null}
235
+ <span className="text-muted-foreground">
236
+ {itemConfig?.label || item.name}
237
+ </span>
238
+ </div>
239
+ {item.value && (
240
+ <span className="font-mono font-medium tabular-nums text-foreground">
241
+ {item.value.toLocaleString()}
242
+ </span>
243
+ )}
244
+ </div>
245
+ </>
246
+ )}
247
+ </div>
248
+ )
249
+ })}
250
+ </div>
251
+ </div>
252
+ )
253
+ }
254
+ )
255
+ ChartTooltipContent.displayName = "ChartTooltip"
256
+
257
+ const ChartLegend = RechartsPrimitive.Legend
258
+
259
+ const ChartLegendContent = React.forwardRef<
260
+ HTMLDivElement,
261
+ React.ComponentProps<"div"> &
262
+ Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
263
+ hideIcon?: boolean
264
+ nameKey?: string
265
+ }
266
+ >(
267
+ (
268
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
269
+ ref
270
+ ) => {
271
+ const { config } = useChart()
272
+
273
+ if (!payload?.length) {
274
+ return null
275
+ }
276
+
277
+ return (
278
+ <div
279
+ ref={ref}
280
+ className={cn(
281
+ "flex items-center justify-center gap-4",
282
+ verticalAlign === "top" ? "pb-3" : "pt-3",
283
+ className
284
+ )}
285
+ >
286
+ {payload.map((item) => {
287
+ const key = `${nameKey || item.dataKey || "value"}`
288
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
289
+
290
+ return (
291
+ <div
292
+ key={item.value}
293
+ className={cn(
294
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
295
+ )}
296
+ >
297
+ {itemConfig?.icon && !hideIcon ? (
298
+ <itemConfig.icon />
299
+ ) : (
300
+ <div
301
+ className="h-2 w-2 shrink-0 rounded-[2px]"
302
+ style={{
303
+ backgroundColor: item.color,
304
+ }}
305
+ />
306
+ )}
307
+ {itemConfig?.label}
308
+ </div>
309
+ )
310
+ })}
311
+ </div>
312
+ )
313
+ }
314
+ )
315
+ ChartLegendContent.displayName = "ChartLegend"
316
+
317
+ // Helper to extract item config from a payload.
318
+ function getPayloadConfigFromPayload(
319
+ config: ChartConfig,
320
+ payload: unknown,
321
+ key: string
322
+ ) {
323
+ if (typeof payload !== "object" || payload === null) {
324
+ return undefined
325
+ }
326
+
327
+ const payloadPayload =
328
+ "payload" in payload &&
329
+ typeof payload.payload === "object" &&
330
+ payload.payload !== null
331
+ ? payload.payload
332
+ : undefined
333
+
334
+ let configLabelKey: string = key
335
+
336
+ if (
337
+ key in payload &&
338
+ typeof payload[key as keyof typeof payload] === "string"
339
+ ) {
340
+ configLabelKey = payload[key as keyof typeof payload] as string
341
+ } else if (
342
+ payloadPayload &&
343
+ key in payloadPayload &&
344
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
345
+ ) {
346
+ configLabelKey = payloadPayload[
347
+ key as keyof typeof payloadPayload
348
+ ] as string
349
+ }
350
+
351
+ return configLabelKey in config
352
+ ? config[configLabelKey]
353
+ : config[key as keyof typeof config]
354
+ }
355
+
356
+ export {
357
+ ChartContainer,
358
+ ChartTooltip,
359
+ ChartTooltipContent,
360
+ ChartLegend,
361
+ ChartLegendContent,
362
+ ChartStyle,
363
+ }
src/components/ui/checkbox.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
+ import { Check } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Checkbox = React.forwardRef<
8
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <CheckboxPrimitive.Root
12
+ ref={ref}
13
+ className={cn(
14
+ "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",
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ <CheckboxPrimitive.Indicator
20
+ className={cn("flex items-center justify-center text-current")}
21
+ >
22
+ <Check className="h-4 w-4" />
23
+ </CheckboxPrimitive.Indicator>
24
+ </CheckboxPrimitive.Root>
25
+ ))
26
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
27
+
28
+ export { Checkbox }
src/components/ui/collapsible.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
2
+
3
+ const Collapsible = CollapsiblePrimitive.Root
4
+
5
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
6
+
7
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
8
+
9
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
src/components/ui/command.tsx ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { type DialogProps } from "@radix-ui/react-dialog"
3
+ import { Command as CommandPrimitive } from "cmdk"
4
+ import { Search } from "lucide-react"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import { Dialog, DialogContent } from "@/components/ui/dialog"
8
+
9
+ const Command = React.forwardRef<
10
+ React.ElementRef<typeof CommandPrimitive>,
11
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
12
+ >(({ className, ...props }, ref) => (
13
+ <CommandPrimitive
14
+ ref={ref}
15
+ className={cn(
16
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ ))
22
+ Command.displayName = CommandPrimitive.displayName
23
+
24
+ interface CommandDialogProps extends DialogProps {}
25
+
26
+ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
27
+ return (
28
+ <Dialog {...props}>
29
+ <DialogContent className="overflow-hidden p-0 shadow-lg">
30
+ <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">
31
+ {children}
32
+ </Command>
33
+ </DialogContent>
34
+ </Dialog>
35
+ )
36
+ }
37
+
38
+ const CommandInput = React.forwardRef<
39
+ React.ElementRef<typeof CommandPrimitive.Input>,
40
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
41
+ >(({ className, ...props }, ref) => (
42
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
43
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
44
+ <CommandPrimitive.Input
45
+ ref={ref}
46
+ className={cn(
47
+ "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",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ </div>
53
+ ))
54
+
55
+ CommandInput.displayName = CommandPrimitive.Input.displayName
56
+
57
+ const CommandList = React.forwardRef<
58
+ React.ElementRef<typeof CommandPrimitive.List>,
59
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
60
+ >(({ className, ...props }, ref) => (
61
+ <CommandPrimitive.List
62
+ ref={ref}
63
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
64
+ {...props}
65
+ />
66
+ ))
67
+
68
+ CommandList.displayName = CommandPrimitive.List.displayName
69
+
70
+ const CommandEmpty = React.forwardRef<
71
+ React.ElementRef<typeof CommandPrimitive.Empty>,
72
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
73
+ >((props, ref) => (
74
+ <CommandPrimitive.Empty
75
+ ref={ref}
76
+ className="py-6 text-center text-sm"
77
+ {...props}
78
+ />
79
+ ))
80
+
81
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName
82
+
83
+ const CommandGroup = React.forwardRef<
84
+ React.ElementRef<typeof CommandPrimitive.Group>,
85
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
86
+ >(({ className, ...props }, ref) => (
87
+ <CommandPrimitive.Group
88
+ ref={ref}
89
+ className={cn(
90
+ "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",
91
+ className
92
+ )}
93
+ {...props}
94
+ />
95
+ ))
96
+
97
+ CommandGroup.displayName = CommandPrimitive.Group.displayName
98
+
99
+ const CommandSeparator = React.forwardRef<
100
+ React.ElementRef<typeof CommandPrimitive.Separator>,
101
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
102
+ >(({ className, ...props }, ref) => (
103
+ <CommandPrimitive.Separator
104
+ ref={ref}
105
+ className={cn("-mx-1 h-px bg-border", className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName
110
+
111
+ const CommandItem = React.forwardRef<
112
+ React.ElementRef<typeof CommandPrimitive.Item>,
113
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
114
+ >(({ className, ...props }, ref) => (
115
+ <CommandPrimitive.Item
116
+ ref={ref}
117
+ className={cn(
118
+ "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",
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ ))
124
+
125
+ CommandItem.displayName = CommandPrimitive.Item.displayName
126
+
127
+ const CommandShortcut = ({
128
+ className,
129
+ ...props
130
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
131
+ return (
132
+ <span
133
+ className={cn(
134
+ "ml-auto text-xs tracking-widest text-muted-foreground",
135
+ className
136
+ )}
137
+ {...props}
138
+ />
139
+ )
140
+ }
141
+ CommandShortcut.displayName = "CommandShortcut"
142
+
143
+ export {
144
+ Command,
145
+ CommandDialog,
146
+ CommandInput,
147
+ CommandList,
148
+ CommandEmpty,
149
+ CommandGroup,
150
+ CommandItem,
151
+ CommandShortcut,
152
+ CommandSeparator,
153
+ }
src/components/ui/context-menu.tsx ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const ContextMenu = ContextMenuPrimitive.Root
8
+
9
+ const ContextMenuTrigger = ContextMenuPrimitive.Trigger
10
+
11
+ const ContextMenuGroup = ContextMenuPrimitive.Group
12
+
13
+ const ContextMenuPortal = ContextMenuPrimitive.Portal
14
+
15
+ const ContextMenuSub = ContextMenuPrimitive.Sub
16
+
17
+ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
18
+
19
+ const ContextMenuSubTrigger = React.forwardRef<
20
+ React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
21
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
22
+ inset?: boolean
23
+ }
24
+ >(({ className, inset, children, ...props }, ref) => (
25
+ <ContextMenuPrimitive.SubTrigger
26
+ ref={ref}
27
+ className={cn(
28
+ "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",
29
+ inset && "pl-8",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronRight className="ml-auto h-4 w-4" />
36
+ </ContextMenuPrimitive.SubTrigger>
37
+ ))
38
+ ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
39
+
40
+ const ContextMenuSubContent = React.forwardRef<
41
+ React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
42
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
43
+ >(({ className, ...props }, ref) => (
44
+ <ContextMenuPrimitive.SubContent
45
+ ref={ref}
46
+ className={cn(
47
+ "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",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ ))
53
+ ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
54
+
55
+ const ContextMenuContent = React.forwardRef<
56
+ React.ElementRef<typeof ContextMenuPrimitive.Content>,
57
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
58
+ >(({ className, ...props }, ref) => (
59
+ <ContextMenuPrimitive.Portal>
60
+ <ContextMenuPrimitive.Content
61
+ ref={ref}
62
+ className={cn(
63
+ "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",
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ </ContextMenuPrimitive.Portal>
69
+ ))
70
+ ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
71
+
72
+ const ContextMenuItem = React.forwardRef<
73
+ React.ElementRef<typeof ContextMenuPrimitive.Item>,
74
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
75
+ inset?: boolean
76
+ }
77
+ >(({ className, inset, ...props }, ref) => (
78
+ <ContextMenuPrimitive.Item
79
+ ref={ref}
80
+ className={cn(
81
+ "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",
82
+ inset && "pl-8",
83
+ className
84
+ )}
85
+ {...props}
86
+ />
87
+ ))
88
+ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
89
+
90
+ const ContextMenuCheckboxItem = React.forwardRef<
91
+ React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
92
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
93
+ >(({ className, children, checked, ...props }, ref) => (
94
+ <ContextMenuPrimitive.CheckboxItem
95
+ ref={ref}
96
+ className={cn(
97
+ "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",
98
+ className
99
+ )}
100
+ checked={checked}
101
+ {...props}
102
+ >
103
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
104
+ <ContextMenuPrimitive.ItemIndicator>
105
+ <Check className="h-4 w-4" />
106
+ </ContextMenuPrimitive.ItemIndicator>
107
+ </span>
108
+ {children}
109
+ </ContextMenuPrimitive.CheckboxItem>
110
+ ))
111
+ ContextMenuCheckboxItem.displayName =
112
+ ContextMenuPrimitive.CheckboxItem.displayName
113
+
114
+ const ContextMenuRadioItem = React.forwardRef<
115
+ React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
116
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
117
+ >(({ className, children, ...props }, ref) => (
118
+ <ContextMenuPrimitive.RadioItem
119
+ ref={ref}
120
+ className={cn(
121
+ "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",
122
+ className
123
+ )}
124
+ {...props}
125
+ >
126
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
127
+ <ContextMenuPrimitive.ItemIndicator>
128
+ <Circle className="h-2 w-2 fill-current" />
129
+ </ContextMenuPrimitive.ItemIndicator>
130
+ </span>
131
+ {children}
132
+ </ContextMenuPrimitive.RadioItem>
133
+ ))
134
+ ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
135
+
136
+ const ContextMenuLabel = React.forwardRef<
137
+ React.ElementRef<typeof ContextMenuPrimitive.Label>,
138
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
139
+ inset?: boolean
140
+ }
141
+ >(({ className, inset, ...props }, ref) => (
142
+ <ContextMenuPrimitive.Label
143
+ ref={ref}
144
+ className={cn(
145
+ "px-2 py-1.5 text-sm font-semibold text-foreground",
146
+ inset && "pl-8",
147
+ className
148
+ )}
149
+ {...props}
150
+ />
151
+ ))
152
+ ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
153
+
154
+ const ContextMenuSeparator = React.forwardRef<
155
+ React.ElementRef<typeof ContextMenuPrimitive.Separator>,
156
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
157
+ >(({ className, ...props }, ref) => (
158
+ <ContextMenuPrimitive.Separator
159
+ ref={ref}
160
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
161
+ {...props}
162
+ />
163
+ ))
164
+ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
165
+
166
+ const ContextMenuShortcut = ({
167
+ className,
168
+ ...props
169
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
170
+ return (
171
+ <span
172
+ className={cn(
173
+ "ml-auto text-xs tracking-widest text-muted-foreground",
174
+ className
175
+ )}
176
+ {...props}
177
+ />
178
+ )
179
+ }
180
+ ContextMenuShortcut.displayName = "ContextMenuShortcut"
181
+
182
+ export {
183
+ ContextMenu,
184
+ ContextMenuTrigger,
185
+ ContextMenuContent,
186
+ ContextMenuItem,
187
+ ContextMenuCheckboxItem,
188
+ ContextMenuRadioItem,
189
+ ContextMenuLabel,
190
+ ContextMenuSeparator,
191
+ ContextMenuShortcut,
192
+ ContextMenuGroup,
193
+ ContextMenuPortal,
194
+ ContextMenuSub,
195
+ ContextMenuSubContent,
196
+ ContextMenuSubTrigger,
197
+ ContextMenuRadioGroup,
198
+ }
src/components/ui/dialog.tsx ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
3
+ import { X } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Dialog = DialogPrimitive.Root
8
+
9
+ const DialogTrigger = DialogPrimitive.Trigger
10
+
11
+ const DialogPortal = DialogPrimitive.Portal
12
+
13
+ const DialogClose = DialogPrimitive.Close
14
+
15
+ const DialogOverlay = React.forwardRef<
16
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
17
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
18
+ >(({ className, ...props }, ref) => (
19
+ <DialogPrimitive.Overlay
20
+ ref={ref}
21
+ className={cn(
22
+ "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",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
29
+
30
+ const DialogContent = React.forwardRef<
31
+ React.ElementRef<typeof DialogPrimitive.Content>,
32
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
33
+ >(({ className, children, ...props }, ref) => (
34
+ <DialogPortal>
35
+ <DialogOverlay />
36
+ <DialogPrimitive.Content
37
+ ref={ref}
38
+ className={cn(
39
+ "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",
40
+ className
41
+ )}
42
+ {...props}
43
+ >
44
+ {children}
45
+ <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">
46
+ <X className="h-4 w-4" />
47
+ <span className="sr-only">Close</span>
48
+ </DialogPrimitive.Close>
49
+ </DialogPrimitive.Content>
50
+ </DialogPortal>
51
+ ))
52
+ DialogContent.displayName = DialogPrimitive.Content.displayName
53
+
54
+ const DialogHeader = ({
55
+ className,
56
+ ...props
57
+ }: React.HTMLAttributes<HTMLDivElement>) => (
58
+ <div
59
+ className={cn(
60
+ "flex flex-col space-y-1.5 text-center sm:text-left",
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ DialogHeader.displayName = "DialogHeader"
67
+
68
+ const DialogFooter = ({
69
+ className,
70
+ ...props
71
+ }: React.HTMLAttributes<HTMLDivElement>) => (
72
+ <div
73
+ className={cn(
74
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
75
+ className
76
+ )}
77
+ {...props}
78
+ />
79
+ )
80
+ DialogFooter.displayName = "DialogFooter"
81
+
82
+ const DialogTitle = React.forwardRef<
83
+ React.ElementRef<typeof DialogPrimitive.Title>,
84
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
85
+ >(({ className, ...props }, ref) => (
86
+ <DialogPrimitive.Title
87
+ ref={ref}
88
+ className={cn(
89
+ "text-lg font-semibold leading-none tracking-tight",
90
+ className
91
+ )}
92
+ {...props}
93
+ />
94
+ ))
95
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
96
+
97
+ const DialogDescription = React.forwardRef<
98
+ React.ElementRef<typeof DialogPrimitive.Description>,
99
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
100
+ >(({ className, ...props }, ref) => (
101
+ <DialogPrimitive.Description
102
+ ref={ref}
103
+ className={cn("text-sm text-muted-foreground", className)}
104
+ {...props}
105
+ />
106
+ ))
107
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
108
+
109
+ export {
110
+ Dialog,
111
+ DialogPortal,
112
+ DialogOverlay,
113
+ DialogClose,
114
+ DialogTrigger,
115
+ DialogContent,
116
+ DialogHeader,
117
+ DialogFooter,
118
+ DialogTitle,
119
+ DialogDescription,
120
+ }
src/components/ui/drawer.tsx ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Drawer as DrawerPrimitive } from "vaul"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Drawer = ({
7
+ shouldScaleBackground = true,
8
+ ...props
9
+ }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
10
+ <DrawerPrimitive.Root
11
+ shouldScaleBackground={shouldScaleBackground}
12
+ {...props}
13
+ />
14
+ )
15
+ Drawer.displayName = "Drawer"
16
+
17
+ const DrawerTrigger = DrawerPrimitive.Trigger
18
+
19
+ const DrawerPortal = DrawerPrimitive.Portal
20
+
21
+ const DrawerClose = DrawerPrimitive.Close
22
+
23
+ const DrawerOverlay = React.forwardRef<
24
+ React.ElementRef<typeof DrawerPrimitive.Overlay>,
25
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
26
+ >(({ className, ...props }, ref) => (
27
+ <DrawerPrimitive.Overlay
28
+ ref={ref}
29
+ className={cn("fixed inset-0 z-50 bg-black/80", className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
34
+
35
+ const DrawerContent = React.forwardRef<
36
+ React.ElementRef<typeof DrawerPrimitive.Content>,
37
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
38
+ >(({ className, children, ...props }, ref) => (
39
+ <DrawerPortal>
40
+ <DrawerOverlay />
41
+ <DrawerPrimitive.Content
42
+ ref={ref}
43
+ className={cn(
44
+ "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
45
+ className
46
+ )}
47
+ {...props}
48
+ >
49
+ <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
50
+ {children}
51
+ </DrawerPrimitive.Content>
52
+ </DrawerPortal>
53
+ ))
54
+ DrawerContent.displayName = "DrawerContent"
55
+
56
+ const DrawerHeader = ({
57
+ className,
58
+ ...props
59
+ }: React.HTMLAttributes<HTMLDivElement>) => (
60
+ <div
61
+ className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
62
+ {...props}
63
+ />
64
+ )
65
+ DrawerHeader.displayName = "DrawerHeader"
66
+
67
+ const DrawerFooter = ({
68
+ className,
69
+ ...props
70
+ }: React.HTMLAttributes<HTMLDivElement>) => (
71
+ <div
72
+ className={cn("mt-auto flex flex-col gap-2 p-4", className)}
73
+ {...props}
74
+ />
75
+ )
76
+ DrawerFooter.displayName = "DrawerFooter"
77
+
78
+ const DrawerTitle = React.forwardRef<
79
+ React.ElementRef<typeof DrawerPrimitive.Title>,
80
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
81
+ >(({ className, ...props }, ref) => (
82
+ <DrawerPrimitive.Title
83
+ ref={ref}
84
+ className={cn(
85
+ "text-lg font-semibold leading-none tracking-tight",
86
+ className
87
+ )}
88
+ {...props}
89
+ />
90
+ ))
91
+ DrawerTitle.displayName = DrawerPrimitive.Title.displayName
92
+
93
+ const DrawerDescription = React.forwardRef<
94
+ React.ElementRef<typeof DrawerPrimitive.Description>,
95
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
96
+ >(({ className, ...props }, ref) => (
97
+ <DrawerPrimitive.Description
98
+ ref={ref}
99
+ className={cn("text-sm text-muted-foreground", className)}
100
+ {...props}
101
+ />
102
+ ))
103
+ DrawerDescription.displayName = DrawerPrimitive.Description.displayName
104
+
105
+ export {
106
+ Drawer,
107
+ DrawerPortal,
108
+ DrawerOverlay,
109
+ DrawerTrigger,
110
+ DrawerClose,
111
+ DrawerContent,
112
+ DrawerHeader,
113
+ DrawerFooter,
114
+ DrawerTitle,
115
+ DrawerDescription,
116
+ }
src/components/ui/dropdown-menu.tsx ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const DropdownMenu = DropdownMenuPrimitive.Root
8
+
9
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
10
+
11
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
12
+
13
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
14
+
15
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
16
+
17
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
18
+
19
+ const DropdownMenuSubTrigger = React.forwardRef<
20
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
21
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
22
+ inset?: boolean
23
+ }
24
+ >(({ className, inset, children, ...props }, ref) => (
25
+ <DropdownMenuPrimitive.SubTrigger
26
+ ref={ref}
27
+ className={cn(
28
+ "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",
29
+ inset && "pl-8",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronRight className="ml-auto h-4 w-4" />
36
+ </DropdownMenuPrimitive.SubTrigger>
37
+ ))
38
+ DropdownMenuSubTrigger.displayName =
39
+ DropdownMenuPrimitive.SubTrigger.displayName
40
+
41
+ const DropdownMenuSubContent = React.forwardRef<
42
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
43
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
44
+ >(({ className, ...props }, ref) => (
45
+ <DropdownMenuPrimitive.SubContent
46
+ ref={ref}
47
+ className={cn(
48
+ "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",
49
+ className
50
+ )}
51
+ {...props}
52
+ />
53
+ ))
54
+ DropdownMenuSubContent.displayName =
55
+ DropdownMenuPrimitive.SubContent.displayName
56
+
57
+ const DropdownMenuContent = React.forwardRef<
58
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
59
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
60
+ >(({ className, sideOffset = 4, ...props }, ref) => (
61
+ <DropdownMenuPrimitive.Portal>
62
+ <DropdownMenuPrimitive.Content
63
+ ref={ref}
64
+ sideOffset={sideOffset}
65
+ className={cn(
66
+ "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",
67
+ className
68
+ )}
69
+ {...props}
70
+ />
71
+ </DropdownMenuPrimitive.Portal>
72
+ ))
73
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
74
+
75
+ const DropdownMenuItem = React.forwardRef<
76
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
77
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
78
+ inset?: boolean
79
+ }
80
+ >(({ className, inset, ...props }, ref) => (
81
+ <DropdownMenuPrimitive.Item
82
+ ref={ref}
83
+ className={cn(
84
+ "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",
85
+ inset && "pl-8",
86
+ className
87
+ )}
88
+ {...props}
89
+ />
90
+ ))
91
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
92
+
93
+ const DropdownMenuCheckboxItem = React.forwardRef<
94
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
95
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
96
+ >(({ className, children, checked, ...props }, ref) => (
97
+ <DropdownMenuPrimitive.CheckboxItem
98
+ ref={ref}
99
+ className={cn(
100
+ "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",
101
+ className
102
+ )}
103
+ checked={checked}
104
+ {...props}
105
+ >
106
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
107
+ <DropdownMenuPrimitive.ItemIndicator>
108
+ <Check className="h-4 w-4" />
109
+ </DropdownMenuPrimitive.ItemIndicator>
110
+ </span>
111
+ {children}
112
+ </DropdownMenuPrimitive.CheckboxItem>
113
+ ))
114
+ DropdownMenuCheckboxItem.displayName =
115
+ DropdownMenuPrimitive.CheckboxItem.displayName
116
+
117
+ const DropdownMenuRadioItem = React.forwardRef<
118
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
119
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
120
+ >(({ className, children, ...props }, ref) => (
121
+ <DropdownMenuPrimitive.RadioItem
122
+ ref={ref}
123
+ className={cn(
124
+ "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",
125
+ className
126
+ )}
127
+ {...props}
128
+ >
129
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
130
+ <DropdownMenuPrimitive.ItemIndicator>
131
+ <Circle className="h-2 w-2 fill-current" />
132
+ </DropdownMenuPrimitive.ItemIndicator>
133
+ </span>
134
+ {children}
135
+ </DropdownMenuPrimitive.RadioItem>
136
+ ))
137
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
138
+
139
+ const DropdownMenuLabel = React.forwardRef<
140
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
141
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
142
+ inset?: boolean
143
+ }
144
+ >(({ className, inset, ...props }, ref) => (
145
+ <DropdownMenuPrimitive.Label
146
+ ref={ref}
147
+ className={cn(
148
+ "px-2 py-1.5 text-sm font-semibold",
149
+ inset && "pl-8",
150
+ className
151
+ )}
152
+ {...props}
153
+ />
154
+ ))
155
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
156
+
157
+ const DropdownMenuSeparator = React.forwardRef<
158
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
159
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
160
+ >(({ className, ...props }, ref) => (
161
+ <DropdownMenuPrimitive.Separator
162
+ ref={ref}
163
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
164
+ {...props}
165
+ />
166
+ ))
167
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
168
+
169
+ const DropdownMenuShortcut = ({
170
+ className,
171
+ ...props
172
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
173
+ return (
174
+ <span
175
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
176
+ {...props}
177
+ />
178
+ )
179
+ }
180
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
181
+
182
+ export {
183
+ DropdownMenu,
184
+ DropdownMenuTrigger,
185
+ DropdownMenuContent,
186
+ DropdownMenuItem,
187
+ DropdownMenuCheckboxItem,
188
+ DropdownMenuRadioItem,
189
+ DropdownMenuLabel,
190
+ DropdownMenuSeparator,
191
+ DropdownMenuShortcut,
192
+ DropdownMenuGroup,
193
+ DropdownMenuPortal,
194
+ DropdownMenuSub,
195
+ DropdownMenuSubContent,
196
+ DropdownMenuSubTrigger,
197
+ DropdownMenuRadioGroup,
198
+ }
src/components/ui/form.tsx ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { Slot } from "@radix-ui/react-slot"
4
+ import {
5
+ Controller,
6
+ ControllerProps,
7
+ FieldPath,
8
+ FieldValues,
9
+ FormProvider,
10
+ useFormContext,
11
+ } from "react-hook-form"
12
+
13
+ import { cn } from "@/lib/utils"
14
+ import { Label } from "@/components/ui/label"
15
+
16
+ const Form = FormProvider
17
+
18
+ type FormFieldContextValue<
19
+ TFieldValues extends FieldValues = FieldValues,
20
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
21
+ > = {
22
+ name: TName
23
+ }
24
+
25
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
26
+ {} as FormFieldContextValue
27
+ )
28
+
29
+ const FormField = <
30
+ TFieldValues extends FieldValues = FieldValues,
31
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
32
+ >({
33
+ ...props
34
+ }: ControllerProps<TFieldValues, TName>) => {
35
+ return (
36
+ <FormFieldContext.Provider value={{ name: props.name }}>
37
+ <Controller {...props} />
38
+ </FormFieldContext.Provider>
39
+ )
40
+ }
41
+
42
+ const useFormField = () => {
43
+ const fieldContext = React.useContext(FormFieldContext)
44
+ const itemContext = React.useContext(FormItemContext)
45
+ const { getFieldState, formState } = useFormContext()
46
+
47
+ const fieldState = getFieldState(fieldContext.name, formState)
48
+
49
+ if (!fieldContext) {
50
+ throw new Error("useFormField should be used within <FormField>")
51
+ }
52
+
53
+ const { id } = itemContext
54
+
55
+ return {
56
+ id,
57
+ name: fieldContext.name,
58
+ formItemId: `${id}-form-item`,
59
+ formDescriptionId: `${id}-form-item-description`,
60
+ formMessageId: `${id}-form-item-message`,
61
+ ...fieldState,
62
+ }
63
+ }
64
+
65
+ type FormItemContextValue = {
66
+ id: string
67
+ }
68
+
69
+ const FormItemContext = React.createContext<FormItemContextValue>(
70
+ {} as FormItemContextValue
71
+ )
72
+
73
+ const FormItem = React.forwardRef<
74
+ HTMLDivElement,
75
+ React.HTMLAttributes<HTMLDivElement>
76
+ >(({ className, ...props }, ref) => {
77
+ const id = React.useId()
78
+
79
+ return (
80
+ <FormItemContext.Provider value={{ id }}>
81
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
82
+ </FormItemContext.Provider>
83
+ )
84
+ })
85
+ FormItem.displayName = "FormItem"
86
+
87
+ const FormLabel = React.forwardRef<
88
+ React.ElementRef<typeof LabelPrimitive.Root>,
89
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
90
+ >(({ className, ...props }, ref) => {
91
+ const { error, formItemId } = useFormField()
92
+
93
+ return (
94
+ <Label
95
+ ref={ref}
96
+ className={cn(error && "text-destructive", className)}
97
+ htmlFor={formItemId}
98
+ {...props}
99
+ />
100
+ )
101
+ })
102
+ FormLabel.displayName = "FormLabel"
103
+
104
+ const FormControl = React.forwardRef<
105
+ React.ElementRef<typeof Slot>,
106
+ React.ComponentPropsWithoutRef<typeof Slot>
107
+ >(({ ...props }, ref) => {
108
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109
+
110
+ return (
111
+ <Slot
112
+ ref={ref}
113
+ id={formItemId}
114
+ aria-describedby={
115
+ !error
116
+ ? `${formDescriptionId}`
117
+ : `${formDescriptionId} ${formMessageId}`
118
+ }
119
+ aria-invalid={!!error}
120
+ {...props}
121
+ />
122
+ )
123
+ })
124
+ FormControl.displayName = "FormControl"
125
+
126
+ const FormDescription = React.forwardRef<
127
+ HTMLParagraphElement,
128
+ React.HTMLAttributes<HTMLParagraphElement>
129
+ >(({ className, ...props }, ref) => {
130
+ const { formDescriptionId } = useFormField()
131
+
132
+ return (
133
+ <p
134
+ ref={ref}
135
+ id={formDescriptionId}
136
+ className={cn("text-sm text-muted-foreground", className)}
137
+ {...props}
138
+ />
139
+ )
140
+ })
141
+ FormDescription.displayName = "FormDescription"
142
+
143
+ const FormMessage = React.forwardRef<
144
+ HTMLParagraphElement,
145
+ React.HTMLAttributes<HTMLParagraphElement>
146
+ >(({ className, children, ...props }, ref) => {
147
+ const { error, formMessageId } = useFormField()
148
+ const body = error ? String(error?.message) : children
149
+
150
+ if (!body) {
151
+ return null
152
+ }
153
+
154
+ return (
155
+ <p
156
+ ref={ref}
157
+ id={formMessageId}
158
+ className={cn("text-sm font-medium text-destructive", className)}
159
+ {...props}
160
+ >
161
+ {body}
162
+ </p>
163
+ )
164
+ })
165
+ FormMessage.displayName = "FormMessage"
166
+
167
+ export {
168
+ useFormField,
169
+ Form,
170
+ FormItem,
171
+ FormLabel,
172
+ FormControl,
173
+ FormDescription,
174
+ FormMessage,
175
+ FormField,
176
+ }
src/components/ui/hover-card.tsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const HoverCard = HoverCardPrimitive.Root
7
+
8
+ const HoverCardTrigger = HoverCardPrimitive.Trigger
9
+
10
+ const HoverCardContent = React.forwardRef<
11
+ React.ElementRef<typeof HoverCardPrimitive.Content>,
12
+ React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
13
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14
+ <HoverCardPrimitive.Content
15
+ ref={ref}
16
+ align={align}
17
+ sideOffset={sideOffset}
18
+ className={cn(
19
+ "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",
20
+ className
21
+ )}
22
+ {...props}
23
+ />
24
+ ))
25
+ HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
26
+
27
+ export { HoverCard, HoverCardTrigger, HoverCardContent }
src/components/ui/input-otp.tsx ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { OTPInput, OTPInputContext } from "input-otp"
3
+ import { Dot } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const InputOTP = React.forwardRef<
8
+ React.ElementRef<typeof OTPInput>,
9
+ React.ComponentPropsWithoutRef<typeof OTPInput>
10
+ >(({ className, containerClassName, ...props }, ref) => (
11
+ <OTPInput
12
+ ref={ref}
13
+ containerClassName={cn(
14
+ "flex items-center gap-2 has-[:disabled]:opacity-50",
15
+ containerClassName
16
+ )}
17
+ className={cn("disabled:cursor-not-allowed", className)}
18
+ {...props}
19
+ />
20
+ ))
21
+ InputOTP.displayName = "InputOTP"
22
+
23
+ const InputOTPGroup = React.forwardRef<
24
+ React.ElementRef<"div">,
25
+ React.ComponentPropsWithoutRef<"div">
26
+ >(({ className, ...props }, ref) => (
27
+ <div ref={ref} className={cn("flex items-center", className)} {...props} />
28
+ ))
29
+ InputOTPGroup.displayName = "InputOTPGroup"
30
+
31
+ const InputOTPSlot = React.forwardRef<
32
+ React.ElementRef<"div">,
33
+ React.ComponentPropsWithoutRef<"div"> & { index: number }
34
+ >(({ index, className, ...props }, ref) => {
35
+ const inputOTPContext = React.useContext(OTPInputContext)
36
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
37
+
38
+ return (
39
+ <div
40
+ ref={ref}
41
+ className={cn(
42
+ "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",
43
+ isActive && "z-10 ring-2 ring-ring ring-offset-background",
44
+ className
45
+ )}
46
+ {...props}
47
+ >
48
+ {char}
49
+ {hasFakeCaret && (
50
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
51
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
52
+ </div>
53
+ )}
54
+ </div>
55
+ )
56
+ })
57
+ InputOTPSlot.displayName = "InputOTPSlot"
58
+
59
+ const InputOTPSeparator = React.forwardRef<
60
+ React.ElementRef<"div">,
61
+ React.ComponentPropsWithoutRef<"div">
62
+ >(({ ...props }, ref) => (
63
+ <div ref={ref} role="separator" {...props}>
64
+ <Dot />
65
+ </div>
66
+ ))
67
+ InputOTPSeparator.displayName = "InputOTPSeparator"
68
+
69
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
src/components/ui/input.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
+ ({ className, type, ...props }, ref) => {
7
+ return (
8
+ <input
9
+ type={type}
10
+ className={cn(
11
+ "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",
12
+ className
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+ )
20
+ Input.displayName = "Input"
21
+
22
+ export { Input }
src/components/ui/label.tsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const labelVariants = cva(
8
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9
+ )
10
+
11
+ const Label = React.forwardRef<
12
+ React.ElementRef<typeof LabelPrimitive.Root>,
13
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
14
+ VariantProps<typeof labelVariants>
15
+ >(({ className, ...props }, ref) => (
16
+ <LabelPrimitive.Root
17
+ ref={ref}
18
+ className={cn(labelVariants(), className)}
19
+ {...props}
20
+ />
21
+ ))
22
+ Label.displayName = LabelPrimitive.Root.displayName
23
+
24
+ export { Label }
src/components/ui/menubar.tsx ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as MenubarPrimitive from "@radix-ui/react-menubar"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const MenubarMenu = MenubarPrimitive.Menu
8
+
9
+ const MenubarGroup = MenubarPrimitive.Group
10
+
11
+ const MenubarPortal = MenubarPrimitive.Portal
12
+
13
+ const MenubarSub = MenubarPrimitive.Sub
14
+
15
+ const MenubarRadioGroup = MenubarPrimitive.RadioGroup
16
+
17
+ const Menubar = React.forwardRef<
18
+ React.ElementRef<typeof MenubarPrimitive.Root>,
19
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
20
+ >(({ className, ...props }, ref) => (
21
+ <MenubarPrimitive.Root
22
+ ref={ref}
23
+ className={cn(
24
+ "flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ ))
30
+ Menubar.displayName = MenubarPrimitive.Root.displayName
31
+
32
+ const MenubarTrigger = React.forwardRef<
33
+ React.ElementRef<typeof MenubarPrimitive.Trigger>,
34
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
35
+ >(({ className, ...props }, ref) => (
36
+ <MenubarPrimitive.Trigger
37
+ ref={ref}
38
+ className={cn(
39
+ "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",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
46
+
47
+ const MenubarSubTrigger = React.forwardRef<
48
+ React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
49
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
50
+ inset?: boolean
51
+ }
52
+ >(({ className, inset, children, ...props }, ref) => (
53
+ <MenubarPrimitive.SubTrigger
54
+ ref={ref}
55
+ className={cn(
56
+ "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",
57
+ inset && "pl-8",
58
+ className
59
+ )}
60
+ {...props}
61
+ >
62
+ {children}
63
+ <ChevronRight className="ml-auto h-4 w-4" />
64
+ </MenubarPrimitive.SubTrigger>
65
+ ))
66
+ MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
67
+
68
+ const MenubarSubContent = React.forwardRef<
69
+ React.ElementRef<typeof MenubarPrimitive.SubContent>,
70
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
71
+ >(({ className, ...props }, ref) => (
72
+ <MenubarPrimitive.SubContent
73
+ ref={ref}
74
+ className={cn(
75
+ "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",
76
+ className
77
+ )}
78
+ {...props}
79
+ />
80
+ ))
81
+ MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
82
+
83
+ const MenubarContent = React.forwardRef<
84
+ React.ElementRef<typeof MenubarPrimitive.Content>,
85
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
86
+ >(
87
+ (
88
+ { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
89
+ ref
90
+ ) => (
91
+ <MenubarPrimitive.Portal>
92
+ <MenubarPrimitive.Content
93
+ ref={ref}
94
+ align={align}
95
+ alignOffset={alignOffset}
96
+ sideOffset={sideOffset}
97
+ className={cn(
98
+ "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",
99
+ className
100
+ )}
101
+ {...props}
102
+ />
103
+ </MenubarPrimitive.Portal>
104
+ )
105
+ )
106
+ MenubarContent.displayName = MenubarPrimitive.Content.displayName
107
+
108
+ const MenubarItem = React.forwardRef<
109
+ React.ElementRef<typeof MenubarPrimitive.Item>,
110
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
111
+ inset?: boolean
112
+ }
113
+ >(({ className, inset, ...props }, ref) => (
114
+ <MenubarPrimitive.Item
115
+ ref={ref}
116
+ className={cn(
117
+ "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",
118
+ inset && "pl-8",
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ ))
124
+ MenubarItem.displayName = MenubarPrimitive.Item.displayName
125
+
126
+ const MenubarCheckboxItem = React.forwardRef<
127
+ React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
128
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
129
+ >(({ className, children, checked, ...props }, ref) => (
130
+ <MenubarPrimitive.CheckboxItem
131
+ ref={ref}
132
+ className={cn(
133
+ "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",
134
+ className
135
+ )}
136
+ checked={checked}
137
+ {...props}
138
+ >
139
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
140
+ <MenubarPrimitive.ItemIndicator>
141
+ <Check className="h-4 w-4" />
142
+ </MenubarPrimitive.ItemIndicator>
143
+ </span>
144
+ {children}
145
+ </MenubarPrimitive.CheckboxItem>
146
+ ))
147
+ MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
148
+
149
+ const MenubarRadioItem = React.forwardRef<
150
+ React.ElementRef<typeof MenubarPrimitive.RadioItem>,
151
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
152
+ >(({ className, children, ...props }, ref) => (
153
+ <MenubarPrimitive.RadioItem
154
+ ref={ref}
155
+ className={cn(
156
+ "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",
157
+ className
158
+ )}
159
+ {...props}
160
+ >
161
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
162
+ <MenubarPrimitive.ItemIndicator>
163
+ <Circle className="h-2 w-2 fill-current" />
164
+ </MenubarPrimitive.ItemIndicator>
165
+ </span>
166
+ {children}
167
+ </MenubarPrimitive.RadioItem>
168
+ ))
169
+ MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
170
+
171
+ const MenubarLabel = React.forwardRef<
172
+ React.ElementRef<typeof MenubarPrimitive.Label>,
173
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
174
+ inset?: boolean
175
+ }
176
+ >(({ className, inset, ...props }, ref) => (
177
+ <MenubarPrimitive.Label
178
+ ref={ref}
179
+ className={cn(
180
+ "px-2 py-1.5 text-sm font-semibold",
181
+ inset && "pl-8",
182
+ className
183
+ )}
184
+ {...props}
185
+ />
186
+ ))
187
+ MenubarLabel.displayName = MenubarPrimitive.Label.displayName
188
+
189
+ const MenubarSeparator = React.forwardRef<
190
+ React.ElementRef<typeof MenubarPrimitive.Separator>,
191
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
192
+ >(({ className, ...props }, ref) => (
193
+ <MenubarPrimitive.Separator
194
+ ref={ref}
195
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
196
+ {...props}
197
+ />
198
+ ))
199
+ MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
200
+
201
+ const MenubarShortcut = ({
202
+ className,
203
+ ...props
204
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
205
+ return (
206
+ <span
207
+ className={cn(
208
+ "ml-auto text-xs tracking-widest text-muted-foreground",
209
+ className
210
+ )}
211
+ {...props}
212
+ />
213
+ )
214
+ }
215
+ MenubarShortcut.displayname = "MenubarShortcut"
216
+
217
+ export {
218
+ Menubar,
219
+ MenubarMenu,
220
+ MenubarTrigger,
221
+ MenubarContent,
222
+ MenubarItem,
223
+ MenubarSeparator,
224
+ MenubarLabel,
225
+ MenubarCheckboxItem,
226
+ MenubarRadioGroup,
227
+ MenubarRadioItem,
228
+ MenubarPortal,
229
+ MenubarSubContent,
230
+ MenubarSubTrigger,
231
+ MenubarGroup,
232
+ MenubarSub,
233
+ MenubarShortcut,
234
+ }