sameerbanchhor commited on
Commit
ba76bdd
·
verified ·
1 Parent(s): 6a4514a

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +45 -0
  2. .idx/dev.nix +43 -0
  3. .modified +0 -0
  4. .vscode/settings.json +4 -0
  5. Dockerfile +33 -0
  6. README.md +4 -9
  7. apphosting.yaml +7 -0
  8. components.json +21 -0
  9. docs/blueprint.md +18 -0
  10. next.config.ts +23 -0
  11. package-lock.json +0 -0
  12. package.json +66 -0
  13. postcss.config.mjs +8 -0
  14. src/ai/dev.ts +4 -0
  15. src/ai/flows/chatbot_system_instructions.txt +0 -0
  16. src/ai/flows/generate-chatbot-response.ts +83 -0
  17. src/ai/genkit.ts +7 -0
  18. src/app/favicon.ico +0 -0
  19. src/app/globals.css +65 -0
  20. src/app/layout.tsx +34 -0
  21. src/app/page.tsx +9 -0
  22. src/components/chat.tsx +147 -0
  23. src/components/message-bubble.tsx +59 -0
  24. src/components/message-form.tsx +80 -0
  25. src/components/message-list.tsx +52 -0
  26. src/components/theme-provider.tsx +9 -0
  27. src/components/theme-toggle.tsx +40 -0
  28. src/components/ui/accordion.tsx +58 -0
  29. src/components/ui/alert-dialog.tsx +141 -0
  30. src/components/ui/alert.tsx +59 -0
  31. src/components/ui/avatar.tsx +50 -0
  32. src/components/ui/badge.tsx +36 -0
  33. src/components/ui/button.tsx +56 -0
  34. src/components/ui/calendar.tsx +70 -0
  35. src/components/ui/card.tsx +79 -0
  36. src/components/ui/chart.tsx +365 -0
  37. src/components/ui/checkbox.tsx +30 -0
  38. src/components/ui/dialog.tsx +122 -0
  39. src/components/ui/dropdown-menu.tsx +200 -0
  40. src/components/ui/form.tsx +178 -0
  41. src/components/ui/input.tsx +22 -0
  42. src/components/ui/label.tsx +26 -0
  43. src/components/ui/menubar.tsx +256 -0
  44. src/components/ui/popover.tsx +31 -0
  45. src/components/ui/progress.tsx +28 -0
  46. src/components/ui/radio-group.tsx +44 -0
  47. src/components/ui/scroll-area.tsx +48 -0
  48. src/components/ui/select.tsx +160 -0
  49. src/components/ui/separator.tsx +31 -0
  50. src/components/ui/sheet.tsx +140 -0
.gitignore ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # vercel
34
+ .vercel
35
+
36
+ # typescript
37
+ *.tsbuildinfo
38
+ next-env.d.ts
39
+
40
+ .genkit/*
41
+ .env*
42
+
43
+ # firebase
44
+ firebase-debug.log
45
+ firestore-debug.log
.idx/dev.nix ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # To learn more about how to use Nix to configure your environment
2
+ # see: https://firebase.google.com/docs/studio/customize-workspace
3
+ {pkgs}: {
4
+ # Which nixpkgs channel to use.
5
+ channel = "stable-24.11"; # or "unstable"
6
+ # Use https://search.nixos.org/packages to find packages
7
+ packages = [
8
+ pkgs.nodejs_20
9
+ pkgs.zulu
10
+ ];
11
+ # Sets environment variables in the workspace
12
+ env = {};
13
+ # This adds a file watcher to startup the firebase emulators. The emulators will only start if
14
+ # a firebase.json file is written into the user's directory
15
+ services.firebase.emulators = {
16
+ detect = true;
17
+ projectId = "demo-app";
18
+ services = ["auth" "firestore"];
19
+ };
20
+ idx = {
21
+ # Search for the extensions you want on https://open-vsx.org/ and use "publisher.id"
22
+ extensions = [
23
+ # "vscodevim.vim"
24
+ ];
25
+ workspace = {
26
+ onCreate = {
27
+ default.openFiles = [
28
+ "src/app/page.tsx"
29
+ ];
30
+ };
31
+ };
32
+ # Enable previews and customize configuration
33
+ previews = {
34
+ enable = true;
35
+ previews = {
36
+ web = {
37
+ command = ["npm" "run" "dev" "--" "--port" "$PORT" "--hostname" "0.0.0.0"];
38
+ manager = "web";
39
+ };
40
+ };
41
+ };
42
+ };
43
+ }
.modified ADDED
File without changes
.vscode/settings.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "IDX.aI.enableInlineCompletion": true,
3
+ "IDX.aI.enableCodebaseIndexing": true
4
+ }
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # for deploying a Next.js application in a Docker container
2
+
3
+ # Use Node 22 as the base image
4
+ FROM node:22-alpine
5
+
6
+ # Set the working directory inside the container
7
+ WORKDIR /app
8
+
9
+ # Copy package.json and package-lock.json
10
+ COPY package*.json ./
11
+
12
+ # Install dependencies
13
+ RUN npm install
14
+
15
+ # Copy the rest of the application code
16
+ COPY . .
17
+
18
+ # Expose the port your app runs on
19
+ EXPOSE 7860
20
+
21
+ # --- FIX IS HERE ---
22
+ # Build the application. This runs during the `docker build` command.
23
+ RUN npm run build
24
+
25
+ # Change ownership of /app to the node user
26
+ # Do this *after* all root operations (like npm install/build) are done
27
+ RUN chown -R node:node /app
28
+
29
+ # Switch to a non-root user for better security
30
+ USER node
31
+
32
+ # Command to run the application when the container starts
33
+ CMD ["npm", "run", "start"]
README.md CHANGED
@@ -1,10 +1,5 @@
1
- ---
2
- title: ChattisVani
3
- emoji: 🚀
4
- colorFrom: indigo
5
- colorTo: indigo
6
- sdk: docker
7
- pinned: false
8
- ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
+ # Firebase Studio
 
 
 
 
 
 
 
2
 
3
+ This is a NextJS starter in Firebase Studio.
4
+
5
+ To get started, take a look at src/app/page.tsx.
apphosting.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Settings to manage and configure a Firebase App Hosting backend.
2
+ # https://firebase.google.com/docs/app-hosting/configure
3
+
4
+ runConfig:
5
+ # Increase this value if you'd like to automatically spin up
6
+ # more instances in response to increased traffic.
7
+ maxInstances: 1
components.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
docs/blueprint.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # **App Name**: Animated Chatty
2
+
3
+ ## Core Features:
4
+
5
+ - Chat Interface: Display a clean and engaging chat interface.
6
+ - Animated Messages: Smooth transitions and animations for message display.
7
+ - User Profiles: Personalized profiles for users, which may or may not be stored or used by the app
8
+ - AI Chatbot: Generate helpful, relevant chatbot responses using an LLM.
9
+
10
+ ## Style Guidelines:
11
+
12
+ - Primary color: Soft lavender (#D0B8FF) to convey a calming, friendly vibe.
13
+ - Background color: Light gray (#F0F0F5) for a clean and modern backdrop.
14
+ - Accent color: Light blue (#A0D2EB) to highlight interactive elements.
15
+ - Body and headline font: 'PT Sans', sans-serif, for a modern yet warm feel that’s readable at any size
16
+ - Simple, outlined icons for navigation and actions, with a consistent stroke weight.
17
+ - Clean and modern layout with clear visual hierarchy to guide the user.
18
+ - Subtle animations for message bubbles and interactive elements to provide a polished user experience.
next.config.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type {NextConfig} from 'next';
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ typescript: {
6
+ ignoreBuildErrors: true,
7
+ },
8
+ eslint: {
9
+ ignoreDuringBuilds: true,
10
+ },
11
+ images: {
12
+ remotePatterns: [
13
+ {
14
+ protocol: 'https',
15
+ hostname: 'placehold.co',
16
+ port: '',
17
+ pathname: '/**',
18
+ },
19
+ ],
20
+ },
21
+ };
22
+
23
+ export default nextConfig;
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "nextn",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev --turbopack -p 7860",
7
+ "genkit:dev": "genkit start -- tsx src/ai/dev.ts",
8
+ "genkit:watch": "genkit start -- tsx --watch src/ai/dev.ts",
9
+ "build": "next build",
10
+ "start": "next start -p 7860",
11
+ "lint": "next lint",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@genkit-ai/googleai": "^1.8.0",
16
+ "@genkit-ai/next": "^1.8.0",
17
+ "@hookform/resolvers": "^4.1.3",
18
+ "@radix-ui/react-accordion": "^1.2.3",
19
+ "@radix-ui/react-alert-dialog": "^1.1.6",
20
+ "@radix-ui/react-avatar": "^1.1.3",
21
+ "@radix-ui/react-checkbox": "^1.1.4",
22
+ "@radix-ui/react-dialog": "^1.1.6",
23
+ "@radix-ui/react-dropdown-menu": "^2.1.6",
24
+ "@radix-ui/react-label": "^2.1.2",
25
+ "@radix-ui/react-menubar": "^1.1.6",
26
+ "@radix-ui/react-popover": "^1.1.6",
27
+ "@radix-ui/react-progress": "^1.1.2",
28
+ "@radix-ui/react-radio-group": "^1.2.3",
29
+ "@radix-ui/react-scroll-area": "^1.2.3",
30
+ "@radix-ui/react-select": "^2.1.6",
31
+ "@radix-ui/react-separator": "^1.1.2",
32
+ "@radix-ui/react-slider": "^1.2.3",
33
+ "@radix-ui/react-slot": "^1.1.2",
34
+ "@radix-ui/react-switch": "^1.1.3",
35
+ "@radix-ui/react-tabs": "^1.1.3",
36
+ "@radix-ui/react-toast": "^1.2.6",
37
+ "@radix-ui/react-tooltip": "^1.1.8",
38
+ "class-variance-authority": "^0.7.1",
39
+ "clsx": "^2.1.1",
40
+ "date-fns": "^3.6.0",
41
+ "dotenv": "^16.5.0",
42
+ "firebase": "^11.9.1",
43
+ "genkit": "^1.8.0",
44
+ "lucide-react": "^0.475.0",
45
+ "next": "15.3.3",
46
+ "next-themes": "^0.3.0",
47
+ "patch-package": "^8.0.0",
48
+ "react": "^18.3.1",
49
+ "react-day-picker": "^8.10.1",
50
+ "react-dom": "^18.3.1",
51
+ "react-hook-form": "^7.54.2",
52
+ "recharts": "^2.15.1",
53
+ "tailwind-merge": "^3.0.1",
54
+ "tailwindcss-animate": "^1.0.7",
55
+ "zod": "^3.24.2"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^20",
59
+ "@types/react": "^18",
60
+ "@types/react-dom": "^18",
61
+ "genkit-cli": "^1.8.0",
62
+ "postcss": "^8",
63
+ "tailwindcss": "^3.4.1",
64
+ "typescript": "^5"
65
+ }
66
+ }
postcss.config.mjs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
src/ai/dev.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import { config } from 'dotenv';
2
+ config();
3
+
4
+ import '@/ai/flows/generate-chatbot-response.ts';
src/ai/flows/chatbot_system_instructions.txt ADDED
The diff for this file is too large to render. See raw diff
 
src/ai/flows/generate-chatbot-response.ts ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use server';
2
+
3
+ /**
4
+ * @fileOverview A Genkit flow for generating chatbot responses.
5
+ *
6
+ * - generateChatbotResponse - A function that generates chatbot responses.
7
+ * - GenerateChatbotResponseInput - The input type for the generateChatbotResponse function.
8
+ * - GenerateChatbotResponseOutput - The return type for the generateChatbotResponse function.
9
+ */
10
+
11
+ import {ai} from '@/ai/genkit';
12
+ import {z} from 'genkit';
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+
16
+ const systemInstructionsPath = path.join(
17
+ process.cwd(),
18
+ 'src',
19
+ 'ai',
20
+ 'flows',
21
+ 'chatbot_system_instructions.txt'
22
+ );
23
+ const systemInstructions = fs.readFileSync(systemInstructionsPath, 'utf-8');
24
+
25
+ const GenerateChatbotResponseInputSchema = z.object({
26
+ message: z.string().describe('The user message to respond to.'),
27
+ chatHistory: z
28
+ .array(z.object({role: z.enum(['user', 'assistant']), content: z.string()}))
29
+ .optional()
30
+ .describe('The chat history to maintain context.'),
31
+ photoDataUri: z
32
+ .string()
33
+ .optional()
34
+ .describe(
35
+ "A photo, as a data URI that must include a MIME type and use Base64 encoding. Expected format: 'data:<mimetype>;base64,<encoded_data>'."
36
+ ),
37
+ });
38
+ export type GenerateChatbotResponseInput = z.infer<
39
+ typeof GenerateChatbotResponseInputSchema
40
+ >;
41
+
42
+ const GenerateChatbotResponseOutputSchema = z.object({
43
+ response: z.string().describe('The chatbot response.'),
44
+ });
45
+ export type GenerateChatbotResponseOutput = z.infer<
46
+ typeof GenerateChatbotResponseOutputSchema
47
+ >;
48
+
49
+ export async function generateChatbotResponse(
50
+ input: GenerateChatbotResponseInput
51
+ ): Promise<GenerateChatbotResponseOutput> {
52
+ return generateChatbotResponseFlow(input);
53
+ }
54
+
55
+ const prompt = ai.definePrompt({
56
+ name: 'generateChatbotResponsePrompt',
57
+ system: systemInstructions,
58
+ input: {schema: GenerateChatbotResponseInputSchema},
59
+ output: {schema: GenerateChatbotResponseOutputSchema},
60
+ prompt: `Here's the chat history (if any):
61
+ {{#each chatHistory}}
62
+ {{role}}: {{{content}}}
63
+ {{/each}}
64
+
65
+ {{#if photoDataUri}}
66
+ [User has uploaded an image, analyze it and answer based on that and the user message.]
67
+ Photo: {{media url=photoDataUri}}
68
+ {{/if}}
69
+ User message: {{{message}}}
70
+ `,
71
+ });
72
+
73
+ const generateChatbotResponseFlow = ai.defineFlow(
74
+ {
75
+ name: 'generateChatbotResponseFlow',
76
+ inputSchema: GenerateChatbotResponseInputSchema,
77
+ outputSchema: GenerateChatbotResponseOutputSchema,
78
+ },
79
+ async (input) => {
80
+ const {output} = await prompt(input);
81
+ return output!;
82
+ }
83
+ );
src/ai/genkit.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import {genkit} from 'genkit';
2
+ import {googleAI} from '@genkit-ai/googleai';
3
+
4
+ export const ai = genkit({
5
+ plugins: [googleAI()],
6
+ model: 'googleai/gemini-2.0-flash',
7
+ });
src/app/favicon.ico ADDED
src/app/globals.css ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 240 10% 3.9%;
9
+ --card: 0 0% 100%;
10
+ --card-foreground: 240 10% 3.9%;
11
+ --popover: 0 0% 100%;
12
+ --popover-foreground: 240 10% 3.9%;
13
+ --primary: 34.9 83.3% 47.1%;
14
+ --primary-foreground: 0 0% 98%;
15
+ --secondary: 240 4.8% 95.9%;
16
+ --secondary-foreground: 240 5.9% 10%;
17
+ --muted: 240 4.8% 95.9%;
18
+ --muted-foreground: 240 3.8% 46.1%;
19
+ --accent: 240 4.8% 95.9%;
20
+ --accent-foreground: 240 5.9% 10%;
21
+ --destructive: 0 84.2% 60.2%;
22
+ --destructive-foreground: 0 0% 98%;
23
+ --border: 240 5.9% 90%;
24
+ --input: 240 5.9% 90%;
25
+ --ring: 240 10% 3.9%;
26
+ --radius: 0.5rem;
27
+ }
28
+
29
+ .dark {
30
+ --background: 240 10% 3.9%;
31
+ --foreground: 0 0% 98%;
32
+ --card: 240 10% 3.9%;
33
+ --card-foreground: 0 0% 98%;
34
+ --popover: 240 10% 3.9%;
35
+ --popover-foreground: 0 0% 98%;
36
+ --primary: 34.9 83.3% 47.1%;
37
+ --primary-foreground: 0 0% 98%;
38
+ --secondary: 240 4.8% 9.5%;
39
+ --secondary-foreground: 0 0% 98%;
40
+ --muted: 240 3.7% 15.9%;
41
+ --muted-foreground: 240 5% 64.9%;
42
+ --accent: 240 3.7% 15.9%;
43
+ --accent-foreground: 0 0% 98%;
44
+ --destructive: 0 62.8% 30.6%;
45
+ --destructive-foreground: 0 0% 98%;
46
+ --border: 240 3.7% 15.9%;
47
+ --input: 240 3.7% 15.9%;
48
+ --ring: 240 4.9% 83.9%;
49
+ }
50
+ }
51
+
52
+ @layer base {
53
+ * {
54
+ @apply border-border;
55
+ }
56
+ body {
57
+ @apply bg-background text-foreground;
58
+ }
59
+ }
60
+
61
+ @layer utilities {
62
+ .container {
63
+ @apply max-w-4xl mx-auto px-4;
64
+ }
65
+ }
src/app/layout.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type {Metadata} from 'next';
2
+ import { Inter } from 'next/font/google';
3
+ import './globals.css';
4
+ import { Toaster } from "@/components/ui/toaster";
5
+ import { ThemeProvider } from '@/components/theme-provider';
6
+
7
+ const inter = Inter({ subsets: ['latin'] });
8
+
9
+ export const metadata: Metadata = {
10
+ title: 'ChhattisVani',
11
+ description: 'A friendly AI chatbot that speaks Chhattisgarhi.',
12
+ };
13
+
14
+ export default function RootLayout({
15
+ children,
16
+ }: Readonly<{
17
+ children: React.ReactNode;
18
+ }>) {
19
+ return (
20
+ <html lang="en" suppressHydrationWarning>
21
+ <body className={inter.className}>
22
+ <ThemeProvider
23
+ attribute="class"
24
+ defaultTheme="system"
25
+ enableSystem
26
+ disableTransitionOnChange
27
+ >
28
+ {children}
29
+ <Toaster />
30
+ </ThemeProvider>
31
+ </body>
32
+ </html>
33
+ );
34
+ }
src/app/page.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { Chat } from '@/components/chat';
2
+
3
+ export default function Home() {
4
+ return (
5
+ <main>
6
+ <Chat />
7
+ </main>
8
+ );
9
+ }
src/components/chat.tsx ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState } from 'react';
4
+ import Image from 'next/image';
5
+ import { useToast } from "@/hooks/use-toast";
6
+ import { generateChatbotResponse } from '@/ai/flows/generate-chatbot-response';
7
+ import type { Message } from '@/lib/types';
8
+ import MessageList from './message-list';
9
+ import MessageForm from './message-form';
10
+ import { ThemeToggle } from './theme-toggle';
11
+ import { Bot, X } from 'lucide-react';
12
+ import { Button } from './ui/button';
13
+
14
+ export function Chat() {
15
+ const [messages, setMessages] = useState<Message[]>([
16
+ {
17
+ id: '1',
18
+ role: 'assistant',
19
+ content: "जय जोहार! मैं चत्तीसवाणी हावंव। आप मन के का मदद कर सकत हंव?",
20
+ },
21
+ ]);
22
+ const [input, setInput] = useState('');
23
+ const [file, setFile] = useState<File | null>(null);
24
+ const [isLoading, setIsLoading] = useState(false);
25
+ const { toast } = useToast();
26
+
27
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
28
+ if (e.target.files?.[0]) {
29
+ setFile(e.target.files[0]);
30
+ }
31
+ };
32
+
33
+ const handleRemoveFile = () => {
34
+ setFile(null);
35
+ }
36
+
37
+ const fileToDataUri = (file: File): Promise<string> =>
38
+ new Promise((resolve, reject) => {
39
+ const reader = new FileReader();
40
+ reader.onload = () => resolve(reader.result as string);
41
+ reader.onerror = reject;
42
+ reader.readAsDataURL(file);
43
+ });
44
+
45
+ const handleSendMessage = async (e: React.FormEvent<HTMLFormElement>) => {
46
+ e.preventDefault();
47
+ if ((!input.trim() && !file) || isLoading) return;
48
+
49
+ setIsLoading(true);
50
+
51
+ let fileDataUri: string | undefined = undefined;
52
+ let previewUrl: string | undefined = undefined;
53
+
54
+ if (file) {
55
+ previewUrl = URL.createObjectURL(file);
56
+ fileDataUri = await fileToDataUri(file);
57
+ }
58
+
59
+ const userMessage: Message = {
60
+ id: Date.now().toString(),
61
+ role: 'user',
62
+ content: input,
63
+ imageUrl: previewUrl,
64
+ };
65
+
66
+ setMessages((prev) => [...prev, userMessage]);
67
+ const currentInput = input;
68
+ setInput('');
69
+ setFile(null);
70
+
71
+ try {
72
+ const chatHistory = [...messages, userMessage].map(({ role, content }) => ({ role, content }));
73
+ const response = await generateChatbotResponse({
74
+ message: currentInput,
75
+ chatHistory,
76
+ photoDataUri: fileDataUri
77
+ });
78
+
79
+ const assistantMessage: Message = {
80
+ id: (Date.now() + 1).toString(),
81
+ role: 'assistant',
82
+ content: response.response,
83
+ };
84
+
85
+ setMessages((prev) => [...prev, assistantMessage]);
86
+ } catch (error) {
87
+ console.error("Error generating chatbot response:", error);
88
+ toast({
89
+ variant: "destructive",
90
+ title: "Oh no! Something went wrong.",
91
+ description: "There was a problem with our AI. Please try again later.",
92
+ });
93
+ const errorMessage: Message = {
94
+ id: (Date.now() + 1).toString(),
95
+ role: 'assistant',
96
+ content: "Sorry, I'm having a little trouble right now. Please try again in a moment.",
97
+ };
98
+ setMessages((prev) => [...prev, errorMessage]);
99
+ } finally {
100
+ setIsLoading(false);
101
+ // Clean up the object URL
102
+ if (previewUrl) {
103
+ URL.revokeObjectURL(previewUrl);
104
+ }
105
+ }
106
+ };
107
+
108
+ return (
109
+ <div className="flex h-screen w-full flex-col">
110
+ <header className="flex h-16 shrink-0 items-center justify-between border-b bg-background px-4">
111
+ <div className="flex items-center gap-2">
112
+ <Bot className="h-6 w-6 text-primary" />
113
+ <h1 className="text-lg font-semibold">ChhattisVani</h1>
114
+ <span className="text-xs text-muted-foreground">0.2.29 beta</span>
115
+ </div>
116
+ <ThemeToggle />
117
+ </header>
118
+ <div className="flex-1 overflow-hidden">
119
+ <MessageList messages={messages} isLoading={isLoading} />
120
+ </div>
121
+ <div className="w-full max-w-3xl mx-auto p-4 shrink-0">
122
+ {file && (
123
+ <div className="mb-2 p-2 border rounded-lg bg-muted flex items-center justify-between animate-slide-in-fade-in">
124
+ <div className="flex items-center gap-3">
125
+ <Image src={URL.createObjectURL(file)} alt="File preview" width={40} height={40} className="rounded object-cover" />
126
+ <span className="text-sm font-medium truncate max-w-xs">{file.name}</span>
127
+ </div>
128
+ <Button variant="ghost" size="icon" onClick={handleRemoveFile} className="h-7 w-7 shrink-0">
129
+ <X className="h-4 w-4" />
130
+ </Button>
131
+ </div>
132
+ )}
133
+ <MessageForm
134
+ input={input}
135
+ setInput={setInput}
136
+ handleSendMessage={handleSendMessage}
137
+ isLoading={isLoading}
138
+ handleFileChange={handleFileChange}
139
+ isFileSelected={!!file}
140
+ />
141
+ <p className="text-xs text-center text-muted-foreground pt-2">
142
+ created with ❤��� sameer banchhor , 2025 all rights reserved
143
+ </p>
144
+ </div>
145
+ </div>
146
+ );
147
+ }
src/components/message-bubble.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import type { Message } from '@/lib/types';
4
+ import { cn } from '@/lib/utils';
5
+ import { Avatar, AvatarFallback } from '@/components/ui/avatar';
6
+ import { Bot, User } from 'lucide-react';
7
+ import Image from 'next/image';
8
+
9
+ interface MessageBubbleProps {
10
+ message: Message;
11
+ }
12
+
13
+ export default function MessageBubble({ message }: MessageBubbleProps) {
14
+ const isUser = message.role === 'user';
15
+ return (
16
+ <div
17
+ className={cn(
18
+ 'flex items-start gap-4 my-4',
19
+ isUser ? 'justify-end' : 'justify-start'
20
+ )}
21
+ >
22
+ {!isUser && (
23
+ <Avatar className="h-8 w-8 shrink-0">
24
+ <AvatarFallback className="bg-primary text-primary-foreground">
25
+ <Bot className="h-5 w-5" />
26
+ </AvatarFallback>
27
+ </Avatar>
28
+ )}
29
+ <div
30
+ className={cn(
31
+ 'max-w-[75%] rounded-lg p-3 text-sm shadow-md',
32
+ isUser
33
+ ? 'bg-primary text-primary-foreground'
34
+ : 'bg-muted'
35
+ )}
36
+ >
37
+ {message.imageUrl && (
38
+ <Image
39
+ src={message.imageUrl}
40
+ alt="Uploaded content"
41
+ width={300}
42
+ height={300}
43
+ className="rounded-md mb-2 object-cover"
44
+ />
45
+ )}
46
+ <p className="whitespace-pre-wrap leading-relaxed">
47
+ {message.content}
48
+ </p>
49
+ </div>
50
+ {isUser && (
51
+ <Avatar className="h-8 w-8 shrink-0">
52
+ <AvatarFallback className="bg-accent">
53
+ <User className="h-5 w-5" />
54
+ </AvatarFallback>
55
+ </Avatar>
56
+ )}
57
+ </div>
58
+ );
59
+ }
src/components/message-form.tsx ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React from 'react';
4
+ import { Input } from '@/components/ui/input';
5
+ import { Button } from '@/components/ui/button';
6
+ import { Send, Paperclip } from 'lucide-react';
7
+
8
+ interface MessageFormProps {
9
+ input: string;
10
+ setInput: (value: string) => void;
11
+ handleSendMessage: (e: React.FormEvent<HTMLFormElement>) => Promise<void>;
12
+ isLoading: boolean;
13
+ handleFileChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
14
+ isFileSelected: boolean;
15
+ }
16
+
17
+ export default function MessageForm({
18
+ input,
19
+ setInput,
20
+ handleSendMessage,
21
+ isLoading,
22
+ handleFileChange,
23
+ isFileSelected,
24
+ }: MessageFormProps) {
25
+ const fileInputRef = React.useRef<HTMLInputElement>(null);
26
+
27
+ const handleAttachClick = () => {
28
+ fileInputRef.current?.click();
29
+ }
30
+
31
+ const handleFileReset = (e: React.MouseEvent<HTMLInputElement>) => {
32
+ // Reset file input value so the same file can be selected again
33
+ (e.target as HTMLInputElement).value = '';
34
+ }
35
+
36
+ return (
37
+ <form onSubmit={handleSendMessage} className="relative flex w-full items-center">
38
+ <Button
39
+ type="button"
40
+ variant="ghost"
41
+ size="icon"
42
+ disabled={isLoading}
43
+ onClick={handleAttachClick}
44
+ className="absolute left-3 top-1/2 -translate-y-1/2 h-8 w-8 shrink-0 rounded-full"
45
+ >
46
+ <Paperclip className="h-4 w-4" />
47
+ <span className="sr-only">Attach file</span>
48
+ </Button>
49
+ <input
50
+ type="file"
51
+ ref={fileInputRef}
52
+ onChange={handleFileChange}
53
+ onClick={handleFileReset}
54
+ className="hidden"
55
+ accept="image/*"
56
+ disabled={isLoading}
57
+ />
58
+ <Input
59
+ type="text"
60
+ placeholder="Type a message..."
61
+ value={input}
62
+ onChange={(e) => setInput(e.target.value)}
63
+ disabled={isLoading}
64
+ className="flex-1 rounded-full border bg-input pr-12 pl-12 py-6 shadow-lg focus-visible:ring-0 focus-visible:ring-offset-0"
65
+ autoComplete="off"
66
+ onKeyDown={(e) => {
67
+ if (e.key === 'Enter' && !e.shiftKey) {
68
+ e.preventDefault();
69
+ const form = e.target as HTMLInputElement;
70
+ form.form?.requestSubmit();
71
+ }
72
+ }}
73
+ />
74
+ <Button type="submit" size="icon" disabled={isLoading || (!input.trim() && !isFileSelected)} className="absolute right-3 top-1/2 -translate-y-1/2 h-8 w-8 shrink-0 rounded-full">
75
+ <Send className="h-4 w-4" />
76
+ <span className="sr-only">Send</span>
77
+ </Button>
78
+ </form>
79
+ );
80
+ }
src/components/message-list.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useRef, useEffect } from 'react';
4
+ import type { Message } from '@/lib/types';
5
+ import { ScrollArea } from '@/components/ui/scroll-area';
6
+ import MessageBubble from './message-bubble';
7
+ import { Avatar, AvatarFallback } from './ui/avatar';
8
+ import { Bot } from 'lucide-react';
9
+
10
+ interface MessageListProps {
11
+ messages: Message[];
12
+ isLoading: boolean;
13
+ }
14
+
15
+ export default function MessageList({ messages, isLoading }: MessageListProps) {
16
+ const messagesEndRef = useRef<HTMLDivElement>(null);
17
+
18
+ const scrollToBottom = () => {
19
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
20
+ };
21
+
22
+ useEffect(() => {
23
+ scrollToBottom();
24
+ }, [messages, isLoading]);
25
+
26
+ return (
27
+ <ScrollArea className="h-full w-full">
28
+ <div className="container max-w-4xl mx-auto">
29
+ {messages.map((message) => (
30
+ <MessageBubble key={message.id} message={message} />
31
+ ))}
32
+ {isLoading && (
33
+ <div className="flex items-start gap-4 my-4 justify-start">
34
+ <Avatar className="h-8 w-8 shrink-0">
35
+ <AvatarFallback className="bg-primary text-primary-foreground">
36
+ <Bot className="h-5 w-5" />
37
+ </AvatarFallback>
38
+ </Avatar>
39
+ <div className="bg-muted rounded-lg p-3 flex items-center h-[42px] shadow-md">
40
+ <div className="flex items-center gap-1.5">
41
+ <span className="h-2 w-2 rounded-full bg-muted-foreground animate-typing-indicator" style={{ animationDelay: '0s' }} />
42
+ <span className="h-2 w-2 rounded-full bg-muted-foreground animate-typing-indicator" style={{ animationDelay: '0.2s' }} />
43
+ <span className="h-2 w-2 rounded-full bg-muted-foreground animate-typing-indicator" style={{ animationDelay: '0.4s' }} />
44
+ </div>
45
+ </div>
46
+ </div>
47
+ )}
48
+ <div ref={messagesEndRef} />
49
+ </div>
50
+ </ScrollArea>
51
+ );
52
+ }
src/components/theme-provider.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ThemeProvider as NextThemesProvider } from "next-themes"
5
+ import type { ThemeProviderProps } from "next-themes/dist/types"
6
+
7
+ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>
9
+ }
src/components/theme-toggle.tsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Moon, Sun } from "lucide-react"
5
+ import { useTheme } from "next-themes"
6
+
7
+ import { Button } from "@/components/ui/button"
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuTrigger,
13
+ } from "@/components/ui/dropdown-menu"
14
+
15
+ export function ThemeToggle() {
16
+ const { setTheme } = useTheme()
17
+
18
+ return (
19
+ <DropdownMenu>
20
+ <DropdownMenuTrigger asChild>
21
+ <Button variant="outline" size="icon">
22
+ <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
23
+ <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
24
+ <span className="sr-only">Toggle theme</span>
25
+ </Button>
26
+ </DropdownMenuTrigger>
27
+ <DropdownMenuContent align="end">
28
+ <DropdownMenuItem onClick={() => setTheme("light")}>
29
+ Light
30
+ </DropdownMenuItem>
31
+ <DropdownMenuItem onClick={() => setTheme("dark")}>
32
+ Dark
33
+ </DropdownMenuItem>
34
+ <DropdownMenuItem onClick={() => setTheme("system")}>
35
+ System
36
+ </DropdownMenuItem>
37
+ </DropdownMenuContent>
38
+ </DropdownMenu>
39
+ )
40
+ }
src/components/ui/accordion.tsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
5
+ import { ChevronDown } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Accordion = AccordionPrimitive.Root
10
+
11
+ const AccordionItem = React.forwardRef<
12
+ React.ElementRef<typeof AccordionPrimitive.Item>,
13
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
14
+ >(({ className, ...props }, ref) => (
15
+ <AccordionPrimitive.Item
16
+ ref={ref}
17
+ className={cn("border-b", className)}
18
+ {...props}
19
+ />
20
+ ))
21
+ AccordionItem.displayName = "AccordionItem"
22
+
23
+ const AccordionTrigger = React.forwardRef<
24
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
25
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
26
+ >(({ className, children, ...props }, ref) => (
27
+ <AccordionPrimitive.Header className="flex">
28
+ <AccordionPrimitive.Trigger
29
+ ref={ref}
30
+ className={cn(
31
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
38
+ </AccordionPrimitive.Trigger>
39
+ </AccordionPrimitive.Header>
40
+ ))
41
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42
+
43
+ const AccordionContent = React.forwardRef<
44
+ React.ElementRef<typeof AccordionPrimitive.Content>,
45
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
46
+ >(({ className, children, ...props }, ref) => (
47
+ <AccordionPrimitive.Content
48
+ ref={ref}
49
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
50
+ {...props}
51
+ >
52
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
53
+ </AccordionPrimitive.Content>
54
+ ))
55
+
56
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
57
+
58
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
src/components/ui/alert-dialog.tsx ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import { buttonVariants } from "@/components/ui/button"
8
+
9
+ const AlertDialog = AlertDialogPrimitive.Root
10
+
11
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
12
+
13
+ const AlertDialogPortal = AlertDialogPrimitive.Portal
14
+
15
+ const AlertDialogOverlay = React.forwardRef<
16
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
17
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
18
+ >(({ className, ...props }, ref) => (
19
+ <AlertDialogPrimitive.Overlay
20
+ className={cn(
21
+ "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",
22
+ className
23
+ )}
24
+ {...props}
25
+ ref={ref}
26
+ />
27
+ ))
28
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
29
+
30
+ const AlertDialogContent = React.forwardRef<
31
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
32
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
33
+ >(({ className, ...props }, ref) => (
34
+ <AlertDialogPortal>
35
+ <AlertDialogOverlay />
36
+ <AlertDialogPrimitive.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
+ </AlertDialogPortal>
45
+ ))
46
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
47
+
48
+ const AlertDialogHeader = ({
49
+ className,
50
+ ...props
51
+ }: React.HTMLAttributes<HTMLDivElement>) => (
52
+ <div
53
+ className={cn(
54
+ "flex flex-col space-y-2 text-center sm:text-left",
55
+ className
56
+ )}
57
+ {...props}
58
+ />
59
+ )
60
+ AlertDialogHeader.displayName = "AlertDialogHeader"
61
+
62
+ const AlertDialogFooter = ({
63
+ className,
64
+ ...props
65
+ }: React.HTMLAttributes<HTMLDivElement>) => (
66
+ <div
67
+ className={cn(
68
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ )
74
+ AlertDialogFooter.displayName = "AlertDialogFooter"
75
+
76
+ const AlertDialogTitle = React.forwardRef<
77
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
78
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
79
+ >(({ className, ...props }, ref) => (
80
+ <AlertDialogPrimitive.Title
81
+ ref={ref}
82
+ className={cn("text-lg font-semibold", className)}
83
+ {...props}
84
+ />
85
+ ))
86
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
87
+
88
+ const AlertDialogDescription = React.forwardRef<
89
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
90
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
91
+ >(({ className, ...props }, ref) => (
92
+ <AlertDialogPrimitive.Description
93
+ ref={ref}
94
+ className={cn("text-sm text-muted-foreground", className)}
95
+ {...props}
96
+ />
97
+ ))
98
+ AlertDialogDescription.displayName =
99
+ AlertDialogPrimitive.Description.displayName
100
+
101
+ const AlertDialogAction = React.forwardRef<
102
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
103
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
104
+ >(({ className, ...props }, ref) => (
105
+ <AlertDialogPrimitive.Action
106
+ ref={ref}
107
+ className={cn(buttonVariants(), className)}
108
+ {...props}
109
+ />
110
+ ))
111
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
112
+
113
+ const AlertDialogCancel = React.forwardRef<
114
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
115
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
116
+ >(({ className, ...props }, ref) => (
117
+ <AlertDialogPrimitive.Cancel
118
+ ref={ref}
119
+ className={cn(
120
+ buttonVariants({ variant: "outline" }),
121
+ "mt-2 sm:mt-0",
122
+ className
123
+ )}
124
+ {...props}
125
+ />
126
+ ))
127
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
128
+
129
+ export {
130
+ AlertDialog,
131
+ AlertDialogPortal,
132
+ AlertDialogOverlay,
133
+ AlertDialogTrigger,
134
+ AlertDialogContent,
135
+ AlertDialogHeader,
136
+ AlertDialogFooter,
137
+ AlertDialogTitle,
138
+ AlertDialogDescription,
139
+ AlertDialogAction,
140
+ AlertDialogCancel,
141
+ }
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/avatar.tsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Avatar = React.forwardRef<
9
+ React.ElementRef<typeof AvatarPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <AvatarPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ ))
21
+ Avatar.displayName = AvatarPrimitive.Root.displayName
22
+
23
+ const AvatarImage = React.forwardRef<
24
+ React.ElementRef<typeof AvatarPrimitive.Image>,
25
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
26
+ >(({ className, ...props }, ref) => (
27
+ <AvatarPrimitive.Image
28
+ ref={ref}
29
+ className={cn("aspect-square h-full w-full", className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
34
+
35
+ const AvatarFallback = React.forwardRef<
36
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
37
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
38
+ >(({ className, ...props }, ref) => (
39
+ <AvatarPrimitive.Fallback
40
+ ref={ref}
41
+ className={cn(
42
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ ))
48
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49
+
50
+ 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/button.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-10 px-4 py-2",
24
+ sm: "h-9 rounded-md px-3",
25
+ lg: "h-11 rounded-md px-8",
26
+ icon: "h-10 w-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button"
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+ )
54
+ Button.displayName = "Button"
55
+
56
+ export { Button, buttonVariants }
src/components/ui/calendar.tsx ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ChevronLeft, ChevronRight } from "lucide-react"
5
+ import { DayPicker } from "react-day-picker"
6
+
7
+ import { cn } from "@/lib/utils"
8
+ import { buttonVariants } from "@/components/ui/button"
9
+
10
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>
11
+
12
+ function Calendar({
13
+ className,
14
+ classNames,
15
+ showOutsideDays = true,
16
+ ...props
17
+ }: CalendarProps) {
18
+ return (
19
+ <DayPicker
20
+ showOutsideDays={showOutsideDays}
21
+ className={cn("p-3", className)}
22
+ classNames={{
23
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
24
+ month: "space-y-4",
25
+ caption: "flex justify-center pt-1 relative items-center",
26
+ caption_label: "text-sm font-medium",
27
+ nav: "space-x-1 flex items-center",
28
+ nav_button: cn(
29
+ buttonVariants({ variant: "outline" }),
30
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
31
+ ),
32
+ nav_button_previous: "absolute left-1",
33
+ nav_button_next: "absolute right-1",
34
+ table: "w-full border-collapse space-y-1",
35
+ head_row: "flex",
36
+ head_cell:
37
+ "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
38
+ row: "flex w-full mt-2",
39
+ 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",
40
+ day: cn(
41
+ buttonVariants({ variant: "ghost" }),
42
+ "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
43
+ ),
44
+ day_range_end: "day-range-end",
45
+ day_selected:
46
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
47
+ day_today: "bg-accent text-accent-foreground",
48
+ day_outside:
49
+ "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
50
+ day_disabled: "text-muted-foreground opacity-50",
51
+ day_range_middle:
52
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
53
+ day_hidden: "invisible",
54
+ ...classNames,
55
+ }}
56
+ components={{
57
+ IconLeft: ({ className, ...props }) => (
58
+ <ChevronLeft className={cn("h-4 w-4", className)} {...props} />
59
+ ),
60
+ IconRight: ({ className, ...props }) => (
61
+ <ChevronRight className={cn("h-4 w-4", className)} {...props} />
62
+ ),
63
+ }}
64
+ {...props}
65
+ />
66
+ )
67
+ }
68
+ Calendar.displayName = "Calendar"
69
+
70
+ 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
+ HTMLDivElement,
34
+ React.HTMLAttributes<HTMLDivElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <div
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
+ HTMLDivElement,
49
+ React.HTMLAttributes<HTMLDivElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
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/chart.tsx ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RechartsPrimitive from "recharts"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ // Format: { THEME_NAME: CSS_SELECTOR }
9
+ const THEMES = { light: "", dark: ".dark" } as const
10
+
11
+ export type ChartConfig = {
12
+ [k in string]: {
13
+ label?: React.ReactNode
14
+ icon?: React.ComponentType
15
+ } & (
16
+ | { color?: string; theme?: never }
17
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
18
+ )
19
+ }
20
+
21
+ type ChartContextProps = {
22
+ config: ChartConfig
23
+ }
24
+
25
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
26
+
27
+ function useChart() {
28
+ const context = React.useContext(ChartContext)
29
+
30
+ if (!context) {
31
+ throw new Error("useChart must be used within a <ChartContainer />")
32
+ }
33
+
34
+ return context
35
+ }
36
+
37
+ const ChartContainer = React.forwardRef<
38
+ HTMLDivElement,
39
+ React.ComponentProps<"div"> & {
40
+ config: ChartConfig
41
+ children: React.ComponentProps<
42
+ typeof RechartsPrimitive.ResponsiveContainer
43
+ >["children"]
44
+ }
45
+ >(({ id, className, children, config, ...props }, ref) => {
46
+ const uniqueId = React.useId()
47
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
48
+
49
+ return (
50
+ <ChartContext.Provider value={{ config }}>
51
+ <div
52
+ data-chart={chartId}
53
+ ref={ref}
54
+ className={cn(
55
+ "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",
56
+ className
57
+ )}
58
+ {...props}
59
+ >
60
+ <ChartStyle id={chartId} config={config} />
61
+ <RechartsPrimitive.ResponsiveContainer>
62
+ {children}
63
+ </RechartsPrimitive.ResponsiveContainer>
64
+ </div>
65
+ </ChartContext.Provider>
66
+ )
67
+ })
68
+ ChartContainer.displayName = "Chart"
69
+
70
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
71
+ const colorConfig = Object.entries(config).filter(
72
+ ([, config]) => config.theme || config.color
73
+ )
74
+
75
+ if (!colorConfig.length) {
76
+ return null
77
+ }
78
+
79
+ return (
80
+ <style
81
+ dangerouslySetInnerHTML={{
82
+ __html: Object.entries(THEMES)
83
+ .map(
84
+ ([theme, prefix]) => `
85
+ ${prefix} [data-chart=${id}] {
86
+ ${colorConfig
87
+ .map(([key, itemConfig]) => {
88
+ const color =
89
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
90
+ itemConfig.color
91
+ return color ? ` --color-${key}: ${color};` : null
92
+ })
93
+ .join("\n")}
94
+ }
95
+ `
96
+ )
97
+ .join("\n"),
98
+ }}
99
+ />
100
+ )
101
+ }
102
+
103
+ const ChartTooltip = RechartsPrimitive.Tooltip
104
+
105
+ const ChartTooltipContent = React.forwardRef<
106
+ HTMLDivElement,
107
+ React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
108
+ React.ComponentProps<"div"> & {
109
+ hideLabel?: boolean
110
+ hideIndicator?: boolean
111
+ indicator?: "line" | "dot" | "dashed"
112
+ nameKey?: string
113
+ labelKey?: string
114
+ }
115
+ >(
116
+ (
117
+ {
118
+ active,
119
+ payload,
120
+ className,
121
+ indicator = "dot",
122
+ hideLabel = false,
123
+ hideIndicator = false,
124
+ label,
125
+ labelFormatter,
126
+ labelClassName,
127
+ formatter,
128
+ color,
129
+ nameKey,
130
+ labelKey,
131
+ },
132
+ ref
133
+ ) => {
134
+ const { config } = useChart()
135
+
136
+ const tooltipLabel = React.useMemo(() => {
137
+ if (hideLabel || !payload?.length) {
138
+ return null
139
+ }
140
+
141
+ const [item] = payload
142
+ const key = `${labelKey || item.dataKey || item.name || "value"}`
143
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
144
+ const value =
145
+ !labelKey && typeof label === "string"
146
+ ? config[label as keyof typeof config]?.label || label
147
+ : itemConfig?.label
148
+
149
+ if (labelFormatter) {
150
+ return (
151
+ <div className={cn("font-medium", labelClassName)}>
152
+ {labelFormatter(value, payload)}
153
+ </div>
154
+ )
155
+ }
156
+
157
+ if (!value) {
158
+ return null
159
+ }
160
+
161
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
162
+ }, [
163
+ label,
164
+ labelFormatter,
165
+ payload,
166
+ hideLabel,
167
+ labelClassName,
168
+ config,
169
+ labelKey,
170
+ ])
171
+
172
+ if (!active || !payload?.length) {
173
+ return null
174
+ }
175
+
176
+ const nestLabel = payload.length === 1 && indicator !== "dot"
177
+
178
+ return (
179
+ <div
180
+ ref={ref}
181
+ className={cn(
182
+ "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",
183
+ className
184
+ )}
185
+ >
186
+ {!nestLabel ? tooltipLabel : null}
187
+ <div className="grid gap-1.5">
188
+ {payload.map((item, index) => {
189
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
190
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
191
+ const indicatorColor = color || item.payload.fill || item.color
192
+
193
+ return (
194
+ <div
195
+ key={item.dataKey}
196
+ className={cn(
197
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
198
+ indicator === "dot" && "items-center"
199
+ )}
200
+ >
201
+ {formatter && item?.value !== undefined && item.name ? (
202
+ formatter(item.value, item.name, item, index, item.payload)
203
+ ) : (
204
+ <>
205
+ {itemConfig?.icon ? (
206
+ <itemConfig.icon />
207
+ ) : (
208
+ !hideIndicator && (
209
+ <div
210
+ className={cn(
211
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
212
+ {
213
+ "h-2.5 w-2.5": indicator === "dot",
214
+ "w-1": indicator === "line",
215
+ "w-0 border-[1.5px] border-dashed bg-transparent":
216
+ indicator === "dashed",
217
+ "my-0.5": nestLabel && indicator === "dashed",
218
+ }
219
+ )}
220
+ style={
221
+ {
222
+ "--color-bg": indicatorColor,
223
+ "--color-border": indicatorColor,
224
+ } as React.CSSProperties
225
+ }
226
+ />
227
+ )
228
+ )}
229
+ <div
230
+ className={cn(
231
+ "flex flex-1 justify-between leading-none",
232
+ nestLabel ? "items-end" : "items-center"
233
+ )}
234
+ >
235
+ <div className="grid gap-1.5">
236
+ {nestLabel ? tooltipLabel : null}
237
+ <span className="text-muted-foreground">
238
+ {itemConfig?.label || item.name}
239
+ </span>
240
+ </div>
241
+ {item.value && (
242
+ <span className="font-mono font-medium tabular-nums text-foreground">
243
+ {item.value.toLocaleString()}
244
+ </span>
245
+ )}
246
+ </div>
247
+ </>
248
+ )}
249
+ </div>
250
+ )
251
+ })}
252
+ </div>
253
+ </div>
254
+ )
255
+ }
256
+ )
257
+ ChartTooltipContent.displayName = "ChartTooltip"
258
+
259
+ const ChartLegend = RechartsPrimitive.Legend
260
+
261
+ const ChartLegendContent = React.forwardRef<
262
+ HTMLDivElement,
263
+ React.ComponentProps<"div"> &
264
+ Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
265
+ hideIcon?: boolean
266
+ nameKey?: string
267
+ }
268
+ >(
269
+ (
270
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
271
+ ref
272
+ ) => {
273
+ const { config } = useChart()
274
+
275
+ if (!payload?.length) {
276
+ return null
277
+ }
278
+
279
+ return (
280
+ <div
281
+ ref={ref}
282
+ className={cn(
283
+ "flex items-center justify-center gap-4",
284
+ verticalAlign === "top" ? "pb-3" : "pt-3",
285
+ className
286
+ )}
287
+ >
288
+ {payload.map((item) => {
289
+ const key = `${nameKey || item.dataKey || "value"}`
290
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
291
+
292
+ return (
293
+ <div
294
+ key={item.value}
295
+ className={cn(
296
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
297
+ )}
298
+ >
299
+ {itemConfig?.icon && !hideIcon ? (
300
+ <itemConfig.icon />
301
+ ) : (
302
+ <div
303
+ className="h-2 w-2 shrink-0 rounded-[2px]"
304
+ style={{
305
+ backgroundColor: item.color,
306
+ }}
307
+ />
308
+ )}
309
+ {itemConfig?.label}
310
+ </div>
311
+ )
312
+ })}
313
+ </div>
314
+ )
315
+ }
316
+ )
317
+ ChartLegendContent.displayName = "ChartLegend"
318
+
319
+ // Helper to extract item config from a payload.
320
+ function getPayloadConfigFromPayload(
321
+ config: ChartConfig,
322
+ payload: unknown,
323
+ key: string
324
+ ) {
325
+ if (typeof payload !== "object" || payload === null) {
326
+ return undefined
327
+ }
328
+
329
+ const payloadPayload =
330
+ "payload" in payload &&
331
+ typeof payload.payload === "object" &&
332
+ payload.payload !== null
333
+ ? payload.payload
334
+ : undefined
335
+
336
+ let configLabelKey: string = key
337
+
338
+ if (
339
+ key in payload &&
340
+ typeof payload[key as keyof typeof payload] === "string"
341
+ ) {
342
+ configLabelKey = payload[key as keyof typeof payload] as string
343
+ } else if (
344
+ payloadPayload &&
345
+ key in payloadPayload &&
346
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
347
+ ) {
348
+ configLabelKey = payloadPayload[
349
+ key as keyof typeof payloadPayload
350
+ ] as string
351
+ }
352
+
353
+ return configLabelKey in config
354
+ ? config[configLabelKey]
355
+ : config[key as keyof typeof config]
356
+ }
357
+
358
+ export {
359
+ ChartContainer,
360
+ ChartTooltip,
361
+ ChartTooltipContent,
362
+ ChartLegend,
363
+ ChartLegendContent,
364
+ ChartStyle,
365
+ }
src/components/ui/checkbox.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5
+ import { Check } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Checkbox = React.forwardRef<
10
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
12
+ >(({ className, ...props }, ref) => (
13
+ <CheckboxPrimitive.Root
14
+ ref={ref}
15
+ className={cn(
16
+ "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",
17
+ className
18
+ )}
19
+ {...props}
20
+ >
21
+ <CheckboxPrimitive.Indicator
22
+ className={cn("flex items-center justify-center text-current")}
23
+ >
24
+ <Check className="h-4 w-4" />
25
+ </CheckboxPrimitive.Indicator>
26
+ </CheckboxPrimitive.Root>
27
+ ))
28
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
29
+
30
+ export { Checkbox }
src/components/ui/dialog.tsx ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
5
+ import { X } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Dialog = DialogPrimitive.Root
10
+
11
+ const DialogTrigger = DialogPrimitive.Trigger
12
+
13
+ const DialogPortal = DialogPrimitive.Portal
14
+
15
+ const DialogClose = DialogPrimitive.Close
16
+
17
+ const DialogOverlay = React.forwardRef<
18
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
19
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
20
+ >(({ className, ...props }, ref) => (
21
+ <DialogPrimitive.Overlay
22
+ ref={ref}
23
+ className={cn(
24
+ "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",
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ ))
30
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31
+
32
+ const DialogContent = React.forwardRef<
33
+ React.ElementRef<typeof DialogPrimitive.Content>,
34
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
35
+ >(({ className, children, ...props }, ref) => (
36
+ <DialogPortal>
37
+ <DialogOverlay />
38
+ <DialogPrimitive.Content
39
+ ref={ref}
40
+ className={cn(
41
+ "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",
42
+ className
43
+ )}
44
+ {...props}
45
+ >
46
+ {children}
47
+ <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">
48
+ <X className="h-4 w-4" />
49
+ <span className="sr-only">Close</span>
50
+ </DialogPrimitive.Close>
51
+ </DialogPrimitive.Content>
52
+ </DialogPortal>
53
+ ))
54
+ DialogContent.displayName = DialogPrimitive.Content.displayName
55
+
56
+ const DialogHeader = ({
57
+ className,
58
+ ...props
59
+ }: React.HTMLAttributes<HTMLDivElement>) => (
60
+ <div
61
+ className={cn(
62
+ "flex flex-col space-y-1.5 text-center sm:text-left",
63
+ className
64
+ )}
65
+ {...props}
66
+ />
67
+ )
68
+ DialogHeader.displayName = "DialogHeader"
69
+
70
+ const DialogFooter = ({
71
+ className,
72
+ ...props
73
+ }: React.HTMLAttributes<HTMLDivElement>) => (
74
+ <div
75
+ className={cn(
76
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
77
+ className
78
+ )}
79
+ {...props}
80
+ />
81
+ )
82
+ DialogFooter.displayName = "DialogFooter"
83
+
84
+ const DialogTitle = React.forwardRef<
85
+ React.ElementRef<typeof DialogPrimitive.Title>,
86
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
87
+ >(({ className, ...props }, ref) => (
88
+ <DialogPrimitive.Title
89
+ ref={ref}
90
+ className={cn(
91
+ "text-lg font-semibold leading-none tracking-tight",
92
+ className
93
+ )}
94
+ {...props}
95
+ />
96
+ ))
97
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
98
+
99
+ const DialogDescription = React.forwardRef<
100
+ React.ElementRef<typeof DialogPrimitive.Description>,
101
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
102
+ >(({ className, ...props }, ref) => (
103
+ <DialogPrimitive.Description
104
+ ref={ref}
105
+ className={cn("text-sm text-muted-foreground", className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
110
+
111
+ export {
112
+ Dialog,
113
+ DialogPortal,
114
+ DialogOverlay,
115
+ DialogClose,
116
+ DialogTrigger,
117
+ DialogContent,
118
+ DialogHeader,
119
+ DialogFooter,
120
+ DialogTitle,
121
+ DialogDescription,
122
+ }
src/components/ui/dropdown-menu.tsx ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5
+ import { Check, ChevronRight, Circle } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const DropdownMenu = DropdownMenuPrimitive.Root
10
+
11
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12
+
13
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
14
+
15
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16
+
17
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
18
+
19
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20
+
21
+ const DropdownMenuSubTrigger = React.forwardRef<
22
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
23
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
24
+ inset?: boolean
25
+ }
26
+ >(({ className, inset, children, ...props }, ref) => (
27
+ <DropdownMenuPrimitive.SubTrigger
28
+ ref={ref}
29
+ className={cn(
30
+ "flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
31
+ inset && "pl-8",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronRight className="ml-auto" />
38
+ </DropdownMenuPrimitive.SubTrigger>
39
+ ))
40
+ DropdownMenuSubTrigger.displayName =
41
+ DropdownMenuPrimitive.SubTrigger.displayName
42
+
43
+ const DropdownMenuSubContent = React.forwardRef<
44
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
45
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
46
+ >(({ className, ...props }, ref) => (
47
+ <DropdownMenuPrimitive.SubContent
48
+ ref={ref}
49
+ className={cn(
50
+ "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",
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ ))
56
+ DropdownMenuSubContent.displayName =
57
+ DropdownMenuPrimitive.SubContent.displayName
58
+
59
+ const DropdownMenuContent = React.forwardRef<
60
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
61
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
62
+ >(({ className, sideOffset = 4, ...props }, ref) => (
63
+ <DropdownMenuPrimitive.Portal>
64
+ <DropdownMenuPrimitive.Content
65
+ ref={ref}
66
+ sideOffset={sideOffset}
67
+ className={cn(
68
+ "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",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ </DropdownMenuPrimitive.Portal>
74
+ ))
75
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76
+
77
+ const DropdownMenuItem = React.forwardRef<
78
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
79
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
80
+ inset?: boolean
81
+ }
82
+ >(({ className, inset, ...props }, ref) => (
83
+ <DropdownMenuPrimitive.Item
84
+ ref={ref}
85
+ className={cn(
86
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
87
+ inset && "pl-8",
88
+ className
89
+ )}
90
+ {...props}
91
+ />
92
+ ))
93
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94
+
95
+ const DropdownMenuCheckboxItem = React.forwardRef<
96
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
97
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
98
+ >(({ className, children, checked, ...props }, ref) => (
99
+ <DropdownMenuPrimitive.CheckboxItem
100
+ ref={ref}
101
+ className={cn(
102
+ "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",
103
+ className
104
+ )}
105
+ checked={checked}
106
+ {...props}
107
+ >
108
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
109
+ <DropdownMenuPrimitive.ItemIndicator>
110
+ <Check className="h-4 w-4" />
111
+ </DropdownMenuPrimitive.ItemIndicator>
112
+ </span>
113
+ {children}
114
+ </DropdownMenuPrimitive.CheckboxItem>
115
+ ))
116
+ DropdownMenuCheckboxItem.displayName =
117
+ DropdownMenuPrimitive.CheckboxItem.displayName
118
+
119
+ const DropdownMenuRadioItem = React.forwardRef<
120
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
121
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
122
+ >(({ className, children, ...props }, ref) => (
123
+ <DropdownMenuPrimitive.RadioItem
124
+ ref={ref}
125
+ className={cn(
126
+ "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",
127
+ className
128
+ )}
129
+ {...props}
130
+ >
131
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
132
+ <DropdownMenuPrimitive.ItemIndicator>
133
+ <Circle className="h-2 w-2 fill-current" />
134
+ </DropdownMenuPrimitive.ItemIndicator>
135
+ </span>
136
+ {children}
137
+ </DropdownMenuPrimitive.RadioItem>
138
+ ))
139
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140
+
141
+ const DropdownMenuLabel = React.forwardRef<
142
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
143
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
144
+ inset?: boolean
145
+ }
146
+ >(({ className, inset, ...props }, ref) => (
147
+ <DropdownMenuPrimitive.Label
148
+ ref={ref}
149
+ className={cn(
150
+ "px-2 py-1.5 text-sm font-semibold",
151
+ inset && "pl-8",
152
+ className
153
+ )}
154
+ {...props}
155
+ />
156
+ ))
157
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158
+
159
+ const DropdownMenuSeparator = React.forwardRef<
160
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
161
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
162
+ >(({ className, ...props }, ref) => (
163
+ <DropdownMenuPrimitive.Separator
164
+ ref={ref}
165
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
166
+ {...props}
167
+ />
168
+ ))
169
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170
+
171
+ const DropdownMenuShortcut = ({
172
+ className,
173
+ ...props
174
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
175
+ return (
176
+ <span
177
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
178
+ {...props}
179
+ />
180
+ )
181
+ }
182
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183
+
184
+ export {
185
+ DropdownMenu,
186
+ DropdownMenuTrigger,
187
+ DropdownMenuContent,
188
+ DropdownMenuItem,
189
+ DropdownMenuCheckboxItem,
190
+ DropdownMenuRadioItem,
191
+ DropdownMenuLabel,
192
+ DropdownMenuSeparator,
193
+ DropdownMenuShortcut,
194
+ DropdownMenuGroup,
195
+ DropdownMenuPortal,
196
+ DropdownMenuSub,
197
+ DropdownMenuSubContent,
198
+ DropdownMenuSubTrigger,
199
+ DropdownMenuRadioGroup,
200
+ }
src/components/ui/form.tsx ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { Slot } from "@radix-ui/react-slot"
6
+ import {
7
+ Controller,
8
+ FormProvider,
9
+ useFormContext,
10
+ type ControllerProps,
11
+ type FieldPath,
12
+ type FieldValues,
13
+ } from "react-hook-form"
14
+
15
+ import { cn } from "@/lib/utils"
16
+ import { Label } from "@/components/ui/label"
17
+
18
+ const Form = FormProvider
19
+
20
+ type FormFieldContextValue<
21
+ TFieldValues extends FieldValues = FieldValues,
22
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
23
+ > = {
24
+ name: TName
25
+ }
26
+
27
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
28
+ {} as FormFieldContextValue
29
+ )
30
+
31
+ const FormField = <
32
+ TFieldValues extends FieldValues = FieldValues,
33
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
34
+ >({
35
+ ...props
36
+ }: ControllerProps<TFieldValues, TName>) => {
37
+ return (
38
+ <FormFieldContext.Provider value={{ name: props.name }}>
39
+ <Controller {...props} />
40
+ </FormFieldContext.Provider>
41
+ )
42
+ }
43
+
44
+ const useFormField = () => {
45
+ const fieldContext = React.useContext(FormFieldContext)
46
+ const itemContext = React.useContext(FormItemContext)
47
+ const { getFieldState, formState } = useFormContext()
48
+
49
+ const fieldState = getFieldState(fieldContext.name, formState)
50
+
51
+ if (!fieldContext) {
52
+ throw new Error("useFormField should be used within <FormField>")
53
+ }
54
+
55
+ const { id } = itemContext
56
+
57
+ return {
58
+ id,
59
+ name: fieldContext.name,
60
+ formItemId: `${id}-form-item`,
61
+ formDescriptionId: `${id}-form-item-description`,
62
+ formMessageId: `${id}-form-item-message`,
63
+ ...fieldState,
64
+ }
65
+ }
66
+
67
+ type FormItemContextValue = {
68
+ id: string
69
+ }
70
+
71
+ const FormItemContext = React.createContext<FormItemContextValue>(
72
+ {} as FormItemContextValue
73
+ )
74
+
75
+ const FormItem = React.forwardRef<
76
+ HTMLDivElement,
77
+ React.HTMLAttributes<HTMLDivElement>
78
+ >(({ className, ...props }, ref) => {
79
+ const id = React.useId()
80
+
81
+ return (
82
+ <FormItemContext.Provider value={{ id }}>
83
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
84
+ </FormItemContext.Provider>
85
+ )
86
+ })
87
+ FormItem.displayName = "FormItem"
88
+
89
+ const FormLabel = React.forwardRef<
90
+ React.ElementRef<typeof LabelPrimitive.Root>,
91
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
92
+ >(({ className, ...props }, ref) => {
93
+ const { error, formItemId } = useFormField()
94
+
95
+ return (
96
+ <Label
97
+ ref={ref}
98
+ className={cn(error && "text-destructive", className)}
99
+ htmlFor={formItemId}
100
+ {...props}
101
+ />
102
+ )
103
+ })
104
+ FormLabel.displayName = "FormLabel"
105
+
106
+ const FormControl = React.forwardRef<
107
+ React.ElementRef<typeof Slot>,
108
+ React.ComponentPropsWithoutRef<typeof Slot>
109
+ >(({ ...props }, ref) => {
110
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
111
+
112
+ return (
113
+ <Slot
114
+ ref={ref}
115
+ id={formItemId}
116
+ aria-describedby={
117
+ !error
118
+ ? `${formDescriptionId}`
119
+ : `${formDescriptionId} ${formMessageId}`
120
+ }
121
+ aria-invalid={!!error}
122
+ {...props}
123
+ />
124
+ )
125
+ })
126
+ FormControl.displayName = "FormControl"
127
+
128
+ const FormDescription = React.forwardRef<
129
+ HTMLParagraphElement,
130
+ React.HTMLAttributes<HTMLParagraphElement>
131
+ >(({ className, ...props }, ref) => {
132
+ const { formDescriptionId } = useFormField()
133
+
134
+ return (
135
+ <p
136
+ ref={ref}
137
+ id={formDescriptionId}
138
+ className={cn("text-sm text-muted-foreground", className)}
139
+ {...props}
140
+ />
141
+ )
142
+ })
143
+ FormDescription.displayName = "FormDescription"
144
+
145
+ const FormMessage = React.forwardRef<
146
+ HTMLParagraphElement,
147
+ React.HTMLAttributes<HTMLParagraphElement>
148
+ >(({ className, children, ...props }, ref) => {
149
+ const { error, formMessageId } = useFormField()
150
+ const body = error ? String(error?.message ?? "") : children
151
+
152
+ if (!body) {
153
+ return null
154
+ }
155
+
156
+ return (
157
+ <p
158
+ ref={ref}
159
+ id={formMessageId}
160
+ className={cn("text-sm font-medium text-destructive", className)}
161
+ {...props}
162
+ >
163
+ {body}
164
+ </p>
165
+ )
166
+ })
167
+ FormMessage.displayName = "FormMessage"
168
+
169
+ export {
170
+ useFormField,
171
+ Form,
172
+ FormItem,
173
+ FormLabel,
174
+ FormControl,
175
+ FormDescription,
176
+ FormMessage,
177
+ FormField,
178
+ }
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,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const labelVariants = cva(
10
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11
+ )
12
+
13
+ const Label = React.forwardRef<
14
+ React.ElementRef<typeof LabelPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16
+ VariantProps<typeof labelVariants>
17
+ >(({ className, ...props }, ref) => (
18
+ <LabelPrimitive.Root
19
+ ref={ref}
20
+ className={cn(labelVariants(), className)}
21
+ {...props}
22
+ />
23
+ ))
24
+ Label.displayName = LabelPrimitive.Root.displayName
25
+
26
+ export { Label }
src/components/ui/menubar.tsx ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as MenubarPrimitive from "@radix-ui/react-menubar"
5
+ import { Check, ChevronRight, Circle } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function MenubarMenu({
10
+ ...props
11
+ }: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
12
+ return <MenubarPrimitive.Menu {...props} />
13
+ }
14
+
15
+ function MenubarGroup({
16
+ ...props
17
+ }: React.ComponentProps<typeof MenubarPrimitive.Group>) {
18
+ return <MenubarPrimitive.Group {...props} />
19
+ }
20
+
21
+ function MenubarPortal({
22
+ ...props
23
+ }: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
24
+ return <MenubarPrimitive.Portal {...props} />
25
+ }
26
+
27
+ function MenubarRadioGroup({
28
+ ...props
29
+ }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
30
+ return <MenubarPrimitive.RadioGroup {...props} />
31
+ }
32
+
33
+ function MenubarSub({
34
+ ...props
35
+ }: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
36
+ return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
37
+ }
38
+
39
+ const Menubar = React.forwardRef<
40
+ React.ElementRef<typeof MenubarPrimitive.Root>,
41
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
42
+ >(({ className, ...props }, ref) => (
43
+ <MenubarPrimitive.Root
44
+ ref={ref}
45
+ className={cn(
46
+ "flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ ))
52
+ Menubar.displayName = MenubarPrimitive.Root.displayName
53
+
54
+ const MenubarTrigger = React.forwardRef<
55
+ React.ElementRef<typeof MenubarPrimitive.Trigger>,
56
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
57
+ >(({ className, ...props }, ref) => (
58
+ <MenubarPrimitive.Trigger
59
+ ref={ref}
60
+ className={cn(
61
+ "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",
62
+ className
63
+ )}
64
+ {...props}
65
+ />
66
+ ))
67
+ MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
68
+
69
+ const MenubarSubTrigger = React.forwardRef<
70
+ React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
71
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
72
+ inset?: boolean
73
+ }
74
+ >(({ className, inset, children, ...props }, ref) => (
75
+ <MenubarPrimitive.SubTrigger
76
+ ref={ref}
77
+ className={cn(
78
+ "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",
79
+ inset && "pl-8",
80
+ className
81
+ )}
82
+ {...props}
83
+ >
84
+ {children}
85
+ <ChevronRight className="ml-auto h-4 w-4" />
86
+ </MenubarPrimitive.SubTrigger>
87
+ ))
88
+ MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
89
+
90
+ const MenubarSubContent = React.forwardRef<
91
+ React.ElementRef<typeof MenubarPrimitive.SubContent>,
92
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
93
+ >(({ className, ...props }, ref) => (
94
+ <MenubarPrimitive.SubContent
95
+ ref={ref}
96
+ className={cn(
97
+ "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",
98
+ className
99
+ )}
100
+ {...props}
101
+ />
102
+ ))
103
+ MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
104
+
105
+ const MenubarContent = React.forwardRef<
106
+ React.ElementRef<typeof MenubarPrimitive.Content>,
107
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
108
+ >(
109
+ (
110
+ { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
111
+ ref
112
+ ) => (
113
+ <MenubarPrimitive.Portal>
114
+ <MenubarPrimitive.Content
115
+ ref={ref}
116
+ align={align}
117
+ alignOffset={alignOffset}
118
+ sideOffset={sideOffset}
119
+ className={cn(
120
+ "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",
121
+ className
122
+ )}
123
+ {...props}
124
+ />
125
+ </MenubarPrimitive.Portal>
126
+ )
127
+ )
128
+ MenubarContent.displayName = MenubarPrimitive.Content.displayName
129
+
130
+ const MenubarItem = React.forwardRef<
131
+ React.ElementRef<typeof MenubarPrimitive.Item>,
132
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
133
+ inset?: boolean
134
+ }
135
+ >(({ className, inset, ...props }, ref) => (
136
+ <MenubarPrimitive.Item
137
+ ref={ref}
138
+ className={cn(
139
+ "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",
140
+ inset && "pl-8",
141
+ className
142
+ )}
143
+ {...props}
144
+ />
145
+ ))
146
+ MenubarItem.displayName = MenubarPrimitive.Item.displayName
147
+
148
+ const MenubarCheckboxItem = React.forwardRef<
149
+ React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
150
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
151
+ >(({ className, children, checked, ...props }, ref) => (
152
+ <MenubarPrimitive.CheckboxItem
153
+ ref={ref}
154
+ className={cn(
155
+ "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",
156
+ className
157
+ )}
158
+ checked={checked}
159
+ {...props}
160
+ >
161
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
162
+ <MenubarPrimitive.ItemIndicator>
163
+ <Check className="h-4 w-4" />
164
+ </MenubarPrimitive.ItemIndicator>
165
+ </span>
166
+ {children}
167
+ </MenubarPrimitive.CheckboxItem>
168
+ ))
169
+ MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
170
+
171
+ const MenubarRadioItem = React.forwardRef<
172
+ React.ElementRef<typeof MenubarPrimitive.RadioItem>,
173
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
174
+ >(({ className, children, ...props }, ref) => (
175
+ <MenubarPrimitive.RadioItem
176
+ ref={ref}
177
+ className={cn(
178
+ "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",
179
+ className
180
+ )}
181
+ {...props}
182
+ >
183
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
184
+ <MenubarPrimitive.ItemIndicator>
185
+ <Circle className="h-2 w-2 fill-current" />
186
+ </MenubarPrimitive.ItemIndicator>
187
+ </span>
188
+ {children}
189
+ </MenubarPrimitive.RadioItem>
190
+ ))
191
+ MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
192
+
193
+ const MenubarLabel = React.forwardRef<
194
+ React.ElementRef<typeof MenubarPrimitive.Label>,
195
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
196
+ inset?: boolean
197
+ }
198
+ >(({ className, inset, ...props }, ref) => (
199
+ <MenubarPrimitive.Label
200
+ ref={ref}
201
+ className={cn(
202
+ "px-2 py-1.5 text-sm font-semibold",
203
+ inset && "pl-8",
204
+ className
205
+ )}
206
+ {...props}
207
+ />
208
+ ))
209
+ MenubarLabel.displayName = MenubarPrimitive.Label.displayName
210
+
211
+ const MenubarSeparator = React.forwardRef<
212
+ React.ElementRef<typeof MenubarPrimitive.Separator>,
213
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
214
+ >(({ className, ...props }, ref) => (
215
+ <MenubarPrimitive.Separator
216
+ ref={ref}
217
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
218
+ {...props}
219
+ />
220
+ ))
221
+ MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
222
+
223
+ const MenubarShortcut = ({
224
+ className,
225
+ ...props
226
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
227
+ return (
228
+ <span
229
+ className={cn(
230
+ "ml-auto text-xs tracking-widest text-muted-foreground",
231
+ className
232
+ )}
233
+ {...props}
234
+ />
235
+ )
236
+ }
237
+ MenubarShortcut.displayname = "MenubarShortcut"
238
+
239
+ export {
240
+ Menubar,
241
+ MenubarMenu,
242
+ MenubarTrigger,
243
+ MenubarContent,
244
+ MenubarItem,
245
+ MenubarSeparator,
246
+ MenubarLabel,
247
+ MenubarCheckboxItem,
248
+ MenubarRadioGroup,
249
+ MenubarRadioItem,
250
+ MenubarPortal,
251
+ MenubarSubContent,
252
+ MenubarSubTrigger,
253
+ MenubarGroup,
254
+ MenubarSub,
255
+ MenubarShortcut,
256
+ }
src/components/ui/popover.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as PopoverPrimitive from "@radix-ui/react-popover"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Popover = PopoverPrimitive.Root
9
+
10
+ const PopoverTrigger = PopoverPrimitive.Trigger
11
+
12
+ const PopoverContent = React.forwardRef<
13
+ React.ElementRef<typeof PopoverPrimitive.Content>,
14
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
15
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16
+ <PopoverPrimitive.Portal>
17
+ <PopoverPrimitive.Content
18
+ ref={ref}
19
+ align={align}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ </PopoverPrimitive.Portal>
28
+ ))
29
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName
30
+
31
+ export { Popover, PopoverTrigger, PopoverContent }
src/components/ui/progress.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as ProgressPrimitive from "@radix-ui/react-progress"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Progress = React.forwardRef<
9
+ React.ElementRef<typeof ProgressPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
11
+ >(({ className, value, ...props }, ref) => (
12
+ <ProgressPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative h-4 w-full overflow-hidden rounded-full bg-secondary",
16
+ className
17
+ )}
18
+ {...props}
19
+ >
20
+ <ProgressPrimitive.Indicator
21
+ className="h-full w-full flex-1 bg-primary transition-all"
22
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
23
+ />
24
+ </ProgressPrimitive.Root>
25
+ ))
26
+ Progress.displayName = ProgressPrimitive.Root.displayName
27
+
28
+ export { Progress }
src/components/ui/radio-group.tsx ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5
+ import { Circle } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const RadioGroup = React.forwardRef<
10
+ React.ElementRef<typeof RadioGroupPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
12
+ >(({ className, ...props }, ref) => {
13
+ return (
14
+ <RadioGroupPrimitive.Root
15
+ className={cn("grid gap-2", className)}
16
+ {...props}
17
+ ref={ref}
18
+ />
19
+ )
20
+ })
21
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
22
+
23
+ const RadioGroupItem = React.forwardRef<
24
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
25
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
26
+ >(({ className, ...props }, ref) => {
27
+ return (
28
+ <RadioGroupPrimitive.Item
29
+ ref={ref}
30
+ className={cn(
31
+ "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
37
+ <Circle className="h-2.5 w-2.5 fill-current text-current" />
38
+ </RadioGroupPrimitive.Indicator>
39
+ </RadioGroupPrimitive.Item>
40
+ )
41
+ })
42
+ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
43
+
44
+ export { RadioGroup, RadioGroupItem }
src/components/ui/scroll-area.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const ScrollArea = React.forwardRef<
9
+ React.ElementRef<typeof ScrollAreaPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
11
+ >(({ className, children, ...props }, ref) => (
12
+ <ScrollAreaPrimitive.Root
13
+ ref={ref}
14
+ className={cn("relative overflow-hidden", className)}
15
+ {...props}
16
+ >
17
+ <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
18
+ {children}
19
+ </ScrollAreaPrimitive.Viewport>
20
+ <ScrollBar />
21
+ <ScrollAreaPrimitive.Corner />
22
+ </ScrollAreaPrimitive.Root>
23
+ ))
24
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25
+
26
+ const ScrollBar = React.forwardRef<
27
+ React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
28
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
29
+ >(({ className, orientation = "vertical", ...props }, ref) => (
30
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
31
+ ref={ref}
32
+ orientation={orientation}
33
+ className={cn(
34
+ "flex touch-none select-none transition-colors",
35
+ orientation === "vertical" &&
36
+ "h-full w-2.5 border-l border-l-transparent p-[1px]",
37
+ orientation === "horizontal" &&
38
+ "h-2.5 flex-col border-t border-t-transparent p-[1px]",
39
+ className
40
+ )}
41
+ {...props}
42
+ >
43
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
44
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
45
+ ))
46
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47
+
48
+ export { ScrollArea, ScrollBar }
src/components/ui/select.tsx ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Select = SelectPrimitive.Root
10
+
11
+ const SelectGroup = SelectPrimitive.Group
12
+
13
+ const SelectValue = SelectPrimitive.Value
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <ChevronDown className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ))
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34
+
35
+ const SelectScrollUpButton = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
38
+ >(({ className, ...props }, ref) => (
39
+ <SelectPrimitive.ScrollUpButton
40
+ ref={ref}
41
+ className={cn(
42
+ "flex cursor-default items-center justify-center py-1",
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ <ChevronUp className="h-4 w-4" />
48
+ </SelectPrimitive.ScrollUpButton>
49
+ ))
50
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51
+
52
+ const SelectScrollDownButton = React.forwardRef<
53
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
54
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
55
+ >(({ className, ...props }, ref) => (
56
+ <SelectPrimitive.ScrollDownButton
57
+ ref={ref}
58
+ className={cn(
59
+ "flex cursor-default items-center justify-center py-1",
60
+ className
61
+ )}
62
+ {...props}
63
+ >
64
+ <ChevronDown className="h-4 w-4" />
65
+ </SelectPrimitive.ScrollDownButton>
66
+ ))
67
+ SelectScrollDownButton.displayName =
68
+ SelectPrimitive.ScrollDownButton.displayName
69
+
70
+ const SelectContent = React.forwardRef<
71
+ React.ElementRef<typeof SelectPrimitive.Content>,
72
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
73
+ >(({ className, children, position = "popper", ...props }, ref) => (
74
+ <SelectPrimitive.Portal>
75
+ <SelectPrimitive.Content
76
+ ref={ref}
77
+ className={cn(
78
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
79
+ position === "popper" &&
80
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
81
+ className
82
+ )}
83
+ position={position}
84
+ {...props}
85
+ >
86
+ <SelectScrollUpButton />
87
+ <SelectPrimitive.Viewport
88
+ className={cn(
89
+ "p-1",
90
+ position === "popper" &&
91
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
92
+ )}
93
+ >
94
+ {children}
95
+ </SelectPrimitive.Viewport>
96
+ <SelectScrollDownButton />
97
+ </SelectPrimitive.Content>
98
+ </SelectPrimitive.Portal>
99
+ ))
100
+ SelectContent.displayName = SelectPrimitive.Content.displayName
101
+
102
+ const SelectLabel = React.forwardRef<
103
+ React.ElementRef<typeof SelectPrimitive.Label>,
104
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
105
+ >(({ className, ...props }, ref) => (
106
+ <SelectPrimitive.Label
107
+ ref={ref}
108
+ className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
109
+ {...props}
110
+ />
111
+ ))
112
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
113
+
114
+ const SelectItem = React.forwardRef<
115
+ React.ElementRef<typeof SelectPrimitive.Item>,
116
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
117
+ >(({ className, children, ...props }, ref) => (
118
+ <SelectPrimitive.Item
119
+ ref={ref}
120
+ className={cn(
121
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
+ className
123
+ )}
124
+ {...props}
125
+ >
126
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
127
+ <SelectPrimitive.ItemIndicator>
128
+ <Check className="h-4 w-4" />
129
+ </SelectPrimitive.ItemIndicator>
130
+ </span>
131
+
132
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
133
+ </SelectPrimitive.Item>
134
+ ))
135
+ SelectItem.displayName = SelectPrimitive.Item.displayName
136
+
137
+ const SelectSeparator = React.forwardRef<
138
+ React.ElementRef<typeof SelectPrimitive.Separator>,
139
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
140
+ >(({ className, ...props }, ref) => (
141
+ <SelectPrimitive.Separator
142
+ ref={ref}
143
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
144
+ {...props}
145
+ />
146
+ ))
147
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
148
+
149
+ export {
150
+ Select,
151
+ SelectGroup,
152
+ SelectValue,
153
+ SelectTrigger,
154
+ SelectContent,
155
+ SelectLabel,
156
+ SelectItem,
157
+ SelectSeparator,
158
+ SelectScrollUpButton,
159
+ SelectScrollDownButton,
160
+ }
src/components/ui/separator.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Separator = React.forwardRef<
9
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
11
+ >(
12
+ (
13
+ { className, orientation = "horizontal", decorative = true, ...props },
14
+ ref
15
+ ) => (
16
+ <SeparatorPrimitive.Root
17
+ ref={ref}
18
+ decorative={decorative}
19
+ orientation={orientation}
20
+ className={cn(
21
+ "shrink-0 bg-border",
22
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ )
28
+ )
29
+ Separator.displayName = SeparatorPrimitive.Root.displayName
30
+
31
+ export { Separator }
src/components/ui/sheet.tsx ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SheetPrimitive from "@radix-ui/react-dialog"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { X } from "lucide-react"
7
+
8
+ import { cn } from "@/lib/utils"
9
+
10
+ const Sheet = SheetPrimitive.Root
11
+
12
+ const SheetTrigger = SheetPrimitive.Trigger
13
+
14
+ const SheetClose = SheetPrimitive.Close
15
+
16
+ const SheetPortal = SheetPrimitive.Portal
17
+
18
+ const SheetOverlay = React.forwardRef<
19
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
20
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
21
+ >(({ className, ...props }, ref) => (
22
+ <SheetPrimitive.Overlay
23
+ className={cn(
24
+ "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",
25
+ className
26
+ )}
27
+ {...props}
28
+ ref={ref}
29
+ />
30
+ ))
31
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
32
+
33
+ const sheetVariants = cva(
34
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
35
+ {
36
+ variants: {
37
+ side: {
38
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
39
+ bottom:
40
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
41
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
42
+ right:
43
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
44
+ },
45
+ },
46
+ defaultVariants: {
47
+ side: "right",
48
+ },
49
+ }
50
+ )
51
+
52
+ interface SheetContentProps
53
+ extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
54
+ VariantProps<typeof sheetVariants> {}
55
+
56
+ const SheetContent = React.forwardRef<
57
+ React.ElementRef<typeof SheetPrimitive.Content>,
58
+ SheetContentProps
59
+ >(({ side = "right", className, children, ...props }, ref) => (
60
+ <SheetPortal>
61
+ <SheetOverlay />
62
+ <SheetPrimitive.Content
63
+ ref={ref}
64
+ className={cn(sheetVariants({ side }), className)}
65
+ {...props}
66
+ >
67
+ {children}
68
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
69
+ <X className="h-4 w-4" />
70
+ <span className="sr-only">Close</span>
71
+ </SheetPrimitive.Close>
72
+ </SheetPrimitive.Content>
73
+ </SheetPortal>
74
+ ))
75
+ SheetContent.displayName = SheetPrimitive.Content.displayName
76
+
77
+ const SheetHeader = ({
78
+ className,
79
+ ...props
80
+ }: React.HTMLAttributes<HTMLDivElement>) => (
81
+ <div
82
+ className={cn(
83
+ "flex flex-col space-y-2 text-center sm:text-left",
84
+ className
85
+ )}
86
+ {...props}
87
+ />
88
+ )
89
+ SheetHeader.displayName = "SheetHeader"
90
+
91
+ const SheetFooter = ({
92
+ className,
93
+ ...props
94
+ }: React.HTMLAttributes<HTMLDivElement>) => (
95
+ <div
96
+ className={cn(
97
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
98
+ className
99
+ )}
100
+ {...props}
101
+ />
102
+ )
103
+ SheetFooter.displayName = "SheetFooter"
104
+
105
+ const SheetTitle = React.forwardRef<
106
+ React.ElementRef<typeof SheetPrimitive.Title>,
107
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
108
+ >(({ className, ...props }, ref) => (
109
+ <SheetPrimitive.Title
110
+ ref={ref}
111
+ className={cn("text-lg font-semibold text-foreground", className)}
112
+ {...props}
113
+ />
114
+ ))
115
+ SheetTitle.displayName = SheetPrimitive.Title.displayName
116
+
117
+ const SheetDescription = React.forwardRef<
118
+ React.ElementRef<typeof SheetPrimitive.Description>,
119
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
120
+ >(({ className, ...props }, ref) => (
121
+ <SheetPrimitive.Description
122
+ ref={ref}
123
+ className={cn("text-sm text-muted-foreground", className)}
124
+ {...props}
125
+ />
126
+ ))
127
+ SheetDescription.displayName = SheetPrimitive.Description.displayName
128
+
129
+ export {
130
+ Sheet,
131
+ SheetPortal,
132
+ SheetOverlay,
133
+ SheetTrigger,
134
+ SheetClose,
135
+ SheetContent,
136
+ SheetHeader,
137
+ SheetFooter,
138
+ SheetTitle,
139
+ SheetDescription,
140
+ }