viktor-hu commited on
Commit
f232e48
·
1 Parent(s): 206db4e

refactor. Add lint and format tools

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.template +22 -0
  2. .eslintrc.json +3 -0
  3. README.md +2 -1
  4. components.json +1 -1
  5. package.json +11 -3
  6. pnpm-lock.yaml +0 -0
  7. src/app/api/auth/check-bypass/route.ts +1 -1
  8. src/app/api/auth/login/route.ts +7 -6
  9. src/app/api/generate-code/route.ts +35 -31
  10. src/app/api/improve-prompt/route.ts +12 -12
  11. src/app/api/login/route.ts +2 -2
  12. src/app/api/share-link/route.ts +13 -10
  13. src/app/layout.tsx +21 -15
  14. src/app/providers.tsx +12 -16
  15. src/components/app-container.tsx +108 -95
  16. src/components/auth-error-popup.tsx +29 -18
  17. src/components/code-editor.tsx +33 -22
  18. src/components/color-panel.tsx +67 -48
  19. src/components/header.tsx +22 -14
  20. src/components/logo.tsx +1 -1
  21. src/components/model-selector.tsx +33 -25
  22. src/components/preview.tsx +97 -56
  23. src/components/prompt-input.tsx +66 -48
  24. src/components/share-dialog.tsx +26 -22
  25. src/components/theme-provider.tsx +4 -4
  26. src/components/ui/accordion.tsx +14 -14
  27. src/components/ui/alert-dialog.tsx +30 -30
  28. src/components/ui/alert.tsx +12 -12
  29. src/components/ui/aspect-ratio.tsx +4 -4
  30. src/components/ui/avatar.tsx +13 -13
  31. src/components/ui/badge.tsx +7 -7
  32. src/components/ui/breadcrumb.tsx +24 -24
  33. src/components/ui/button.tsx +23 -14
  34. src/components/ui/calendar.tsx +12 -12
  35. src/components/ui/card.tsx +24 -17
  36. src/components/ui/carousel.tsx +80 -80
  37. src/components/ui/chart.tsx +85 -85
  38. src/components/ui/checkbox.tsx +9 -9
  39. src/components/ui/collapsible.tsx +6 -6
  40. src/components/ui/command.tsx +32 -32
  41. src/components/ui/context-menu.tsx +42 -42
  42. src/components/ui/dialog.tsx +27 -27
  43. src/components/ui/drawer.tsx +24 -24
  44. src/components/ui/dropdown-menu.tsx +41 -41
  45. src/components/ui/error-message.tsx +23 -7
  46. src/components/ui/form.tsx +52 -51
  47. src/components/ui/fullscreen-toggle.tsx +20 -11
  48. src/components/ui/hover-card.tsx +10 -10
  49. src/components/ui/icons.tsx +49 -9
  50. src/components/ui/input-otp.tsx +19 -19
.env.template ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # App configurations
2
+ APP_PORT=
3
+ NODE_ENV=
4
+
5
+ # For debugging
6
+ DEFAULT_HF_TOKEN=
7
+ INFERENCE_ENDPOINT_URL=
8
+
9
+ # Hugging Face OAuth2.0 configurations.
10
+ REDIRECT_URI=
11
+ OAUTH_CLIENT_ID=
12
+ OAUTH_CLIENT_SECRET=
13
+
14
+ # Anysite gallery service API auth token.
15
+ ANYSITE_GALLERY_AUTH_TOKEN=
16
+
17
+ # optional. POSTHOG Auth configurations.
18
+ NEXT_PUBLIC_POSTHOG_HOST=
19
+ NEXT_PUBLIC_POSTHOG_KEY=
20
+
21
+ # Novita AI API Key
22
+ NOVITA_API_TOKEN=
.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": "next"
3
+ }
README.md CHANGED
@@ -18,4 +18,5 @@ models:
18
  ---
19
 
20
  ## Credits
21
- This space is inspired by https://huggingface.co/spaces/enzostvs/deepsite.
 
 
18
  ---
19
 
20
  ## Credits
21
+
22
+ This space is inspired by https://huggingface.co/spaces/enzostvs/deepsite.
components.json CHANGED
@@ -18,4 +18,4 @@
18
  "hooks": "@/hooks"
19
  },
20
  "iconLibrary": "lucide"
21
- }
 
18
  "hooks": "@/hooks"
19
  },
20
  "iconLibrary": "lucide"
21
+ }
package.json CHANGED
@@ -1,12 +1,16 @@
1
  {
2
- "name": "my-v0-project",
3
  "version": "0.1.0",
4
  "private": true,
5
  "scripts": {
6
  "dev": "next dev -p ${APP_PORT:-5001}",
7
  "build": "next build",
8
  "start": "next start -p ${APP_PORT:-5001}",
9
- "lint": "next lint"
 
 
 
 
10
  },
11
  "dependencies": {
12
  "@hookform/resolvers": "^3.9.1",
@@ -48,6 +52,7 @@
48
  "date-fns": "4.1.0",
49
  "embla-carousel-react": "8.5.1",
50
  "input-otp": "1.4.1",
 
51
  "lucide-react": "^0.454.0",
52
  "nanoid": "^5.1.5",
53
  "next": "15.2.4",
@@ -66,11 +71,14 @@
66
  "zod": "^3.24.1"
67
  },
68
  "devDependencies": {
 
69
  "@types/node": "^22",
70
  "@types/react": "^19",
71
  "@types/react-dom": "^19",
 
 
72
  "postcss": "^8",
73
  "tailwindcss": "^3.4.17",
74
  "typescript": "^5"
75
  }
76
- }
 
1
  {
2
+ "name": "novita-anysite",
3
  "version": "0.1.0",
4
  "private": true,
5
  "scripts": {
6
  "dev": "next dev -p ${APP_PORT:-5001}",
7
  "build": "next build",
8
  "start": "next start -p ${APP_PORT:-5001}",
9
+ "lint": "next lint",
10
+ "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,css,scss}\"",
11
+ "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,css,scss}\"",
12
+ "format:ts": "prettier --write \"**/*.{ts,tsx}\"",
13
+ "type-check": "tsc --noEmit"
14
  },
15
  "dependencies": {
16
  "@hookform/resolvers": "^3.9.1",
 
52
  "date-fns": "4.1.0",
53
  "embla-carousel-react": "8.5.1",
54
  "input-otp": "1.4.1",
55
+ "liquid-glass-react": "^1.1.1",
56
  "lucide-react": "^0.454.0",
57
  "nanoid": "^5.1.5",
58
  "next": "15.2.4",
 
71
  "zod": "^3.24.1"
72
  },
73
  "devDependencies": {
74
+ "prettier": "^3.5.3",
75
  "@types/node": "^22",
76
  "@types/react": "^19",
77
  "@types/react-dom": "^19",
78
+ "eslint": "^9.28.0",
79
+ "eslint-config-next": "15.3.3",
80
  "postcss": "^8",
81
  "tailwindcss": "^3.4.17",
82
  "typescript": "^5"
83
  }
84
+ }
pnpm-lock.yaml CHANGED
The diff for this file is too large to render. See raw diff
 
src/app/api/auth/check-bypass/route.ts CHANGED
@@ -6,4 +6,4 @@ export async function GET(request: NextRequest): Promise<NextResponse> {
6
  const fingerprint = generateFingerprintFromHeaders(request);
7
  const canBypass = evaluateLoginBypass(fingerprint, true);
8
  return new NextResponse(String(canBypass), { status: 200 });
9
- }
 
6
  const fingerprint = generateFingerprintFromHeaders(request);
7
  const canBypass = evaluateLoginBypass(fingerprint, true);
8
  return new NextResponse(String(canBypass), { status: 200 });
9
+ }
src/app/api/auth/login/route.ts CHANGED
@@ -7,7 +7,8 @@ export async function GET(request: NextRequest) {
7
  const searchParams = request.nextUrl.searchParams;
8
  const code = searchParams.get("code");
9
 
10
- const host = request.headers.get("x-forwarded-host") || request.headers.get("host");
 
11
  const protocol = request.headers.get("x-forwarded-proto") || "http";
12
  const baseUrl = `${protocol}://${host}`;
13
 
@@ -16,7 +17,7 @@ export async function GET(request: NextRequest) {
16
  }
17
 
18
  const Authorization = `Basic ${Buffer.from(
19
- `${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`
20
  ).toString("base64")}`;
21
 
22
  try {
@@ -32,7 +33,7 @@ export async function GET(request: NextRequest) {
32
  redirect_uri: REDIRECT_URI,
33
  }),
34
  });
35
-
36
  const response = await request_auth.json();
37
  if (!response.access_token) {
38
  return NextResponse.redirect(new URL("/", baseUrl));
@@ -40,8 +41,8 @@ export async function GET(request: NextRequest) {
40
 
41
  const res = NextResponse.redirect(new URL("/", baseUrl));
42
  res.cookies.set("hf_token", response.access_token, {
43
- httpOnly: false,
44
- secure: true,
45
  sameSite: "none",
46
  maxAge: 30 * 24 * 60 * 60 * 1000,
47
  path: "/",
@@ -52,4 +53,4 @@ export async function GET(request: NextRequest) {
52
  console.error("Error during login:", error);
53
  return NextResponse.redirect(new URL("/", baseUrl));
54
  }
55
- }
 
7
  const searchParams = request.nextUrl.searchParams;
8
  const code = searchParams.get("code");
9
 
10
+ const host =
11
+ request.headers.get("x-forwarded-host") || request.headers.get("host");
12
  const protocol = request.headers.get("x-forwarded-proto") || "http";
13
  const baseUrl = `${protocol}://${host}`;
14
 
 
17
  }
18
 
19
  const Authorization = `Basic ${Buffer.from(
20
+ `${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`,
21
  ).toString("base64")}`;
22
 
23
  try {
 
33
  redirect_uri: REDIRECT_URI,
34
  }),
35
  });
36
+
37
  const response = await request_auth.json();
38
  if (!response.access_token) {
39
  return NextResponse.redirect(new URL("/", baseUrl));
 
41
 
42
  const res = NextResponse.redirect(new URL("/", baseUrl));
43
  res.cookies.set("hf_token", response.access_token, {
44
+ httpOnly: false,
45
+ secure: true,
46
  sameSite: "none",
47
  maxAge: 30 * 24 * 60 * 60 * 1000,
48
  path: "/",
 
53
  console.error("Error during login:", error);
54
  return NextResponse.redirect(new URL("/", baseUrl));
55
  }
56
+ }
src/app/api/generate-code/route.ts CHANGED
@@ -1,11 +1,11 @@
1
  import { NextRequest, NextResponse } from "next/server";
2
  import { MODEL_CONFIG_CODE_GENERATION } from "@/lib/constants";
3
- import {
4
- getInferenceToken,
5
- checkTokenLimit,
6
- createInferenceClient,
7
  createStreamResponse,
8
- getInferenceOptions
9
  } from "@/lib/inference-utils";
10
 
11
  export const dynamic = "force-dynamic";
@@ -13,7 +13,8 @@ const NO_THINK_TAG = " /no_think";
13
 
14
  export async function POST(request: NextRequest) {
15
  try {
16
- const { prompt, html, previousPrompt, colors, modelId } = await request.json();
 
17
 
18
  if (!prompt) {
19
  return NextResponse.json(
@@ -21,13 +22,14 @@ export async function POST(request: NextRequest) {
21
  ok: false,
22
  message: "Missing required fields",
23
  },
24
- { status: 400 }
25
  );
26
  }
27
 
28
  // Find the model config by modelId or use the first one as default
29
- const modelConfig = modelId
30
- ? MODEL_CONFIG_CODE_GENERATION.find(config => config.id === modelId) || MODEL_CONFIG_CODE_GENERATION[0]
 
31
  : MODEL_CONFIG_CODE_GENERATION[0];
32
 
33
  // Get inference token
@@ -39,7 +41,7 @@ export async function POST(request: NextRequest) {
39
  openLogin: tokenResult.error.openLogin,
40
  message: tokenResult.error.message,
41
  },
42
- { status: tokenResult.error.status }
43
  );
44
  }
45
 
@@ -54,8 +56,10 @@ export async function POST(request: NextRequest) {
54
  return NextResponse.json(tokenLimitError, { status: 400 });
55
  }
56
 
57
- const actualNoThinkTag = modelConfig.default_enable_thinking ? NO_THINK_TAG : "";
58
-
 
 
59
  // Use streaming response
60
  return createStreamResponse(async (controller) => {
61
  const client = createInferenceClient(tokenResult);
@@ -68,27 +72,27 @@ export async function POST(request: NextRequest) {
68
  },
69
  ...(previousPrompt
70
  ? [
71
- {
72
- role: "user",
73
- content: previousPrompt,
74
- },
75
- ]
76
  : []),
77
  ...(html
78
  ? [
79
- {
80
- role: "assistant",
81
- content: `The current code is: ${html}.`,
82
- },
83
- ]
84
  : []),
85
- ...((colors && colors.length > 0)
86
  ? [
87
- {
88
- role: "user",
89
- content: `Use the following color palette in your UI design: ${colors.join(', ')}`,
90
- },
91
- ]
92
  : []),
93
  {
94
  role: "user",
@@ -97,7 +101,7 @@ export async function POST(request: NextRequest) {
97
  ];
98
 
99
  const chatCompletion = client.chatCompletionStream(
100
- getInferenceOptions(modelConfig, messages, modelConfig.max_tokens)
101
  );
102
  const encoder = new TextEncoder();
103
  for await (const chunk of chatCompletion) {
@@ -117,7 +121,7 @@ export async function POST(request: NextRequest) {
117
  ok: false,
118
  message: error instanceof Error ? error.message : "Unknown error",
119
  },
120
- { status: 500 }
121
  );
122
  }
123
- }
 
1
  import { NextRequest, NextResponse } from "next/server";
2
  import { MODEL_CONFIG_CODE_GENERATION } from "@/lib/constants";
3
+ import {
4
+ getInferenceToken,
5
+ checkTokenLimit,
6
+ createInferenceClient,
7
  createStreamResponse,
8
+ getInferenceOptions,
9
  } from "@/lib/inference-utils";
10
 
11
  export const dynamic = "force-dynamic";
 
13
 
14
  export async function POST(request: NextRequest) {
15
  try {
16
+ const { prompt, html, previousPrompt, colors, modelId } =
17
+ await request.json();
18
 
19
  if (!prompt) {
20
  return NextResponse.json(
 
22
  ok: false,
23
  message: "Missing required fields",
24
  },
25
+ { status: 400 },
26
  );
27
  }
28
 
29
  // Find the model config by modelId or use the first one as default
30
+ const modelConfig = modelId
31
+ ? MODEL_CONFIG_CODE_GENERATION.find((config) => config.id === modelId) ||
32
+ MODEL_CONFIG_CODE_GENERATION[0]
33
  : MODEL_CONFIG_CODE_GENERATION[0];
34
 
35
  // Get inference token
 
41
  openLogin: tokenResult.error.openLogin,
42
  message: tokenResult.error.message,
43
  },
44
+ { status: tokenResult.error.status },
45
  );
46
  }
47
 
 
56
  return NextResponse.json(tokenLimitError, { status: 400 });
57
  }
58
 
59
+ const actualNoThinkTag = modelConfig.default_enable_thinking
60
+ ? NO_THINK_TAG
61
+ : "";
62
+
63
  // Use streaming response
64
  return createStreamResponse(async (controller) => {
65
  const client = createInferenceClient(tokenResult);
 
72
  },
73
  ...(previousPrompt
74
  ? [
75
+ {
76
+ role: "user",
77
+ content: previousPrompt,
78
+ },
79
+ ]
80
  : []),
81
  ...(html
82
  ? [
83
+ {
84
+ role: "assistant",
85
+ content: `The current code is: ${html}.`,
86
+ },
87
+ ]
88
  : []),
89
+ ...(colors && colors.length > 0
90
  ? [
91
+ {
92
+ role: "user",
93
+ content: `Use the following color palette in your UI design: ${colors.join(", ")}`,
94
+ },
95
+ ]
96
  : []),
97
  {
98
  role: "user",
 
101
  ];
102
 
103
  const chatCompletion = client.chatCompletionStream(
104
+ getInferenceOptions(modelConfig, messages, modelConfig.max_tokens),
105
  );
106
  const encoder = new TextEncoder();
107
  for await (const chunk of chatCompletion) {
 
121
  ok: false,
122
  message: error instanceof Error ? error.message : "Unknown error",
123
  },
124
+ { status: 500 },
125
  );
126
  }
127
+ }
src/app/api/improve-prompt/route.ts CHANGED
@@ -1,11 +1,11 @@
1
  import { NextRequest, NextResponse } from "next/server";
2
  import { MODEL_CONFIG_PROMPT_IMPROVEMENT } from "@/lib/constants";
3
- import {
4
- getInferenceToken,
5
- checkTokenLimit,
6
- createInferenceClient,
7
  createStreamResponse,
8
- getInferenceOptions
9
  } from "@/lib/inference-utils";
10
 
11
  export const dynamic = "force-dynamic";
@@ -20,7 +20,7 @@ export async function POST(request: NextRequest) {
20
  ok: false,
21
  message: "Missing required fields",
22
  },
23
- { status: 400 }
24
  );
25
  }
26
 
@@ -33,12 +33,12 @@ export async function POST(request: NextRequest) {
33
  openLogin: tokenResult.error.openLogin,
34
  message: tokenResult.error.message,
35
  },
36
- { status: tokenResult.error.status }
37
  );
38
  }
39
 
40
  let TOKENS_USED = prompt?.length || 0;
41
-
42
  let modelConfig = MODEL_CONFIG_PROMPT_IMPROVEMENT;
43
 
44
  // Check token limit
@@ -52,7 +52,7 @@ export async function POST(request: NextRequest) {
52
  // Use streaming response
53
  return createStreamResponse(async (controller) => {
54
  const client = createInferenceClient(tokenResult);
55
-
56
  const messages = [
57
  {
58
  role: "system",
@@ -65,7 +65,7 @@ export async function POST(request: NextRequest) {
65
  ];
66
 
67
  const chatCompletion = client.chatCompletionStream(
68
- getInferenceOptions(modelConfig, messages, modelConfig.max_tokens)
69
  );
70
 
71
  const encoder = new TextEncoder();
@@ -82,7 +82,7 @@ export async function POST(request: NextRequest) {
82
  ok: false,
83
  message: error instanceof Error ? error.message : "Unknown error",
84
  },
85
- { status: 500 }
86
  );
87
  }
88
- }
 
1
  import { NextRequest, NextResponse } from "next/server";
2
  import { MODEL_CONFIG_PROMPT_IMPROVEMENT } from "@/lib/constants";
3
+ import {
4
+ getInferenceToken,
5
+ checkTokenLimit,
6
+ createInferenceClient,
7
  createStreamResponse,
8
+ getInferenceOptions,
9
  } from "@/lib/inference-utils";
10
 
11
  export const dynamic = "force-dynamic";
 
20
  ok: false,
21
  message: "Missing required fields",
22
  },
23
+ { status: 400 },
24
  );
25
  }
26
 
 
33
  openLogin: tokenResult.error.openLogin,
34
  message: tokenResult.error.message,
35
  },
36
+ { status: tokenResult.error.status },
37
  );
38
  }
39
 
40
  let TOKENS_USED = prompt?.length || 0;
41
+
42
  let modelConfig = MODEL_CONFIG_PROMPT_IMPROVEMENT;
43
 
44
  // Check token limit
 
52
  // Use streaming response
53
  return createStreamResponse(async (controller) => {
54
  const client = createInferenceClient(tokenResult);
55
+
56
  const messages = [
57
  {
58
  role: "system",
 
65
  ];
66
 
67
  const chatCompletion = client.chatCompletionStream(
68
+ getInferenceOptions(modelConfig, messages, modelConfig.max_tokens),
69
  );
70
 
71
  const encoder = new TextEncoder();
 
82
  ok: false,
83
  message: error instanceof Error ? error.message : "Unknown error",
84
  },
85
+ { status: 500 },
86
  );
87
  }
88
+ }
src/app/api/login/route.ts CHANGED
@@ -4,6 +4,6 @@ export const dynamic = "force-dynamic";
4
 
5
  export async function GET() {
6
  const url = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=openid%20profile%20inference-api&prompt=consent&state=1234567890`;
7
-
8
  return NextResponse.redirect(url);
9
- }
 
4
 
5
  export async function GET() {
6
  const url = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=openid%20profile%20inference-api&prompt=consent&state=1234567890`;
7
+
8
  return NextResponse.redirect(url);
9
+ }
src/app/api/share-link/route.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
 
3
  const BASE_URL = "https://anysite-gallery.novita.ai";
4
 
@@ -7,10 +7,10 @@ export async function POST(request: NextRequest) {
7
  const { filename, code } = await request.json();
8
 
9
  const response = await fetch(`${BASE_URL}/api/upload-code`, {
10
- method: 'POST',
11
  headers: {
12
- 'Content-Type': 'application/json',
13
- 'x-token': process.env.ANYSITE_GALLERY_AUTH_TOKEN || "",
14
  },
15
  body: JSON.stringify({
16
  filename,
@@ -20,18 +20,21 @@ export async function POST(request: NextRequest) {
20
 
21
  if (!response.ok) {
22
  return NextResponse.json(
23
- { success: false, message: `Failed to upload: ${response.status} ${response.statusText}` },
24
- { status: response.status }
 
 
 
25
  );
26
  }
27
 
28
  const result = await response.json();
29
  return NextResponse.json(result);
30
  } catch (error) {
31
- console.error('Error in share-link API:', error);
32
  return NextResponse.json(
33
- { success: false, message: 'Internal server error' },
34
- { status: 500 }
35
  );
36
  }
37
- }
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
 
3
  const BASE_URL = "https://anysite-gallery.novita.ai";
4
 
 
7
  const { filename, code } = await request.json();
8
 
9
  const response = await fetch(`${BASE_URL}/api/upload-code`, {
10
+ method: "POST",
11
  headers: {
12
+ "Content-Type": "application/json",
13
+ "x-token": process.env.ANYSITE_GALLERY_AUTH_TOKEN || "",
14
  },
15
  body: JSON.stringify({
16
  filename,
 
20
 
21
  if (!response.ok) {
22
  return NextResponse.json(
23
+ {
24
+ success: false,
25
+ message: `Failed to upload: ${response.status} ${response.statusText}`,
26
+ },
27
+ { status: response.status },
28
  );
29
  }
30
 
31
  const result = await response.json();
32
  return NextResponse.json(result);
33
  } catch (error) {
34
+ console.error("Error in share-link API:", error);
35
  return NextResponse.json(
36
+ { success: false, message: "Internal server error" },
37
+ { status: 500 },
38
  );
39
  }
40
+ }
src/app/layout.tsx CHANGED
@@ -1,30 +1,36 @@
1
- import type React from "react"
2
- import type { Metadata } from "next"
3
- import { Inter } from "next/font/google"
4
- import "./globals.css"
5
- import { ThemeProvider } from "@/components/theme-provider"
6
- import { TooltipProvider } from "@/components/ui/tooltip"
7
- import { Toaster } from "sonner"
8
- import { ModelProvider } from "@/lib/contexts/model-context"
9
- import { PostHogProvider } from './providers'
10
 
11
- const inter = Inter({ subsets: ["latin"] })
12
 
13
  export const metadata: Metadata = {
14
  title: "Novita AnySite",
15
- description: "Create stunning websites with cutting-edge AI models powered by Novita AI.",
16
- }
 
17
 
18
  export default function RootLayout({
19
  children,
20
  }: Readonly<{
21
- children: React.ReactNode
22
  }>) {
23
  return (
24
  <html lang="en" suppressHydrationWarning>
25
  <body className={inter.className} suppressHydrationWarning>
26
  <PostHogProvider>
27
- <ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
 
 
 
 
 
28
  <ModelProvider>
29
  <TooltipProvider>{children}</TooltipProvider>
30
  <Toaster richColors />
@@ -33,5 +39,5 @@ export default function RootLayout({
33
  </PostHogProvider>
34
  </body>
35
  </html>
36
- )
37
  }
 
1
+ import type React from "react";
2
+ import type { Metadata } from "next";
3
+ import { Inter } from "next/font/google";
4
+ import "./globals.css";
5
+ import { ThemeProvider } from "@/components/theme-provider";
6
+ import { TooltipProvider } from "@/components/ui/tooltip";
7
+ import { Toaster } from "sonner";
8
+ import { ModelProvider } from "@/lib/contexts/model-context";
9
+ import { PostHogProvider } from "./providers";
10
 
11
+ const inter = Inter({ subsets: ["latin"] });
12
 
13
  export const metadata: Metadata = {
14
  title: "Novita AnySite",
15
+ description:
16
+ "Create stunning websites with cutting-edge AI models powered by Novita AI.",
17
+ };
18
 
19
  export default function RootLayout({
20
  children,
21
  }: Readonly<{
22
+ children: React.ReactNode;
23
  }>) {
24
  return (
25
  <html lang="en" suppressHydrationWarning>
26
  <body className={inter.className} suppressHydrationWarning>
27
  <PostHogProvider>
28
+ <ThemeProvider
29
+ attribute="class"
30
+ defaultTheme="dark"
31
+ enableSystem
32
+ disableTransitionOnChange
33
+ >
34
  <ModelProvider>
35
  <TooltipProvider>{children}</TooltipProvider>
36
  <Toaster richColors />
 
39
  </PostHogProvider>
40
  </body>
41
  </html>
42
+ );
43
  }
src/app/providers.tsx CHANGED
@@ -1,21 +1,17 @@
1
  // app/providers.tsx
2
- 'use client'
3
 
4
- import posthog from 'posthog-js'
5
- import { PostHogProvider as PHProvider } from 'posthog-js/react'
6
- import { useEffect } from 'react'
7
 
8
  export function PostHogProvider({ children }: { children: React.ReactNode }) {
9
- useEffect(() => {
10
- posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY || '', {
11
- api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
12
- defaults: '2025-05-24',
13
- })
14
- }, [])
15
 
16
- return (
17
- <PHProvider client={posthog}>
18
- {children}
19
- </PHProvider>
20
- )
21
- }
 
1
  // app/providers.tsx
2
+ "use client";
3
 
4
+ import posthog from "posthog-js";
5
+ import { PostHogProvider as PHProvider } from "posthog-js/react";
6
+ import { useEffect } from "react";
7
 
8
  export function PostHogProvider({ children }: { children: React.ReactNode }) {
9
+ useEffect(() => {
10
+ posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY || "", {
11
+ api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
12
+ defaults: "2025-05-24",
13
+ });
14
+ }, []);
15
 
16
+ return <PHProvider client={posthog}>{children}</PHProvider>;
17
+ }
 
 
 
 
src/components/app-container.tsx CHANGED
@@ -1,17 +1,21 @@
1
- "use client"
2
-
3
- import { useState, useRef, useEffect } from "react"
4
- import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@/components/ui/resizable"
5
- import { CodeEditor } from "./code-editor"
6
- import { Preview } from "./preview"
7
- import { PromptInput } from "./prompt-input"
8
- import { Header } from "./header"
9
- import { VersionDropdown } from "./version-dropdown"
10
- import { AuthErrorPopup } from "./auth-error-popup"
11
- import { toast } from "sonner"
12
- import { DEFAULT_HTML } from "@/lib/constants"
13
- import { Version, PreviewRef } from "@/lib/types"
14
- import { ErrorMessage } from "./ui/error-message"
 
 
 
 
15
 
16
  export function AppContainer() {
17
  const [code, setCode] = useState<string>("");
@@ -35,15 +39,17 @@ export function AppContainer() {
35
  // Initialize current version ID from localStorage on mount
36
  useEffect(() => {
37
  setInitialLoading(true);
38
-
39
- const storedVersions = localStorage.getItem('novita-versions');
40
  if (storedVersions) {
41
  const parsedVersions = JSON.parse(storedVersions) as Version[];
42
  if (parsedVersions.length > 0) {
43
  // Sort by creation time, newest first
44
- const sortedVersions = parsedVersions.sort((a, b) => b.createdAt - a.createdAt);
 
 
45
  const newestVersion = sortedVersions[0];
46
-
47
  // Set the current version ID and also load its code and prompt
48
  setCurrentVersionId(newestVersion.id);
49
  latestVersionIdRef.current = newestVersion.id;
@@ -61,9 +67,9 @@ export function AppContainer() {
61
  setCode(DEFAULT_HTML);
62
  setCurrentVersionId(null);
63
  latestVersionIdRef.current = null;
64
- localStorage.setItem('novita-versions', JSON.stringify([]));
65
  }
66
-
67
  // Finish loading after a short delay to ensure everything renders properly
68
  setTimeout(() => {
69
  setInitialLoading(false);
@@ -73,23 +79,23 @@ export function AppContainer() {
73
  const handlePromptSubmit = async (newPrompt: string, colors: string[]) => {
74
  // Store the current prompt as previous prompt
75
  const oldPrompt = currentPromptRef.current;
76
-
77
  // Update prompt state
78
  setPrompt(newPrompt);
79
  currentPromptRef.current = newPrompt; // Update the ref immediately
80
-
81
  // Clear code and preview when submit is pressed
82
  setCode("");
83
-
84
  // Create a new version immediately with empty code when submit is clicked
85
  const newVersionId = saveVersionInitial(newPrompt);
86
  setCurrentVersionId(newVersionId);
87
-
88
  if (previewRef.current) {
89
  // Pass both the new prompt and old prompt for context
90
  await previewRef.current.generateCode(newPrompt, colors, oldPrompt);
91
  }
92
- }
93
 
94
  // Handle code change and save version
95
  const handleCodeChange = (newCode: string, save = false) => {
@@ -101,73 +107,74 @@ export function AppContainer() {
101
  // Make sure we're updating the latest version
102
  updateVersionWithFinalCode(newCode);
103
  }
104
- }
105
-
106
  // Explicitly save version when loading completes
107
  const handleLoadingChange = (isLoading: boolean) => {
108
  setLoading(isLoading);
109
- }
110
 
111
  // Function to create a new version initially with empty code
112
  const saveVersionInitial = (promptToSave: string) => {
113
  // Get existing versions
114
- const storedVersions = localStorage.getItem('novita-versions');
115
  let versions: Version[] = storedVersions ? JSON.parse(storedVersions) : [];
116
-
117
  // Create new version with empty code
118
  const newVersion: Version = {
119
  id: Date.now().toString(),
120
  code: "", // Empty code field initially
121
  prompt: promptToSave,
122
- createdAt: Date.now()
123
  };
124
-
125
  // Add to beginning (newest first)
126
  versions = [newVersion, ...versions];
127
-
128
  // Save to localStorage
129
- localStorage.setItem('novita-versions', JSON.stringify(versions));
130
-
131
  // Update current version ID
132
  setCurrentVersionId(newVersion.id);
133
  // Also update the latest version ID ref
134
  latestVersionIdRef.current = newVersion.id;
135
-
136
  return newVersion.id;
137
- }
138
 
139
  // Function to update existing version with final code
140
  const updateVersionWithFinalCode = (finalCode: string) => {
141
  // Get existing versions
142
- const storedVersions = localStorage.getItem('novita-versions');
143
  if (!storedVersions) return;
144
-
145
  let versions: Version[] = JSON.parse(storedVersions);
146
-
147
  if (!versions.length) return;
148
-
149
  // Prioritize using the latestVersionIdRef, then fall back to currentVersionId, then to the most recent version
150
- const versionIdToUpdate = latestVersionIdRef.current || currentVersionId || versions[0].id;
151
-
 
152
  if (!versionIdToUpdate) return;
153
-
154
  // Find and update the current version
155
- const updatedVersions = versions.map(version => {
156
  if (version.id === versionIdToUpdate) {
157
  return { ...version, code: finalCode };
158
  }
159
  return version;
160
  });
161
-
162
  // Save to localStorage
163
- localStorage.setItem('novita-versions', JSON.stringify(updatedVersions));
164
-
165
  // Show a subtle toast notification when code generation completes
166
- toast.success('Generated code saved', {
167
- position: 'top-right',
168
  duration: 2000,
169
  });
170
- }
171
 
172
  // Handle selecting a version from the dropdown
173
  const handleVersionSelect = (version: Version) => {
@@ -176,77 +183,78 @@ export function AppContainer() {
176
  currentPromptRef.current = version.prompt;
177
  setCurrentVersionId(version.id);
178
  latestVersionIdRef.current = version.id;
179
- }
180
-
181
  // Handle clearing all versions
182
  const handleClearAll = () => {
183
  // Reset to default HTML
184
  setCode(DEFAULT_HTML);
185
- setPrompt('');
186
- currentPromptRef.current = '';
187
  setCurrentVersionId(null);
188
  latestVersionIdRef.current = null;
189
- }
190
 
191
  const handleManualCodeEdit = (newCode: string) => {
192
  setCode(newCode);
193
-
194
  if (currentVersionId) {
195
  updateVersionWithCode(newCode);
196
  }
197
  };
198
 
199
- const updateVersionWithCode = (editedCode: string) => { // Get existing versions
200
- const storedVersions = localStorage.getItem('novita-versions');
 
201
  if (!storedVersions || !currentVersionId) return;
202
-
203
  let versions: Version[] = JSON.parse(storedVersions);
204
-
205
- const updatedVersions = versions.map(version => {
206
  if (version.id === currentVersionId) {
207
  return { ...version, code: editedCode };
208
  }
209
  return version;
210
  });
211
-
212
- localStorage.setItem('novita-versions', JSON.stringify(updatedVersions));
213
  };
214
 
215
  return (
216
  <div className="flex flex-col h-screen bg-novita-dark text-white">
217
- <Header
218
  onVersionSelect={handleVersionSelect}
219
  currentVersion={currentVersionId || undefined}
220
  onClearAll={handleClearAll}
221
  />
222
  <div className="flex flex-1 overflow-hidden relative">
223
  <ResizablePanelGroup direction="horizontal" className="h-full">
224
- <ResizablePanel
225
- defaultSize={33}
226
- minSize={20}
227
  maxSize={60}
228
  className="flex flex-col relative"
229
  >
230
- <CodeEditor
231
- code={code}
232
- isLoading={loading}
233
  onCodeChange={handleManualCodeEdit}
234
  />
235
-
236
- <AuthErrorPopup
237
- show={showAuthError}
238
- onClose={() => setShowAuthError(false)}
239
  />
240
-
241
- <PromptInput
242
- onSubmit={handlePromptSubmit}
243
- isLoading={loading}
244
  initialPrompt={prompt}
245
  onImproveError={setImproveError}
246
  />
247
  </ResizablePanel>
248
-
249
- <ResizableHandle
250
  withHandle={false}
251
  className="w-1 bg-novita-gray/20 hover:bg-novita-gray/40 transition-colors duration-200 relative group cursor-col-resize"
252
  >
@@ -254,16 +262,16 @@ export function AppContainer() {
254
  <div className="w-0.5 h-8 bg-novita-gray/60 rounded-full" />
255
  </div>
256
  </ResizableHandle>
257
-
258
- <ResizablePanel
259
- defaultSize={67}
260
  minSize={40}
261
  className="flex flex-col"
262
  >
263
- <Preview
264
  ref={previewRef}
265
- initialHtml={code}
266
- onCodeChange={handleCodeChange}
267
  onLoadingChange={handleLoadingChange}
268
  onAuthErrorChange={setShowAuthError}
269
  onErrorChange={setGenerationError}
@@ -271,29 +279,34 @@ export function AppContainer() {
271
  />
272
  </ResizablePanel>
273
  </ResizablePanelGroup>
274
-
275
  {initialLoading && (
276
  <div className="absolute inset-0 bg-novita-dark/80 backdrop-blur-sm flex items-center justify-center z-[999]">
277
  <div className="p-4 text-center">
278
- <div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]" role="status">
279
- <span className="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]">Loading...</span>
 
 
 
 
 
280
  </div>
281
  <p className="mt-2">Preparing the workspace...</p>
282
  </div>
283
  </div>
284
  )}
285
  </div>
286
-
287
- <ErrorMessage
288
- message={improveError || generationError}
289
  onClose={() => {
290
  if (improveError) {
291
  setImproveError(null);
292
  } else if (generationError) {
293
  setGenerationError(null);
294
  }
295
- }}
296
  />
297
  </div>
298
- )
299
  }
 
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect } from "react";
4
+ import {
5
+ ResizablePanelGroup,
6
+ ResizablePanel,
7
+ ResizableHandle,
8
+ } from "@/components/ui/resizable";
9
+ import { CodeEditor } from "./code-editor";
10
+ import { Preview } from "./preview";
11
+ import { PromptInput } from "./prompt-input";
12
+ import { Header } from "./header";
13
+ import { VersionDropdown } from "./version-dropdown";
14
+ import { AuthErrorPopup } from "./auth-error-popup";
15
+ import { toast } from "sonner";
16
+ import { DEFAULT_HTML } from "@/lib/constants";
17
+ import { Version, PreviewRef } from "@/lib/types";
18
+ import { ErrorMessage } from "./ui/error-message";
19
 
20
  export function AppContainer() {
21
  const [code, setCode] = useState<string>("");
 
39
  // Initialize current version ID from localStorage on mount
40
  useEffect(() => {
41
  setInitialLoading(true);
42
+
43
+ const storedVersions = localStorage.getItem("novita-versions");
44
  if (storedVersions) {
45
  const parsedVersions = JSON.parse(storedVersions) as Version[];
46
  if (parsedVersions.length > 0) {
47
  // Sort by creation time, newest first
48
+ const sortedVersions = parsedVersions.sort(
49
+ (a, b) => b.createdAt - a.createdAt,
50
+ );
51
  const newestVersion = sortedVersions[0];
52
+
53
  // Set the current version ID and also load its code and prompt
54
  setCurrentVersionId(newestVersion.id);
55
  latestVersionIdRef.current = newestVersion.id;
 
67
  setCode(DEFAULT_HTML);
68
  setCurrentVersionId(null);
69
  latestVersionIdRef.current = null;
70
+ localStorage.setItem("novita-versions", JSON.stringify([]));
71
  }
72
+
73
  // Finish loading after a short delay to ensure everything renders properly
74
  setTimeout(() => {
75
  setInitialLoading(false);
 
79
  const handlePromptSubmit = async (newPrompt: string, colors: string[]) => {
80
  // Store the current prompt as previous prompt
81
  const oldPrompt = currentPromptRef.current;
82
+
83
  // Update prompt state
84
  setPrompt(newPrompt);
85
  currentPromptRef.current = newPrompt; // Update the ref immediately
86
+
87
  // Clear code and preview when submit is pressed
88
  setCode("");
89
+
90
  // Create a new version immediately with empty code when submit is clicked
91
  const newVersionId = saveVersionInitial(newPrompt);
92
  setCurrentVersionId(newVersionId);
93
+
94
  if (previewRef.current) {
95
  // Pass both the new prompt and old prompt for context
96
  await previewRef.current.generateCode(newPrompt, colors, oldPrompt);
97
  }
98
+ };
99
 
100
  // Handle code change and save version
101
  const handleCodeChange = (newCode: string, save = false) => {
 
107
  // Make sure we're updating the latest version
108
  updateVersionWithFinalCode(newCode);
109
  }
110
+ };
111
+
112
  // Explicitly save version when loading completes
113
  const handleLoadingChange = (isLoading: boolean) => {
114
  setLoading(isLoading);
115
+ };
116
 
117
  // Function to create a new version initially with empty code
118
  const saveVersionInitial = (promptToSave: string) => {
119
  // Get existing versions
120
+ const storedVersions = localStorage.getItem("novita-versions");
121
  let versions: Version[] = storedVersions ? JSON.parse(storedVersions) : [];
122
+
123
  // Create new version with empty code
124
  const newVersion: Version = {
125
  id: Date.now().toString(),
126
  code: "", // Empty code field initially
127
  prompt: promptToSave,
128
+ createdAt: Date.now(),
129
  };
130
+
131
  // Add to beginning (newest first)
132
  versions = [newVersion, ...versions];
133
+
134
  // Save to localStorage
135
+ localStorage.setItem("novita-versions", JSON.stringify(versions));
136
+
137
  // Update current version ID
138
  setCurrentVersionId(newVersion.id);
139
  // Also update the latest version ID ref
140
  latestVersionIdRef.current = newVersion.id;
141
+
142
  return newVersion.id;
143
+ };
144
 
145
  // Function to update existing version with final code
146
  const updateVersionWithFinalCode = (finalCode: string) => {
147
  // Get existing versions
148
+ const storedVersions = localStorage.getItem("novita-versions");
149
  if (!storedVersions) return;
150
+
151
  let versions: Version[] = JSON.parse(storedVersions);
152
+
153
  if (!versions.length) return;
154
+
155
  // Prioritize using the latestVersionIdRef, then fall back to currentVersionId, then to the most recent version
156
+ const versionIdToUpdate =
157
+ latestVersionIdRef.current || currentVersionId || versions[0].id;
158
+
159
  if (!versionIdToUpdate) return;
160
+
161
  // Find and update the current version
162
+ const updatedVersions = versions.map((version) => {
163
  if (version.id === versionIdToUpdate) {
164
  return { ...version, code: finalCode };
165
  }
166
  return version;
167
  });
168
+
169
  // Save to localStorage
170
+ localStorage.setItem("novita-versions", JSON.stringify(updatedVersions));
171
+
172
  // Show a subtle toast notification when code generation completes
173
+ toast.success("Generated code saved", {
174
+ position: "top-right",
175
  duration: 2000,
176
  });
177
+ };
178
 
179
  // Handle selecting a version from the dropdown
180
  const handleVersionSelect = (version: Version) => {
 
183
  currentPromptRef.current = version.prompt;
184
  setCurrentVersionId(version.id);
185
  latestVersionIdRef.current = version.id;
186
+ };
187
+
188
  // Handle clearing all versions
189
  const handleClearAll = () => {
190
  // Reset to default HTML
191
  setCode(DEFAULT_HTML);
192
+ setPrompt("");
193
+ currentPromptRef.current = "";
194
  setCurrentVersionId(null);
195
  latestVersionIdRef.current = null;
196
+ };
197
 
198
  const handleManualCodeEdit = (newCode: string) => {
199
  setCode(newCode);
200
+
201
  if (currentVersionId) {
202
  updateVersionWithCode(newCode);
203
  }
204
  };
205
 
206
+ const updateVersionWithCode = (editedCode: string) => {
207
+ // Get existing versions
208
+ const storedVersions = localStorage.getItem("novita-versions");
209
  if (!storedVersions || !currentVersionId) return;
210
+
211
  let versions: Version[] = JSON.parse(storedVersions);
212
+
213
+ const updatedVersions = versions.map((version) => {
214
  if (version.id === currentVersionId) {
215
  return { ...version, code: editedCode };
216
  }
217
  return version;
218
  });
219
+
220
+ localStorage.setItem("novita-versions", JSON.stringify(updatedVersions));
221
  };
222
 
223
  return (
224
  <div className="flex flex-col h-screen bg-novita-dark text-white">
225
+ <Header
226
  onVersionSelect={handleVersionSelect}
227
  currentVersion={currentVersionId || undefined}
228
  onClearAll={handleClearAll}
229
  />
230
  <div className="flex flex-1 overflow-hidden relative">
231
  <ResizablePanelGroup direction="horizontal" className="h-full">
232
+ <ResizablePanel
233
+ defaultSize={33}
234
+ minSize={20}
235
  maxSize={60}
236
  className="flex flex-col relative"
237
  >
238
+ <CodeEditor
239
+ code={code}
240
+ isLoading={loading}
241
  onCodeChange={handleManualCodeEdit}
242
  />
243
+
244
+ <AuthErrorPopup
245
+ show={showAuthError}
246
+ onClose={() => setShowAuthError(false)}
247
  />
248
+
249
+ <PromptInput
250
+ onSubmit={handlePromptSubmit}
251
+ isLoading={loading}
252
  initialPrompt={prompt}
253
  onImproveError={setImproveError}
254
  />
255
  </ResizablePanel>
256
+
257
+ <ResizableHandle
258
  withHandle={false}
259
  className="w-1 bg-novita-gray/20 hover:bg-novita-gray/40 transition-colors duration-200 relative group cursor-col-resize"
260
  >
 
262
  <div className="w-0.5 h-8 bg-novita-gray/60 rounded-full" />
263
  </div>
264
  </ResizableHandle>
265
+
266
+ <ResizablePanel
267
+ defaultSize={67}
268
  minSize={40}
269
  className="flex flex-col"
270
  >
271
+ <Preview
272
  ref={previewRef}
273
+ initialHtml={code}
274
+ onCodeChange={handleCodeChange}
275
  onLoadingChange={handleLoadingChange}
276
  onAuthErrorChange={setShowAuthError}
277
  onErrorChange={setGenerationError}
 
279
  />
280
  </ResizablePanel>
281
  </ResizablePanelGroup>
282
+
283
  {initialLoading && (
284
  <div className="absolute inset-0 bg-novita-dark/80 backdrop-blur-sm flex items-center justify-center z-[999]">
285
  <div className="p-4 text-center">
286
+ <div
287
+ className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"
288
+ role="status"
289
+ >
290
+ <span className="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]">
291
+ Loading...
292
+ </span>
293
  </div>
294
  <p className="mt-2">Preparing the workspace...</p>
295
  </div>
296
  </div>
297
  )}
298
  </div>
299
+
300
+ <ErrorMessage
301
+ message={improveError || generationError}
302
  onClose={() => {
303
  if (improveError) {
304
  setImproveError(null);
305
  } else if (generationError) {
306
  setGenerationError(null);
307
  }
308
+ }}
309
  />
310
  </div>
311
+ );
312
  }
src/components/auth-error-popup.tsx CHANGED
@@ -1,40 +1,51 @@
1
- import React from 'react'
2
 
3
  interface AuthErrorPopupProps {
4
- show: boolean
5
- authUrl?: string
6
- onClose?: () => void
7
  }
8
 
9
- export function AuthErrorPopup({
10
- show,
11
- authUrl = '/api/login',
12
- onClose
13
  }: AuthErrorPopupProps) {
14
- if (!show) return null
15
-
16
  return (
17
  <div className="absolute right-0 bottom-20 mr-4 w-fit px-2.5 bg-gray-800 border border-gray-600 rounded-lg p-4 shadow-xl z-50">
18
  {onClose && (
19
- <button
20
  onClick={onClose}
21
  className="absolute top-1 right-1 text-gray-400 hover:text-white p-1 rounded-full hover:bg-gray-700 focus:outline-none"
22
  aria-label="Close"
23
  >
24
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
25
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
 
 
 
 
 
 
 
 
 
 
 
26
  </svg>
27
  </button>
28
  )}
29
  <div className="flex flex-col items-center">
30
  <p className="mb-3 text-center">Sign in continue</p>
31
  <a href={authUrl} className="block w-[214px] h-[40px]">
32
- <img
33
- src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-lg-dark.svg"
34
- alt="Sign in with Hugging Face"
35
  />
36
  </a>
37
  </div>
38
  </div>
39
- )
40
- }
 
1
+ import React from "react";
2
 
3
  interface AuthErrorPopupProps {
4
+ show: boolean;
5
+ authUrl?: string;
6
+ onClose?: () => void;
7
  }
8
 
9
+ export function AuthErrorPopup({
10
+ show,
11
+ authUrl = "/api/login",
12
+ onClose,
13
  }: AuthErrorPopupProps) {
14
+ if (!show) return null;
15
+
16
  return (
17
  <div className="absolute right-0 bottom-20 mr-4 w-fit px-2.5 bg-gray-800 border border-gray-600 rounded-lg p-4 shadow-xl z-50">
18
  {onClose && (
19
+ <button
20
  onClick={onClose}
21
  className="absolute top-1 right-1 text-gray-400 hover:text-white p-1 rounded-full hover:bg-gray-700 focus:outline-none"
22
  aria-label="Close"
23
  >
24
+ <svg
25
+ className="w-4 h-4"
26
+ fill="none"
27
+ stroke="currentColor"
28
+ viewBox="0 0 24 24"
29
+ xmlns="http://www.w3.org/2000/svg"
30
+ >
31
+ <path
32
+ strokeLinecap="round"
33
+ strokeLinejoin="round"
34
+ strokeWidth={2}
35
+ d="M6 18L18 6M6 6l12 12"
36
+ />
37
  </svg>
38
  </button>
39
  )}
40
  <div className="flex flex-col items-center">
41
  <p className="mb-3 text-center">Sign in continue</p>
42
  <a href={authUrl} className="block w-[214px] h-[40px]">
43
+ <img
44
+ src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-lg-dark.svg"
45
+ alt="Sign in with Hugging Face"
46
  />
47
  </a>
48
  </div>
49
  </div>
50
+ );
51
+ }
src/components/code-editor.tsx CHANGED
@@ -1,4 +1,4 @@
1
- "use client"
2
 
3
  import { useEffect, useRef, useState, useCallback } from "react";
4
  import { debounce, highlightHTML } from "@/lib/utils";
@@ -9,7 +9,11 @@ interface CodeEditorProps {
9
  onCodeChange?: (code: string) => void;
10
  }
11
 
12
- export function CodeEditor({ code, isLoading = false, onCodeChange }: CodeEditorProps) {
 
 
 
 
13
  const containerRef = useRef<HTMLDivElement>(null);
14
  const textareaRef = useRef<HTMLTextAreaElement>(null);
15
  const highlightRef = useRef<HTMLDivElement>(null);
@@ -22,7 +26,7 @@ export function CodeEditor({ code, isLoading = false, onCodeChange }: CodeEditor
22
  onCodeChange(newCode);
23
  }
24
  }, 1000),
25
- [onCodeChange]
26
  );
27
 
28
  useEffect(() => {
@@ -48,7 +52,11 @@ export function CodeEditor({ code, isLoading = false, onCodeChange }: CodeEditor
48
  };
49
 
50
  useEffect(() => {
51
- if (isAtBottom && textareaRef.current && document.activeElement !== textareaRef.current) {
 
 
 
 
52
  textareaRef.current.scrollTop = textareaRef.current.scrollHeight;
53
  if (highlightRef.current) {
54
  highlightRef.current.scrollTop = highlightRef.current.scrollHeight;
@@ -58,16 +66,17 @@ export function CodeEditor({ code, isLoading = false, onCodeChange }: CodeEditor
58
 
59
  // Handle tab key for indentation
60
  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
61
- if (e.key === 'Tab') {
62
  e.preventDefault();
63
  const textarea = e.currentTarget;
64
  const start = textarea.selectionStart;
65
  const end = textarea.selectionEnd;
66
-
67
- const newValue = localCode.substring(0, start) + ' ' + localCode.substring(end);
 
68
  setLocalCode(newValue);
69
  debouncedSave(newValue);
70
-
71
  setTimeout(() => {
72
  textarea.selectionStart = textarea.selectionEnd = start + 2;
73
  }, 0);
@@ -78,20 +87,21 @@ export function CodeEditor({ code, isLoading = false, onCodeChange }: CodeEditor
78
 
79
  return (
80
  <div className="flex-1 overflow-hidden p-4 pr-2">
81
- <div
82
- ref={containerRef}
83
  className="code-area rounded-md font-mono text-sm h-full border border-novita-gray/20 relative overflow-hidden"
84
  >
85
  <div className="relative h-full">
86
  {/* Syntax highlighted background - always visible */}
87
- <div
88
  ref={highlightRef}
89
  className="absolute inset-0 overflow-auto p-4 pointer-events-none whitespace-pre-wrap font-mono text-sm leading-relaxed"
90
- style={{
91
- fontSize: '0.875rem',
92
- lineHeight: '1.5',
93
- fontFamily: 'ui-monospace, SFMono-Regular, \"SF Mono\", Consolas, \"Liberation Mono\", Menlo, monospace',
94
- color: '#d4d4d4'
 
95
  }}
96
  dangerouslySetInnerHTML={{ __html: highlightedCode }}
97
  />
@@ -105,20 +115,21 @@ export function CodeEditor({ code, isLoading = false, onCodeChange }: CodeEditor
105
  onKeyDown={handleKeyDown}
106
  disabled={isLoading}
107
  className="absolute inset-0 w-full h-full bg-transparent text-transparent caret-white resize-none outline-none whitespace-pre-wrap font-mono text-sm leading-relaxed p-4 selection:bg-blue-500/30"
108
- style={{
109
- fontSize: '0.875rem',
110
- lineHeight: '1.5',
111
- fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace'
 
112
  }}
113
  placeholder="Your generated code will appear here..."
114
  spellCheck={false}
115
  />
116
  </div>
117
-
118
  {isLoading && (
119
  <span className="absolute bottom-4 right-4 inline-block h-4 w-2 bg-white/70 animate-pulse z-10"></span>
120
  )}
121
  </div>
122
  </div>
123
- )
124
  }
 
1
+ "use client";
2
 
3
  import { useEffect, useRef, useState, useCallback } from "react";
4
  import { debounce, highlightHTML } from "@/lib/utils";
 
9
  onCodeChange?: (code: string) => void;
10
  }
11
 
12
+ export function CodeEditor({
13
+ code,
14
+ isLoading = false,
15
+ onCodeChange,
16
+ }: CodeEditorProps) {
17
  const containerRef = useRef<HTMLDivElement>(null);
18
  const textareaRef = useRef<HTMLTextAreaElement>(null);
19
  const highlightRef = useRef<HTMLDivElement>(null);
 
26
  onCodeChange(newCode);
27
  }
28
  }, 1000),
29
+ [onCodeChange],
30
  );
31
 
32
  useEffect(() => {
 
52
  };
53
 
54
  useEffect(() => {
55
+ if (
56
+ isAtBottom &&
57
+ textareaRef.current &&
58
+ document.activeElement !== textareaRef.current
59
+ ) {
60
  textareaRef.current.scrollTop = textareaRef.current.scrollHeight;
61
  if (highlightRef.current) {
62
  highlightRef.current.scrollTop = highlightRef.current.scrollHeight;
 
66
 
67
  // Handle tab key for indentation
68
  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
69
+ if (e.key === "Tab") {
70
  e.preventDefault();
71
  const textarea = e.currentTarget;
72
  const start = textarea.selectionStart;
73
  const end = textarea.selectionEnd;
74
+
75
+ const newValue =
76
+ localCode.substring(0, start) + " " + localCode.substring(end);
77
  setLocalCode(newValue);
78
  debouncedSave(newValue);
79
+
80
  setTimeout(() => {
81
  textarea.selectionStart = textarea.selectionEnd = start + 2;
82
  }, 0);
 
87
 
88
  return (
89
  <div className="flex-1 overflow-hidden p-4 pr-2">
90
+ <div
91
+ ref={containerRef}
92
  className="code-area rounded-md font-mono text-sm h-full border border-novita-gray/20 relative overflow-hidden"
93
  >
94
  <div className="relative h-full">
95
  {/* Syntax highlighted background - always visible */}
96
+ <div
97
  ref={highlightRef}
98
  className="absolute inset-0 overflow-auto p-4 pointer-events-none whitespace-pre-wrap font-mono text-sm leading-relaxed"
99
+ style={{
100
+ fontSize: "0.875rem",
101
+ lineHeight: "1.5",
102
+ fontFamily:
103
+ 'ui-monospace, SFMono-Regular, \"SF Mono\", Consolas, \"Liberation Mono\", Menlo, monospace',
104
+ color: "#d4d4d4",
105
  }}
106
  dangerouslySetInnerHTML={{ __html: highlightedCode }}
107
  />
 
115
  onKeyDown={handleKeyDown}
116
  disabled={isLoading}
117
  className="absolute inset-0 w-full h-full bg-transparent text-transparent caret-white resize-none outline-none whitespace-pre-wrap font-mono text-sm leading-relaxed p-4 selection:bg-blue-500/30"
118
+ style={{
119
+ fontSize: "0.875rem",
120
+ lineHeight: "1.5",
121
+ fontFamily:
122
+ 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace',
123
  }}
124
  placeholder="Your generated code will appear here..."
125
  spellCheck={false}
126
  />
127
  </div>
128
+
129
  {isLoading && (
130
  <span className="absolute bottom-4 right-4 inline-block h-4 w-2 bg-white/70 animate-pulse z-10"></span>
131
  )}
132
  </div>
133
  </div>
134
+ );
135
  }
src/components/color-panel.tsx CHANGED
@@ -1,26 +1,42 @@
1
- "use client"
2
 
3
- import { useState, useEffect, useRef } from "react"
4
- import { Plus, X } from "lucide-react"
5
- import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from "@/components/ui/tooltip"
6
- import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
7
- import { cn } from "@/lib/utils"
8
- import { Button } from "@/components/ui/button"
 
 
 
 
 
 
 
 
 
9
 
10
  interface ColorCircleProps {
11
- color: string
12
- onClick?: () => void
13
- onDelete?: () => void
14
- className?: string
15
- size?: "sm" | "md"
16
- showDeleteIcon?: boolean
17
  }
18
 
19
- function ColorCircle({ color, onClick, onDelete, className, size = "sm", showDeleteIcon = false }: ColorCircleProps) {
 
 
 
 
 
 
 
20
  const sizeClasses = {
21
  sm: "w-5 h-5",
22
  md: "w-6 h-6",
23
- }
24
 
25
  return (
26
  <div className="relative group h-5">
@@ -49,7 +65,7 @@ function ColorCircle({ color, onClick, onDelete, className, size = "sm", showDel
49
  </button>
50
  )}
51
  </div>
52
- )
53
  }
54
 
55
  const PRESET_COLORS = [
@@ -65,27 +81,27 @@ const PRESET_COLORS = [
65
  "#14b8a6", // Teal
66
  "#f97316", // Orange
67
  "#6366f1", // Indigo
68
- ]
69
 
70
  interface ColorPanelProps {
71
  onColorsChange?: (colors: string[]) => void;
72
  }
73
 
74
  export function ColorPanel({ onColorsChange }: ColorPanelProps) {
75
- const maxColors = 6
76
- const [colors, setColors] = useState<string[]>([])
77
- const [isPopoverOpen, setIsPopoverOpen] = useState(false)
78
- const [selectedColor, setSelectedColor] = useState<string | null>(null)
79
- const [isAddingDisabled, setIsAddingDisabled] = useState(false)
80
- const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null)
81
 
82
  useEffect(() => {
83
  return () => {
84
  if (debounceTimeoutRef.current) {
85
- clearTimeout(debounceTimeoutRef.current)
86
  }
87
- }
88
- }, [])
89
 
90
  useEffect(() => {
91
  // Call onColorsChange when colors change
@@ -93,37 +109,38 @@ export function ColorPanel({ onColorsChange }: ColorPanelProps) {
93
  }, [colors, onColorsChange]);
94
 
95
  const addColor = () => {
96
- if (colors.length >= maxColors || !selectedColor || isAddingDisabled) return
97
-
 
98
  if (!colors.includes(selectedColor)) {
99
- setIsAddingDisabled(true)
100
- setColors([...colors, selectedColor])
101
- setSelectedColor(null)
102
- setIsPopoverOpen(false)
103
-
104
  // Debounce to prevent rapid clicking
105
  debounceTimeoutRef.current = setTimeout(() => {
106
- setIsAddingDisabled(false)
107
- }, 500)
108
  }
109
- }
110
 
111
  const selectColor = (color: string) => {
112
- setSelectedColor(color)
113
- }
114
 
115
  const removeColor = (indexToRemove: number) => {
116
- setColors(colors.filter((_, index) => index !== indexToRemove))
117
- }
118
 
119
  return (
120
  <TooltipProvider>
121
  <div className="absolute top-2.5 left-3 flex items-center gap-1.5 z-10">
122
  {colors.map((color, index) => (
123
- <ColorCircle
124
- key={`${color}-${index}`}
125
- color={color}
126
- onClick={() => removeColor(index)}
127
  onDelete={() => removeColor(index)}
128
  showDeleteIcon={true}
129
  />
@@ -163,13 +180,15 @@ export function ColorPanel({ onColorsChange }: ColorPanelProps) {
163
  onClick={() => selectColor(color)}
164
  className={cn(
165
  selectedColor === color && "ring-2 ring-white/50",
166
- colors.includes(color) && "opacity-50"
167
  )}
168
  />
169
  ))}
170
  </div>
171
  <div className="pt-2 border-t border-novita-gray/30">
172
- <label className="block text-xs text-novita-gray mb-1.5">Custom color</label>
 
 
173
  <input
174
  type="color"
175
  className="w-full h-8 bg-transparent border border-novita-gray/30 rounded cursor-pointer"
@@ -178,7 +197,7 @@ export function ColorPanel({ onColorsChange }: ColorPanelProps) {
178
  />
179
  </div>
180
  <div className="mt-3">
181
- <Button
182
  onClick={addColor}
183
  disabled={!selectedColor || isAddingDisabled}
184
  className="w-full h-6 bg-novita-white hover:bg-novita-gray/90 text-white rounded border border-novita-gray/90"
@@ -192,5 +211,5 @@ export function ColorPanel({ onColorsChange }: ColorPanelProps) {
192
  )}
193
  </div>
194
  </TooltipProvider>
195
- )
196
  }
 
1
+ "use client";
2
 
3
+ import { useState, useEffect, useRef } from "react";
4
+ import { Plus, X } from "lucide-react";
5
+ import {
6
+ Tooltip,
7
+ TooltipContent,
8
+ TooltipTrigger,
9
+ TooltipProvider,
10
+ } from "@/components/ui/tooltip";
11
+ import {
12
+ Popover,
13
+ PopoverContent,
14
+ PopoverTrigger,
15
+ } from "@/components/ui/popover";
16
+ import { cn } from "@/lib/utils";
17
+ import { Button } from "@/components/ui/button";
18
 
19
  interface ColorCircleProps {
20
+ color: string;
21
+ onClick?: () => void;
22
+ onDelete?: () => void;
23
+ className?: string;
24
+ size?: "sm" | "md";
25
+ showDeleteIcon?: boolean;
26
  }
27
 
28
+ function ColorCircle({
29
+ color,
30
+ onClick,
31
+ onDelete,
32
+ className,
33
+ size = "sm",
34
+ showDeleteIcon = false,
35
+ }: ColorCircleProps) {
36
  const sizeClasses = {
37
  sm: "w-5 h-5",
38
  md: "w-6 h-6",
39
+ };
40
 
41
  return (
42
  <div className="relative group h-5">
 
65
  </button>
66
  )}
67
  </div>
68
+ );
69
  }
70
 
71
  const PRESET_COLORS = [
 
81
  "#14b8a6", // Teal
82
  "#f97316", // Orange
83
  "#6366f1", // Indigo
84
+ ];
85
 
86
  interface ColorPanelProps {
87
  onColorsChange?: (colors: string[]) => void;
88
  }
89
 
90
  export function ColorPanel({ onColorsChange }: ColorPanelProps) {
91
+ const maxColors = 6;
92
+ const [colors, setColors] = useState<string[]>([]);
93
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
94
+ const [selectedColor, setSelectedColor] = useState<string | null>(null);
95
+ const [isAddingDisabled, setIsAddingDisabled] = useState(false);
96
+ const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
97
 
98
  useEffect(() => {
99
  return () => {
100
  if (debounceTimeoutRef.current) {
101
+ clearTimeout(debounceTimeoutRef.current);
102
  }
103
+ };
104
+ }, []);
105
 
106
  useEffect(() => {
107
  // Call onColorsChange when colors change
 
109
  }, [colors, onColorsChange]);
110
 
111
  const addColor = () => {
112
+ if (colors.length >= maxColors || !selectedColor || isAddingDisabled)
113
+ return;
114
+
115
  if (!colors.includes(selectedColor)) {
116
+ setIsAddingDisabled(true);
117
+ setColors([...colors, selectedColor]);
118
+ setSelectedColor(null);
119
+ setIsPopoverOpen(false);
120
+
121
  // Debounce to prevent rapid clicking
122
  debounceTimeoutRef.current = setTimeout(() => {
123
+ setIsAddingDisabled(false);
124
+ }, 500);
125
  }
126
+ };
127
 
128
  const selectColor = (color: string) => {
129
+ setSelectedColor(color);
130
+ };
131
 
132
  const removeColor = (indexToRemove: number) => {
133
+ setColors(colors.filter((_, index) => index !== indexToRemove));
134
+ };
135
 
136
  return (
137
  <TooltipProvider>
138
  <div className="absolute top-2.5 left-3 flex items-center gap-1.5 z-10">
139
  {colors.map((color, index) => (
140
+ <ColorCircle
141
+ key={`${color}-${index}`}
142
+ color={color}
143
+ onClick={() => removeColor(index)}
144
  onDelete={() => removeColor(index)}
145
  showDeleteIcon={true}
146
  />
 
180
  onClick={() => selectColor(color)}
181
  className={cn(
182
  selectedColor === color && "ring-2 ring-white/50",
183
+ colors.includes(color) && "opacity-50",
184
  )}
185
  />
186
  ))}
187
  </div>
188
  <div className="pt-2 border-t border-novita-gray/30">
189
+ <label className="block text-xs text-novita-gray mb-1.5">
190
+ Custom color
191
+ </label>
192
  <input
193
  type="color"
194
  className="w-full h-8 bg-transparent border border-novita-gray/30 rounded cursor-pointer"
 
197
  />
198
  </div>
199
  <div className="mt-3">
200
+ <Button
201
  onClick={addColor}
202
  disabled={!selectedColor || isAddingDisabled}
203
  className="w-full h-6 bg-novita-white hover:bg-novita-gray/90 text-white rounded border border-novita-gray/90"
 
211
  )}
212
  </div>
213
  </TooltipProvider>
214
+ );
215
  }
src/components/header.tsx CHANGED
@@ -1,16 +1,16 @@
1
- "use client"
2
- import { Logo } from "./logo"
3
- import { ModelSelector } from "./model-selector"
4
- import { VersionDropdown } from "./version-dropdown"
5
 
6
  export function Header({
7
  onVersionSelect,
8
  currentVersion,
9
- onClearAll
10
- }: {
11
- onVersionSelect?: (version: any) => void,
12
- currentVersion?: string,
13
- onClearAll?: () => void
14
  }) {
15
  return (
16
  <header className="border-b border-novita-gray/20 p-3 flex justify-between items-center bg-novita-dark">
@@ -21,17 +21,25 @@ export function Header({
21
  <div className="flex items-center">
22
  <ModelSelector />
23
  {onVersionSelect && (
24
- <VersionDropdown
25
- onVersionSelect={onVersionSelect}
26
  currentVersion={currentVersion}
27
  onClearAll={onClearAll}
28
  />
29
  )}
30
  </div>
31
  </div>
32
- <span className="text-sm text-white/40"><a href="https://novita.ai/models/llm" target="_blank" className="underline hover:text-white/70 transition-colors duration-200">Powered by novita.ai</a></span>
 
 
 
 
 
 
 
 
33
  </div>
34
  </div>
35
  </header>
36
- )
37
- }
 
1
+ "use client";
2
+ import { Logo } from "./logo";
3
+ import { ModelSelector } from "./model-selector";
4
+ import { VersionDropdown } from "./version-dropdown";
5
 
6
  export function Header({
7
  onVersionSelect,
8
  currentVersion,
9
+ onClearAll,
10
+ }: {
11
+ onVersionSelect?: (version: any) => void;
12
+ currentVersion?: string;
13
+ onClearAll?: () => void;
14
  }) {
15
  return (
16
  <header className="border-b border-novita-gray/20 p-3 flex justify-between items-center bg-novita-dark">
 
21
  <div className="flex items-center">
22
  <ModelSelector />
23
  {onVersionSelect && (
24
+ <VersionDropdown
25
+ onVersionSelect={onVersionSelect}
26
  currentVersion={currentVersion}
27
  onClearAll={onClearAll}
28
  />
29
  )}
30
  </div>
31
  </div>
32
+ <span className="text-sm text-white/40">
33
+ <a
34
+ href="https://novita.ai/models/llm"
35
+ target="_blank"
36
+ className="underline hover:text-white/70 transition-colors duration-200"
37
+ >
38
+ Powered by novita.ai
39
+ </a>
40
+ </span>
41
  </div>
42
  </div>
43
  </header>
44
+ );
45
+ }
src/components/logo.tsx CHANGED
@@ -10,5 +10,5 @@ export function Logo() {
10
  >
11
  <path d="M50 0L100 50H50L0 0H50Z" fill="currentColor" />
12
  </svg>
13
- )
14
  }
 
10
  >
11
  <path d="M50 0L100 50H50L0 0H50Z" fill="currentColor" />
12
  </svg>
13
+ );
14
  }
src/components/model-selector.tsx CHANGED
@@ -1,35 +1,38 @@
1
- "use client"
2
 
3
- import { useState, useEffect, useRef } from 'react'
4
- import { MODEL_CONFIG_CODE_GENERATION } from '@/lib/constants'
5
- import { useModel } from '@/lib/contexts/model-context'
6
- import { ChevronDown } from 'lucide-react'
7
 
8
  export function ModelSelector() {
9
- const { selectedModelIndex, setSelectedModelIndex } = useModel()
10
- const [isOpen, setIsOpen] = useState(false)
11
- const selectorRef = useRef<HTMLDivElement>(null)
12
 
13
  const handleSelect = (index: number) => {
14
- setSelectedModelIndex(index)
15
- setIsOpen(false)
16
- }
17
 
18
  useEffect(() => {
19
  const handleClickOutside = (event: MouseEvent) => {
20
- if (selectorRef.current && !selectorRef.current.contains(event.target as Node)) {
21
- setIsOpen(false)
 
 
 
22
  }
23
- }
24
 
25
  if (isOpen) {
26
- document.addEventListener('mousedown', handleClickOutside)
27
  }
28
 
29
  return () => {
30
- document.removeEventListener('mousedown', handleClickOutside)
31
- }
32
- }, [isOpen])
33
 
34
  return (
35
  <div className="relative inline-block text-left" ref={selectorRef}>
@@ -42,13 +45,18 @@ export function ModelSelector() {
42
  border border-novita-gray/30 hover:border-novita-gray/60
43
  bg-novita-gray/5 hover:bg-novita-gray/20
44
  transition-all duration-200 ease-in-out
45
- ${isOpen ? 'border-novita-gray/60 bg-novita-gray/20' : ''}
46
  `}
47
  >
48
- <span className="text-sm">{MODEL_CONFIG_CODE_GENERATION[selectedModelIndex]?.id || 'Select model'}</span>
49
- <ChevronDown className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'transform rotate-180' : ''}`} />
 
 
 
 
 
50
  </div>
51
-
52
  {isOpen && (
53
  <div className="absolute z-40 mt-1 w-full origin-top-right rounded-md bg-novita-dark border border-novita-gray/20 shadow-lg">
54
  <div className="py-1">
@@ -58,7 +66,7 @@ export function ModelSelector() {
58
  onClick={() => handleSelect(index)}
59
  className={`
60
  px-4 py-2 text-xs cursor-pointer hover:bg-novita-gray/20
61
- ${selectedModelIndex === index ? 'text-white bg-novita-gray/40' : 'text-white/70'}
62
  `}
63
  >
64
  {model.id}
@@ -68,5 +76,5 @@ export function ModelSelector() {
68
  </div>
69
  )}
70
  </div>
71
- )
72
- }
 
1
+ "use client";
2
 
3
+ import { useState, useEffect, useRef } from "react";
4
+ import { MODEL_CONFIG_CODE_GENERATION } from "@/lib/constants";
5
+ import { useModel } from "@/lib/contexts/model-context";
6
+ import { ChevronDown } from "lucide-react";
7
 
8
  export function ModelSelector() {
9
+ const { selectedModelIndex, setSelectedModelIndex } = useModel();
10
+ const [isOpen, setIsOpen] = useState(false);
11
+ const selectorRef = useRef<HTMLDivElement>(null);
12
 
13
  const handleSelect = (index: number) => {
14
+ setSelectedModelIndex(index);
15
+ setIsOpen(false);
16
+ };
17
 
18
  useEffect(() => {
19
  const handleClickOutside = (event: MouseEvent) => {
20
+ if (
21
+ selectorRef.current &&
22
+ !selectorRef.current.contains(event.target as Node)
23
+ ) {
24
+ setIsOpen(false);
25
  }
26
+ };
27
 
28
  if (isOpen) {
29
+ document.addEventListener("mousedown", handleClickOutside);
30
  }
31
 
32
  return () => {
33
+ document.removeEventListener("mousedown", handleClickOutside);
34
+ };
35
+ }, [isOpen]);
36
 
37
  return (
38
  <div className="relative inline-block text-left" ref={selectorRef}>
 
45
  border border-novita-gray/30 hover:border-novita-gray/60
46
  bg-novita-gray/5 hover:bg-novita-gray/20
47
  transition-all duration-200 ease-in-out
48
+ ${isOpen ? "border-novita-gray/60 bg-novita-gray/20" : ""}
49
  `}
50
  >
51
+ <span className="text-sm">
52
+ {MODEL_CONFIG_CODE_GENERATION[selectedModelIndex]?.id ||
53
+ "Select model"}
54
+ </span>
55
+ <ChevronDown
56
+ className={`w-4 h-4 transition-transform duration-200 ${isOpen ? "transform rotate-180" : ""}`}
57
+ />
58
  </div>
59
+
60
  {isOpen && (
61
  <div className="absolute z-40 mt-1 w-full origin-top-right rounded-md bg-novita-dark border border-novita-gray/20 shadow-lg">
62
  <div className="py-1">
 
66
  onClick={() => handleSelect(index)}
67
  className={`
68
  px-4 py-2 text-xs cursor-pointer hover:bg-novita-gray/20
69
+ ${selectedModelIndex === index ? "text-white bg-novita-gray/40" : "text-white/70"}
70
  `}
71
  >
72
  {model.id}
 
76
  </div>
77
  )}
78
  </div>
79
+ );
80
+ }
src/components/preview.tsx CHANGED
@@ -1,15 +1,26 @@
1
- "use client"
2
-
3
- import { useState, forwardRef, useImperativeHandle, useEffect, useRef } from "react"
4
- import { DEFAULT_HTML } from "@/lib/constants"
5
- import { PreviewRef } from "@/lib/types"
6
- import { MinimizeIcon, MaximizeIcon, DownloadIcon, RefreshIcon } from "./ui/icons"
7
- import { useModel } from "@/lib/contexts/model-context"
8
- import { Loader2, Share2 } from "lucide-react"
9
- import { cn } from "@/lib/utils"
10
- import { generateShareLink } from "@/lib/sharelink"
11
- import { ShareDialog } from "./share-dialog"
12
- import posthog from 'posthog-js'
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  interface PreviewProps {
15
  initialHtml?: string;
@@ -21,8 +32,15 @@ interface PreviewProps {
21
  }
22
 
23
  export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
24
- { initialHtml, onCodeChange, onAuthErrorChange, onLoadingChange, onErrorChange, currentVersion },
25
- ref
 
 
 
 
 
 
 
26
  ) {
27
  const [html, setHtml] = useState<string>(initialHtml || "");
28
  const [isFullscreen, setIsFullscreen] = useState(false);
@@ -53,9 +71,13 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
53
  }, [error, onErrorChange]);
54
 
55
  useImperativeHandle(ref, () => ({
56
- generateCode: async (prompt: string, colors: string[] = [], previousPrompt?: string) => {
 
 
 
 
57
  await generateCode(prompt, colors, previousPrompt);
58
- }
59
  }));
60
 
61
  const toggleFullscreen = () => {
@@ -64,31 +86,35 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
64
 
65
  const partialUpdate = (htmlStr: string) => {
66
  const parser = new DOMParser();
67
- const partialDoc = parser.parseFromString(htmlStr, 'text/html');
68
- const iframe = document.querySelector('iframe');
69
  if (!iframe || !iframe.contentDocument) return;
70
-
71
  const iframeContainer = iframe.contentDocument;
72
  if (iframeContainer?.body && iframeContainer) {
73
  iframeContainer.body.innerHTML = partialDoc.body?.innerHTML;
74
  }
75
  if (renderCount.current % 10 === 0 && !headUpdated.current) {
76
  setHtml(htmlStr);
77
- if (htmlStr.includes('</head>')) {
78
  setTimeout(() => {
79
  headUpdated.current = true;
80
  }, 1000);
81
  }
82
  }
83
  renderCount.current++;
84
- }
85
 
86
  const downloadHtml = () => {
87
  if (!html) return;
88
 
89
  // Get current version and generate filename
90
  // If we have a currentVersion, use it; otherwise omit version part
91
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
 
 
 
 
92
 
93
  // Load current version from localStorage if we have an ID
94
  let versionLabel = "";
@@ -99,9 +125,9 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
99
  // Format the filename with or without version
100
  const filename = `novita-anysite-generated${versionLabel}-${timestamp}.html`;
101
 
102
- const blob = new Blob([html], { type: 'text/html' });
103
  const url = window.URL.createObjectURL(blob);
104
- const a = document.createElement('a');
105
  a.href = url;
106
  a.download = filename;
107
  document.body.appendChild(a);
@@ -112,10 +138,14 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
112
 
113
  const refreshPreview = () => {
114
  if (!html) return;
115
- setRefreshKey(prev => prev + 1);
116
  };
117
 
118
- const generateCode = async (prompt: string, colors: string[] = [], previousPrompt?: string) => {
 
 
 
 
119
  setLoading(true);
120
  renderCount.current = 0;
121
  headUpdated.current = false;
@@ -132,30 +162,34 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
132
  setHtml("");
133
 
134
  // Initialize generated code variable at function scope so it's accessible in finally block
135
- let generatedCode = '';
136
 
137
  try {
138
  // Only include html in the request if it's not DEFAULT_HTML
139
  const isDefaultHtml = initialHtml === DEFAULT_HTML;
140
 
141
- posthog.capture("Generate code", {"model": selectedModelId});
142
 
143
- const response = await fetch('/api/generate-code', {
144
- method: 'POST',
145
  headers: {
146
- 'Content-Type': 'application/json',
147
  },
148
  body: JSON.stringify({
149
  prompt,
150
  html: isDefaultHtml ? undefined : html,
151
  previousPrompt: isDefaultHtml ? undefined : previousPrompt,
152
  colors,
153
- modelId: selectedModelId
154
  }),
155
  });
156
 
157
  if (!response.ok) {
158
- posthog.capture("Generate code", {"type": "failed", "model": selectedModelId, "status": response.status});
 
 
 
 
159
  // Check specifically for 401 error (authentication required)
160
  if (response.status === 401 || response.status === 403) {
161
  try {
@@ -165,7 +199,7 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
165
  if (onAuthErrorChange) {
166
  onAuthErrorChange(true);
167
  }
168
- throw new Error('Signing in to Hugging Face is required.');
169
  }
170
  } catch (e) {
171
  // Fall back to default auth error handling if JSON parsing fails
@@ -173,12 +207,12 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
173
  if (onAuthErrorChange) {
174
  onAuthErrorChange(true);
175
  }
176
- throw new Error('Signing in to Hugging Face is required.');
177
  }
178
  }
179
 
180
  const errorData = await response.json();
181
- throw new Error(errorData.message || 'Failed to generate code');
182
  }
183
 
184
  // Handle streaming response
@@ -190,11 +224,11 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
190
  while (true) {
191
  const { done, value } = await reader.read();
192
  if (done) {
193
- if (!generatedCode.includes('</html>')) {
194
- generatedCode += '</html>';
195
  }
196
  const finalCode = generatedCode.match(
197
- /<!DOCTYPE html>[\s\S]*<\/html>/
198
  )?.[0];
199
  if (finalCode) {
200
  // Update state with the final code
@@ -220,7 +254,7 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
220
  // Try to parse as JSON
221
  parsedChunk = JSON.parse(chunkText);
222
  } catch (parseError) {
223
- appended = true
224
  // If JSON parsing fails, treat it as plain text (backwards compatibility)
225
  generatedCode += chunkText;
226
  }
@@ -252,13 +286,18 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
252
  }
253
  }
254
  } catch (err) {
255
- const errorMessage = (err as Error).message || 'An error occurred while generating code';
256
- posthog.capture("Generate code", {"type": "failed", "model": selectedModelId, "error": errorMessage});
 
 
 
 
 
257
  setError(errorMessage);
258
  if (onErrorChange) {
259
  onErrorChange(errorMessage);
260
  }
261
- console.error('Error generating code:', err);
262
  } finally {
263
  setLoading(false);
264
  if (onLoadingChange) {
@@ -281,12 +320,12 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
281
  setShareUrl(uploadedUrl);
282
  setShareDialogOpen(true);
283
  } catch (err) {
284
- const errorMessage = (err as Error).message || 'Failed to share HTML';
285
  setError(errorMessage);
286
  if (onErrorChange) {
287
  onErrorChange(errorMessage);
288
  }
289
- console.error('Error sharing HTML:', err);
290
  } finally {
291
  setIsSharing(false);
292
  }
@@ -300,14 +339,16 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
300
  };
301
 
302
  return (
303
- <div className={`${isFullscreen ? 'fixed inset-0 z-10 bg-novita-dark' : 'h-full'} p-4 pl-2`}>
304
- { isPartialGenerating && (
 
 
305
  <div className="w-full bg-slate-50 border-b border-slate-200 py-2 px-4">
306
  <div className="container mx-auto flex items-center justify-center">
307
- <div className="flex items-center space-x-2 text-slate-700">
308
- <Loader2 className="h-4 w-4 animate-spin" />
309
- <span className="text-sm font-medium">building...</span>
310
- </div>
311
  </div>
312
  </div>
313
  )}
@@ -325,7 +366,7 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
325
  ) : (
326
  <Share2 className="h-3 w-3 mr-2" />
327
  )}
328
- {isSharing ? 'Sharing...' : 'Share Link'}
329
  </button>
330
 
331
  <button
@@ -347,8 +388,8 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
347
  <button
348
  onClick={toggleFullscreen}
349
  className="bg-novita-gray/90 text-white p-2 rounded-md shadow-md hover:bg-novita-gray/70 transition-colors flex items-center justify-center"
350
- aria-label={isFullscreen ? 'Exit Fullscreen' : 'Full Screen'}
351
- title={isFullscreen ? 'Exit Fullscreen' : 'Full Screen'}
352
  >
353
  {isFullscreen ? <MinimizeIcon /> : <MaximizeIcon />}
354
  </button>
@@ -363,11 +404,11 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
363
  />
364
  </div>
365
 
366
- <ShareDialog
367
  open={shareDialogOpen}
368
  onOpenChange={handleShareDialogClose}
369
  shareUrl={shareUrl}
370
  />
371
  </div>
372
- )
373
  });
 
1
+ "use client";
2
+
3
+ import {
4
+ useState,
5
+ forwardRef,
6
+ useImperativeHandle,
7
+ useEffect,
8
+ useRef,
9
+ } from "react";
10
+ import { DEFAULT_HTML } from "@/lib/constants";
11
+ import { PreviewRef } from "@/lib/types";
12
+ import {
13
+ MinimizeIcon,
14
+ MaximizeIcon,
15
+ DownloadIcon,
16
+ RefreshIcon,
17
+ } from "./ui/icons";
18
+ import { useModel } from "@/lib/contexts/model-context";
19
+ import { Loader2, Share2 } from "lucide-react";
20
+ import { cn } from "@/lib/utils";
21
+ import { generateShareLink } from "@/lib/sharelink";
22
+ import { ShareDialog } from "./share-dialog";
23
+ import posthog from "posthog-js";
24
 
25
  interface PreviewProps {
26
  initialHtml?: string;
 
32
  }
33
 
34
  export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
35
+ {
36
+ initialHtml,
37
+ onCodeChange,
38
+ onAuthErrorChange,
39
+ onLoadingChange,
40
+ onErrorChange,
41
+ currentVersion,
42
+ },
43
+ ref,
44
  ) {
45
  const [html, setHtml] = useState<string>(initialHtml || "");
46
  const [isFullscreen, setIsFullscreen] = useState(false);
 
71
  }, [error, onErrorChange]);
72
 
73
  useImperativeHandle(ref, () => ({
74
+ generateCode: async (
75
+ prompt: string,
76
+ colors: string[] = [],
77
+ previousPrompt?: string,
78
+ ) => {
79
  await generateCode(prompt, colors, previousPrompt);
80
+ },
81
  }));
82
 
83
  const toggleFullscreen = () => {
 
86
 
87
  const partialUpdate = (htmlStr: string) => {
88
  const parser = new DOMParser();
89
+ const partialDoc = parser.parseFromString(htmlStr, "text/html");
90
+ const iframe = document.querySelector("iframe");
91
  if (!iframe || !iframe.contentDocument) return;
92
+
93
  const iframeContainer = iframe.contentDocument;
94
  if (iframeContainer?.body && iframeContainer) {
95
  iframeContainer.body.innerHTML = partialDoc.body?.innerHTML;
96
  }
97
  if (renderCount.current % 10 === 0 && !headUpdated.current) {
98
  setHtml(htmlStr);
99
+ if (htmlStr.includes("</head>")) {
100
  setTimeout(() => {
101
  headUpdated.current = true;
102
  }, 1000);
103
  }
104
  }
105
  renderCount.current++;
106
+ };
107
 
108
  const downloadHtml = () => {
109
  if (!html) return;
110
 
111
  // Get current version and generate filename
112
  // If we have a currentVersion, use it; otherwise omit version part
113
+ const timestamp = new Date()
114
+ .toISOString()
115
+ .replace(/[:.]/g, "-")
116
+ .replace("T", "_")
117
+ .slice(0, 19);
118
 
119
  // Load current version from localStorage if we have an ID
120
  let versionLabel = "";
 
125
  // Format the filename with or without version
126
  const filename = `novita-anysite-generated${versionLabel}-${timestamp}.html`;
127
 
128
+ const blob = new Blob([html], { type: "text/html" });
129
  const url = window.URL.createObjectURL(blob);
130
+ const a = document.createElement("a");
131
  a.href = url;
132
  a.download = filename;
133
  document.body.appendChild(a);
 
138
 
139
  const refreshPreview = () => {
140
  if (!html) return;
141
+ setRefreshKey((prev) => prev + 1);
142
  };
143
 
144
+ const generateCode = async (
145
+ prompt: string,
146
+ colors: string[] = [],
147
+ previousPrompt?: string,
148
+ ) => {
149
  setLoading(true);
150
  renderCount.current = 0;
151
  headUpdated.current = false;
 
162
  setHtml("");
163
 
164
  // Initialize generated code variable at function scope so it's accessible in finally block
165
+ let generatedCode = "";
166
 
167
  try {
168
  // Only include html in the request if it's not DEFAULT_HTML
169
  const isDefaultHtml = initialHtml === DEFAULT_HTML;
170
 
171
+ posthog.capture("Generate code", { model: selectedModelId });
172
 
173
+ const response = await fetch("/api/generate-code", {
174
+ method: "POST",
175
  headers: {
176
+ "Content-Type": "application/json",
177
  },
178
  body: JSON.stringify({
179
  prompt,
180
  html: isDefaultHtml ? undefined : html,
181
  previousPrompt: isDefaultHtml ? undefined : previousPrompt,
182
  colors,
183
+ modelId: selectedModelId,
184
  }),
185
  });
186
 
187
  if (!response.ok) {
188
+ posthog.capture("Generate code", {
189
+ type: "failed",
190
+ model: selectedModelId,
191
+ status: response.status,
192
+ });
193
  // Check specifically for 401 error (authentication required)
194
  if (response.status === 401 || response.status === 403) {
195
  try {
 
199
  if (onAuthErrorChange) {
200
  onAuthErrorChange(true);
201
  }
202
+ throw new Error("Signing in to Hugging Face is required.");
203
  }
204
  } catch (e) {
205
  // Fall back to default auth error handling if JSON parsing fails
 
207
  if (onAuthErrorChange) {
208
  onAuthErrorChange(true);
209
  }
210
+ throw new Error("Signing in to Hugging Face is required.");
211
  }
212
  }
213
 
214
  const errorData = await response.json();
215
+ throw new Error(errorData.message || "Failed to generate code");
216
  }
217
 
218
  // Handle streaming response
 
224
  while (true) {
225
  const { done, value } = await reader.read();
226
  if (done) {
227
+ if (!generatedCode.includes("</html>")) {
228
+ generatedCode += "</html>";
229
  }
230
  const finalCode = generatedCode.match(
231
+ /<!DOCTYPE html>[\s\S]*<\/html>/,
232
  )?.[0];
233
  if (finalCode) {
234
  // Update state with the final code
 
254
  // Try to parse as JSON
255
  parsedChunk = JSON.parse(chunkText);
256
  } catch (parseError) {
257
+ appended = true;
258
  // If JSON parsing fails, treat it as plain text (backwards compatibility)
259
  generatedCode += chunkText;
260
  }
 
286
  }
287
  }
288
  } catch (err) {
289
+ const errorMessage =
290
+ (err as Error).message || "An error occurred while generating code";
291
+ posthog.capture("Generate code", {
292
+ type: "failed",
293
+ model: selectedModelId,
294
+ error: errorMessage,
295
+ });
296
  setError(errorMessage);
297
  if (onErrorChange) {
298
  onErrorChange(errorMessage);
299
  }
300
+ console.error("Error generating code:", err);
301
  } finally {
302
  setLoading(false);
303
  if (onLoadingChange) {
 
320
  setShareUrl(uploadedUrl);
321
  setShareDialogOpen(true);
322
  } catch (err) {
323
+ const errorMessage = (err as Error).message || "Failed to share HTML";
324
  setError(errorMessage);
325
  if (onErrorChange) {
326
  onErrorChange(errorMessage);
327
  }
328
+ console.error("Error sharing HTML:", err);
329
  } finally {
330
  setIsSharing(false);
331
  }
 
339
  };
340
 
341
  return (
342
+ <div
343
+ className={`${isFullscreen ? "fixed inset-0 z-10 bg-novita-dark" : "h-full"} p-4 pl-2`}
344
+ >
345
+ {isPartialGenerating && (
346
  <div className="w-full bg-slate-50 border-b border-slate-200 py-2 px-4">
347
  <div className="container mx-auto flex items-center justify-center">
348
+ <div className="flex items-center space-x-2 text-slate-700">
349
+ <Loader2 className="h-4 w-4 animate-spin" />
350
+ <span className="text-sm font-medium">building...</span>
351
+ </div>
352
  </div>
353
  </div>
354
  )}
 
366
  ) : (
367
  <Share2 className="h-3 w-3 mr-2" />
368
  )}
369
+ {isSharing ? "Sharing..." : "Share Link"}
370
  </button>
371
 
372
  <button
 
388
  <button
389
  onClick={toggleFullscreen}
390
  className="bg-novita-gray/90 text-white p-2 rounded-md shadow-md hover:bg-novita-gray/70 transition-colors flex items-center justify-center"
391
+ aria-label={isFullscreen ? "Exit Fullscreen" : "Full Screen"}
392
+ title={isFullscreen ? "Exit Fullscreen" : "Full Screen"}
393
  >
394
  {isFullscreen ? <MinimizeIcon /> : <MaximizeIcon />}
395
  </button>
 
404
  />
405
  </div>
406
 
407
+ <ShareDialog
408
  open={shareDialogOpen}
409
  onOpenChange={handleShareDialogClose}
410
  shareUrl={shareUrl}
411
  />
412
  </div>
413
+ );
414
  });
src/components/prompt-input.tsx CHANGED
@@ -1,17 +1,22 @@
1
- "use client"
2
 
3
- import type React from "react"
4
 
5
- import { useState, useEffect } from "react"
6
- import { Wand2, ArrowUp, Loader2, Maximize2, Minimize2 } from "lucide-react"
7
- import { Button } from "@/components/ui/button"
8
- import { Textarea } from "@/components/ui/textarea"
9
- import { ColorPanel } from "./color-panel"
10
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
11
- import { FullscreenToggle } from "./ui/fullscreen-toggle"
12
- import { AuthErrorPopup } from "./auth-error-popup"
13
- import { getInferenceToken } from "@/lib/auth"
14
- import posthog from 'posthog-js'
 
 
 
 
 
15
 
16
  interface PromptInputProps {
17
  onSubmit: (prompt: string, colors: string[]) => Promise<void>;
@@ -24,7 +29,7 @@ export function PromptInput({
24
  onSubmit,
25
  isLoading = false,
26
  initialPrompt = "",
27
- onImproveError
28
  }: PromptInputProps) {
29
  const [prompt, setPrompt] = useState(initialPrompt);
30
  const [isImprovingPrompt, setIsImprovingPrompt] = useState(false);
@@ -50,7 +55,9 @@ export function PromptInput({
50
  if (token) {
51
  return true;
52
  }
53
- const canBypass = await fetch("/api/auth/check-bypass").then(res => res.json());
 
 
54
  if (!canBypass) {
55
  throw new Error("Authentication required");
56
  }
@@ -59,11 +66,11 @@ export function PromptInput({
59
  setShowAuthError(true);
60
  return false;
61
  }
62
- }
63
 
64
  const handleSubmit = async (e: React.FormEvent) => {
65
  e.preventDefault();
66
- if (prompt.trim() === '' || isLoading) return;
67
 
68
  // Check for authentication
69
  const isAuthenticated = await checkAuth();
@@ -72,10 +79,10 @@ export function PromptInput({
72
  // Clear any previous errors
73
  setImproveError(null);
74
  await onSubmit(prompt, selectedColors);
75
- }
76
 
77
  const improvePrompt = async () => {
78
- if (prompt.trim() === '' || isImprovingPrompt || isLoading) return;
79
 
80
  // Check for authentication
81
  const isAuthenticated = await checkAuth();
@@ -95,22 +102,27 @@ export function PromptInput({
95
  "Content-Type": "application/json",
96
  },
97
  body: JSON.stringify({ prompt: prompt.trim() }),
98
- })
99
 
100
  if (!response.ok) {
101
- posthog.capture("Improve prompt", {"type": "failed", "status": response.status});
 
 
 
102
 
103
  // Handle auth error with openLogin flag
104
  if (response.status === 401) {
105
  const errorData = await response.json();
106
  if (errorData.openLogin) {
107
  setShowAuthError(true);
108
- throw new Error('Authentication required');
109
  }
110
  }
111
-
112
  const errorText = await response.text();
113
- throw new Error(errorText || `Failed to improve prompt (${response.status})`);
 
 
114
  }
115
 
116
  if (!response.body) {
@@ -135,37 +147,41 @@ export function PromptInput({
135
  } catch (parseError) {
136
  appended = true;
137
  // If JSON parsing fails, treat it as plain text (backwards compatibility)
138
- improvedPrompt += chunkText
139
- setPrompt(improvedPrompt)
140
  }
141
  if (parsedChunk && parsedChunk.type === "error") {
142
  throw new Error(parsedChunk.message || "An error occurred");
143
  } else if (!appended) {
144
- improvedPrompt += chunkText
145
- setPrompt(improvedPrompt)
146
  }
147
  }
148
  } catch (error) {
149
- posthog.capture("Improve prompt", {"type": "failed", "error": error});
150
- console.error("Error improving prompt:", error)
151
- setImproveError(error instanceof Error ? error.message : "Failed to improve prompt")
 
 
152
  } finally {
153
- setIsImprovingPrompt(false)
154
  }
155
- }
156
 
157
  const toggleFullScreen = () => {
158
- setIsFullScreen(!isFullScreen)
159
- }
160
 
161
  const handleColorsChange = (colors: string[]) => {
162
  setSelectedColors(colors);
163
- }
164
 
165
- const isPromptTooShort = prompt.length < 10
166
 
167
  return (
168
- <div className={`border-t border-novita-gray/20 p-4 relative transition-all duration-300 ease-in-out ${isFullScreen ? 'h-[50vh]' : ''}`}>
 
 
169
  <div className="absolute top-1 right-1 z-10">
170
  <FullscreenToggle
171
  isFullScreen={isFullScreen}
@@ -173,17 +189,17 @@ export function PromptInput({
173
  />
174
  </div>
175
  <form onSubmit={handleSubmit} className="flex flex-col gap-4 h-full">
176
- <div className={`relative ${isFullScreen ? 'h-full' : ''}`}>
177
  <ColorPanel onColorsChange={handleColorsChange} />
178
  <Textarea
179
  value={prompt}
180
  onChange={(e) => {
181
- setPrompt(e.target.value)
182
  // Clear error when user types
183
- if (improveError) setImproveError(null)
184
  }}
185
  placeholder="Describe what site you want to build. E.g., Build a snake game"
186
- className={`min-h-24 pr-20 pt-12 bg-novita-gray/20 border-novita-gray/30 text-white placeholder:text-novita-gray/70 resize-none ${isFullScreen ? 'h-full' : ''}`}
187
  disabled={isLoading || isImprovingPrompt}
188
  />
189
  <div className="absolute bottom-3 right-3 flex gap-2">
@@ -196,7 +212,9 @@ export function PromptInput({
196
  size="icon"
197
  variant="outline"
198
  className="h-8 w-8 bg-novita-gray/20 border-novita-gray/30 text-white hover:bg-novita-gray/30"
199
- disabled={isPromptTooShort || isLoading || isImprovingPrompt}
 
 
200
  onClick={improvePrompt}
201
  >
202
  {isImprovingPrompt ? (
@@ -210,7 +228,7 @@ export function PromptInput({
210
  </TooltipTrigger>
211
  {isPromptTooShort && (
212
  <TooltipContent className="bg-novita-gray text-white">
213
- <p>Your prompt is too simple, we can't improve it.</p>
214
  </TooltipContent>
215
  )}
216
  </Tooltip>
@@ -231,11 +249,11 @@ export function PromptInput({
231
  </div>
232
  </div>
233
  </form>
234
-
235
- <AuthErrorPopup
236
- show={showAuthError}
237
- onClose={() => setShowAuthError(false)}
238
  />
239
  </div>
240
- )
241
  }
 
1
+ "use client";
2
 
3
+ import type React from "react";
4
 
5
+ import { useState, useEffect } from "react";
6
+ import { Wand2, ArrowUp, Loader2, Maximize2, Minimize2 } from "lucide-react";
7
+ import { Button } from "@/components/ui/button";
8
+ import { Textarea } from "@/components/ui/textarea";
9
+ import { ColorPanel } from "./color-panel";
10
+ import {
11
+ Tooltip,
12
+ TooltipContent,
13
+ TooltipProvider,
14
+ TooltipTrigger,
15
+ } from "@/components/ui/tooltip";
16
+ import { FullscreenToggle } from "./ui/fullscreen-toggle";
17
+ import { AuthErrorPopup } from "./auth-error-popup";
18
+ import { getInferenceToken } from "@/lib/auth";
19
+ import posthog from "posthog-js";
20
 
21
  interface PromptInputProps {
22
  onSubmit: (prompt: string, colors: string[]) => Promise<void>;
 
29
  onSubmit,
30
  isLoading = false,
31
  initialPrompt = "",
32
+ onImproveError,
33
  }: PromptInputProps) {
34
  const [prompt, setPrompt] = useState(initialPrompt);
35
  const [isImprovingPrompt, setIsImprovingPrompt] = useState(false);
 
55
  if (token) {
56
  return true;
57
  }
58
+ const canBypass = await fetch("/api/auth/check-bypass").then((res) =>
59
+ res.json(),
60
+ );
61
  if (!canBypass) {
62
  throw new Error("Authentication required");
63
  }
 
66
  setShowAuthError(true);
67
  return false;
68
  }
69
+ };
70
 
71
  const handleSubmit = async (e: React.FormEvent) => {
72
  e.preventDefault();
73
+ if (prompt.trim() === "" || isLoading) return;
74
 
75
  // Check for authentication
76
  const isAuthenticated = await checkAuth();
 
79
  // Clear any previous errors
80
  setImproveError(null);
81
  await onSubmit(prompt, selectedColors);
82
+ };
83
 
84
  const improvePrompt = async () => {
85
+ if (prompt.trim() === "" || isImprovingPrompt || isLoading) return;
86
 
87
  // Check for authentication
88
  const isAuthenticated = await checkAuth();
 
102
  "Content-Type": "application/json",
103
  },
104
  body: JSON.stringify({ prompt: prompt.trim() }),
105
+ });
106
 
107
  if (!response.ok) {
108
+ posthog.capture("Improve prompt", {
109
+ type: "failed",
110
+ status: response.status,
111
+ });
112
 
113
  // Handle auth error with openLogin flag
114
  if (response.status === 401) {
115
  const errorData = await response.json();
116
  if (errorData.openLogin) {
117
  setShowAuthError(true);
118
+ throw new Error("Authentication required");
119
  }
120
  }
121
+
122
  const errorText = await response.text();
123
+ throw new Error(
124
+ errorText || `Failed to improve prompt (${response.status})`,
125
+ );
126
  }
127
 
128
  if (!response.body) {
 
147
  } catch (parseError) {
148
  appended = true;
149
  // If JSON parsing fails, treat it as plain text (backwards compatibility)
150
+ improvedPrompt += chunkText;
151
+ setPrompt(improvedPrompt);
152
  }
153
  if (parsedChunk && parsedChunk.type === "error") {
154
  throw new Error(parsedChunk.message || "An error occurred");
155
  } else if (!appended) {
156
+ improvedPrompt += chunkText;
157
+ setPrompt(improvedPrompt);
158
  }
159
  }
160
  } catch (error) {
161
+ posthog.capture("Improve prompt", { type: "failed", error: error });
162
+ console.error("Error improving prompt:", error);
163
+ setImproveError(
164
+ error instanceof Error ? error.message : "Failed to improve prompt",
165
+ );
166
  } finally {
167
+ setIsImprovingPrompt(false);
168
  }
169
+ };
170
 
171
  const toggleFullScreen = () => {
172
+ setIsFullScreen(!isFullScreen);
173
+ };
174
 
175
  const handleColorsChange = (colors: string[]) => {
176
  setSelectedColors(colors);
177
+ };
178
 
179
+ const isPromptTooShort = prompt.length < 10;
180
 
181
  return (
182
+ <div
183
+ className={`border-t border-novita-gray/20 p-4 relative transition-all duration-300 ease-in-out ${isFullScreen ? "h-[50vh]" : ""}`}
184
+ >
185
  <div className="absolute top-1 right-1 z-10">
186
  <FullscreenToggle
187
  isFullScreen={isFullScreen}
 
189
  />
190
  </div>
191
  <form onSubmit={handleSubmit} className="flex flex-col gap-4 h-full">
192
+ <div className={`relative ${isFullScreen ? "h-full" : ""}`}>
193
  <ColorPanel onColorsChange={handleColorsChange} />
194
  <Textarea
195
  value={prompt}
196
  onChange={(e) => {
197
+ setPrompt(e.target.value);
198
  // Clear error when user types
199
+ if (improveError) setImproveError(null);
200
  }}
201
  placeholder="Describe what site you want to build. E.g., Build a snake game"
202
+ className={`min-h-24 pr-20 pt-12 bg-novita-gray/20 border-novita-gray/30 text-white placeholder:text-novita-gray/70 resize-none ${isFullScreen ? "h-full" : ""}`}
203
  disabled={isLoading || isImprovingPrompt}
204
  />
205
  <div className="absolute bottom-3 right-3 flex gap-2">
 
212
  size="icon"
213
  variant="outline"
214
  className="h-8 w-8 bg-novita-gray/20 border-novita-gray/30 text-white hover:bg-novita-gray/30"
215
+ disabled={
216
+ isPromptTooShort || isLoading || isImprovingPrompt
217
+ }
218
  onClick={improvePrompt}
219
  >
220
  {isImprovingPrompt ? (
 
228
  </TooltipTrigger>
229
  {isPromptTooShort && (
230
  <TooltipContent className="bg-novita-gray text-white">
231
+ <p>Your prompt is too simple, we can&apos;t improve it.</p>
232
  </TooltipContent>
233
  )}
234
  </Tooltip>
 
249
  </div>
250
  </div>
251
  </form>
252
+
253
+ <AuthErrorPopup
254
+ show={showAuthError}
255
+ onClose={() => setShowAuthError(false)}
256
  />
257
  </div>
258
+ );
259
  }
src/components/share-dialog.tsx CHANGED
@@ -1,9 +1,15 @@
1
- "use client"
2
 
3
- import { useState } from "react"
4
- import { Copy, Check, AlertTriangle } from "lucide-react"
5
- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "./ui/dialog"
6
- import { Button } from "./ui/button"
 
 
 
 
 
 
7
 
8
  interface ShareDialogProps {
9
  open: boolean;
@@ -11,7 +17,11 @@ interface ShareDialogProps {
11
  shareUrl: string;
12
  }
13
 
14
- export function ShareDialog({ open, onOpenChange, shareUrl }: ShareDialogProps) {
 
 
 
 
15
  const [copied, setCopied] = useState(false);
16
 
17
  const handleCopyLink = async () => {
@@ -20,7 +30,7 @@ export function ShareDialog({ open, onOpenChange, shareUrl }: ShareDialogProps)
20
  setCopied(true);
21
  setTimeout(() => setCopied(false), 2000);
22
  } catch (err) {
23
- console.error('Failed to copy link:', err);
24
  }
25
  };
26
 
@@ -35,11 +45,12 @@ export function ShareDialog({ open, onOpenChange, shareUrl }: ShareDialogProps)
35
  <DialogHeader>
36
  <DialogTitle>Share Your Creation</DialogTitle>
37
  </DialogHeader>
38
-
39
  <div className="flex items-start space-x-2 p-3 rounded-md">
40
  <AlertTriangle className="h-4 w-4 text-muted-foreground mt-0.5 flex-shrink-0" />
41
  <p className="text-sm text-muted-foreground">
42
- <strong>Note:</strong> This shared link is temporary and may not be permanently available.
 
43
  </p>
44
  </div>
45
 
@@ -55,9 +66,9 @@ export function ShareDialog({ open, onOpenChange, shareUrl }: ShareDialogProps)
55
  className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium 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"
56
  />
57
  </div>
58
- <Button
59
- type="button"
60
- size="sm"
61
  className="px-3"
62
  onClick={handleCopyLink}
63
  >
@@ -70,21 +81,14 @@ export function ShareDialog({ open, onOpenChange, shareUrl }: ShareDialogProps)
70
  </Button>
71
  </div>
72
  <div className="flex justify-end space-x-2">
73
- <Button
74
- type="button"
75
- variant="outline"
76
- onClick={handleClose}
77
- >
78
  Close
79
  </Button>
80
- <Button
81
- type="button"
82
- onClick={() => window.open(shareUrl, '_blank')}
83
- >
84
  Open Link
85
  </Button>
86
  </div>
87
  </DialogContent>
88
  </Dialog>
89
  );
90
- }
 
1
+ "use client";
2
 
3
+ import { useState } from "react";
4
+ import { Copy, Check, AlertTriangle } from "lucide-react";
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ DialogDescription,
11
+ } from "./ui/dialog";
12
+ import { Button } from "./ui/button";
13
 
14
  interface ShareDialogProps {
15
  open: boolean;
 
17
  shareUrl: string;
18
  }
19
 
20
+ export function ShareDialog({
21
+ open,
22
+ onOpenChange,
23
+ shareUrl,
24
+ }: ShareDialogProps) {
25
  const [copied, setCopied] = useState(false);
26
 
27
  const handleCopyLink = async () => {
 
30
  setCopied(true);
31
  setTimeout(() => setCopied(false), 2000);
32
  } catch (err) {
33
+ console.error("Failed to copy link:", err);
34
  }
35
  };
36
 
 
45
  <DialogHeader>
46
  <DialogTitle>Share Your Creation</DialogTitle>
47
  </DialogHeader>
48
+
49
  <div className="flex items-start space-x-2 p-3 rounded-md">
50
  <AlertTriangle className="h-4 w-4 text-muted-foreground mt-0.5 flex-shrink-0" />
51
  <p className="text-sm text-muted-foreground">
52
+ <strong>Note:</strong> This shared link is temporary and may not be
53
+ permanently available.
54
  </p>
55
  </div>
56
 
 
66
  className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium 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"
67
  />
68
  </div>
69
+ <Button
70
+ type="button"
71
+ size="sm"
72
  className="px-3"
73
  onClick={handleCopyLink}
74
  >
 
81
  </Button>
82
  </div>
83
  <div className="flex justify-end space-x-2">
84
+ <Button type="button" variant="outline" onClick={handleClose}>
 
 
 
 
85
  Close
86
  </Button>
87
+ <Button type="button" onClick={() => window.open(shareUrl, "_blank")}>
 
 
 
88
  Open Link
89
  </Button>
90
  </div>
91
  </DialogContent>
92
  </Dialog>
93
  );
94
+ }
src/components/theme-provider.tsx CHANGED
@@ -1,11 +1,11 @@
1
- 'use client'
2
 
3
- import * as React from 'react'
4
  import {
5
  ThemeProvider as NextThemesProvider,
6
  type ThemeProviderProps,
7
- } from 'next-themes'
8
 
9
  export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
10
- return <NextThemesProvider {...props}>{children}</NextThemesProvider>
11
  }
 
1
+ "use client";
2
 
3
+ import * as React from "react";
4
  import {
5
  ThemeProvider as NextThemesProvider,
6
  type ThemeProviderProps,
7
+ } from "next-themes";
8
 
9
  export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
10
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
11
  }
src/components/ui/accordion.tsx CHANGED
@@ -1,12 +1,12 @@
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>,
@@ -17,8 +17,8 @@ const AccordionItem = React.forwardRef<
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>,
@@ -29,7 +29,7 @@ const AccordionTrigger = React.forwardRef<
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
  >
@@ -37,8 +37,8 @@ const AccordionTrigger = React.forwardRef<
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>,
@@ -51,8 +51,8 @@ const AccordionContent = React.forwardRef<
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 }
 
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>,
 
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>,
 
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
  >
 
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>,
 
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 CHANGED
@@ -1,16 +1,16 @@
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>,
@@ -19,13 +19,13 @@ const AlertDialogOverlay = React.forwardRef<
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>,
@@ -37,13 +37,13 @@ const AlertDialogContent = React.forwardRef<
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,
@@ -52,12 +52,12 @@ const AlertDialogHeader = ({
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,
@@ -66,12 +66,12 @@ const AlertDialogFooter = ({
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>,
@@ -82,8 +82,8 @@ const AlertDialogTitle = React.forwardRef<
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>,
@@ -94,9 +94,9 @@ const AlertDialogDescription = React.forwardRef<
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>,
@@ -107,8 +107,8 @@ const AlertDialogAction = React.forwardRef<
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>,
@@ -119,12 +119,12 @@ const AlertDialogCancel = React.forwardRef<
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,
@@ -138,4 +138,4 @@ export {
138
  AlertDialogDescription,
139
  AlertDialogAction,
140
  AlertDialogCancel,
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>,
 
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>,
 
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,
 
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,
 
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>,
 
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>,
 
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>,
 
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>,
 
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,
 
138
  AlertDialogDescription,
139
  AlertDialogAction,
140
  AlertDialogCancel,
141
+ };
src/components/ui/alert.tsx CHANGED
@@ -1,7 +1,7 @@
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",
@@ -16,8 +16,8 @@ const alertVariants = cva(
16
  defaultVariants: {
17
  variant: "default",
18
  },
19
- }
20
- )
21
 
22
  const Alert = React.forwardRef<
23
  HTMLDivElement,
@@ -29,8 +29,8 @@ const Alert = React.forwardRef<
29
  className={cn(alertVariants({ variant }), className)}
30
  {...props}
31
  />
32
- ))
33
- Alert.displayName = "Alert"
34
 
35
  const AlertTitle = React.forwardRef<
36
  HTMLParagraphElement,
@@ -41,8 +41,8 @@ const AlertTitle = React.forwardRef<
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,
@@ -53,7 +53,7 @@ const AlertDescription = React.forwardRef<
53
  className={cn("text-sm [&_p]:leading-relaxed", className)}
54
  {...props}
55
  />
56
- ))
57
- AlertDescription.displayName = "AlertDescription"
58
 
59
- export { Alert, AlertTitle, AlertDescription }
 
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",
 
16
  defaultVariants: {
17
  variant: "default",
18
  },
19
+ },
20
+ );
21
 
22
  const Alert = React.forwardRef<
23
  HTMLDivElement,
 
29
  className={cn(alertVariants({ variant }), className)}
30
  {...props}
31
  />
32
+ ));
33
+ Alert.displayName = "Alert";
34
 
35
  const AlertTitle = React.forwardRef<
36
  HTMLParagraphElement,
 
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,
 
53
  className={cn("text-sm [&_p]:leading-relaxed", className)}
54
  {...props}
55
  />
56
+ ));
57
+ AlertDescription.displayName = "AlertDescription";
58
 
59
+ export { Alert, AlertTitle, AlertDescription };
src/components/ui/aspect-ratio.tsx CHANGED
@@ -1,7 +1,7 @@
1
- "use client"
2
 
3
- import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4
 
5
- const AspectRatio = AspectRatioPrimitive.Root
6
 
7
- export { AspectRatio }
 
1
+ "use client";
2
 
3
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
4
 
5
+ const AspectRatio = AspectRatioPrimitive.Root;
6
 
7
+ export { AspectRatio };
src/components/ui/avatar.tsx CHANGED
@@ -1,9 +1,9 @@
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>,
@@ -13,12 +13,12 @@ const Avatar = React.forwardRef<
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>,
@@ -29,8 +29,8 @@ const AvatarImage = React.forwardRef<
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>,
@@ -40,11 +40,11 @@ const AvatarFallback = React.forwardRef<
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 }
 
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>,
 
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>,
 
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>,
 
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 CHANGED
@@ -1,7 +1,7 @@
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",
@@ -20,8 +20,8 @@ const badgeVariants = cva(
20
  defaultVariants: {
21
  variant: "default",
22
  },
23
- }
24
- )
25
 
26
  export interface BadgeProps
27
  extends React.HTMLAttributes<HTMLDivElement>,
@@ -30,7 +30,7 @@ export interface BadgeProps
30
  function Badge({ className, variant, ...props }: BadgeProps) {
31
  return (
32
  <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
- )
34
  }
35
 
36
- export { Badge, badgeVariants }
 
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",
 
20
  defaultVariants: {
21
  variant: "default",
22
  },
23
+ },
24
+ );
25
 
26
  export interface BadgeProps
27
  extends React.HTMLAttributes<HTMLDivElement>,
 
30
  function Badge({ className, variant, ...props }: BadgeProps) {
31
  return (
32
  <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ );
34
  }
35
 
36
+ export { Badge, badgeVariants };
src/components/ui/breadcrumb.tsx CHANGED
@@ -1,16 +1,16 @@
1
- import * as React from "react"
2
- import { Slot } from "@radix-ui/react-slot"
3
- import { ChevronRight, MoreHorizontal } from "lucide-react"
4
 
5
- import { cn } from "@/lib/utils"
6
 
7
  const Breadcrumb = React.forwardRef<
8
  HTMLElement,
9
  React.ComponentPropsWithoutRef<"nav"> & {
10
- separator?: React.ReactNode
11
  }
12
- >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
13
- Breadcrumb.displayName = "Breadcrumb"
14
 
15
  const BreadcrumbList = React.forwardRef<
16
  HTMLOListElement,
@@ -20,12 +20,12 @@ const BreadcrumbList = React.forwardRef<
20
  ref={ref}
21
  className={cn(
22
  "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
23
- className
24
  )}
25
  {...props}
26
  />
27
- ))
28
- BreadcrumbList.displayName = "BreadcrumbList"
29
 
30
  const BreadcrumbItem = React.forwardRef<
31
  HTMLLIElement,
@@ -36,16 +36,16 @@ const BreadcrumbItem = React.forwardRef<
36
  className={cn("inline-flex items-center gap-1.5", className)}
37
  {...props}
38
  />
39
- ))
40
- BreadcrumbItem.displayName = "BreadcrumbItem"
41
 
42
  const BreadcrumbLink = React.forwardRef<
43
  HTMLAnchorElement,
44
  React.ComponentPropsWithoutRef<"a"> & {
45
- asChild?: boolean
46
  }
47
  >(({ asChild, className, ...props }, ref) => {
48
- const Comp = asChild ? Slot : "a"
49
 
50
  return (
51
  <Comp
@@ -53,9 +53,9 @@ const BreadcrumbLink = React.forwardRef<
53
  className={cn("transition-colors hover:text-foreground", className)}
54
  {...props}
55
  />
56
- )
57
- })
58
- BreadcrumbLink.displayName = "BreadcrumbLink"
59
 
60
  const BreadcrumbPage = React.forwardRef<
61
  HTMLSpanElement,
@@ -69,8 +69,8 @@ const BreadcrumbPage = React.forwardRef<
69
  className={cn("font-normal text-foreground", className)}
70
  {...props}
71
  />
72
- ))
73
- BreadcrumbPage.displayName = "BreadcrumbPage"
74
 
75
  const BreadcrumbSeparator = ({
76
  children,
@@ -85,8 +85,8 @@ const BreadcrumbSeparator = ({
85
  >
86
  {children ?? <ChevronRight />}
87
  </li>
88
- )
89
- BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90
 
91
  const BreadcrumbEllipsis = ({
92
  className,
@@ -101,8 +101,8 @@ const BreadcrumbEllipsis = ({
101
  <MoreHorizontal className="h-4 w-4" />
102
  <span className="sr-only">More</span>
103
  </span>
104
- )
105
- BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106
 
107
  export {
108
  Breadcrumb,
@@ -112,4 +112,4 @@ export {
112
  BreadcrumbPage,
113
  BreadcrumbSeparator,
114
  BreadcrumbEllipsis,
115
- }
 
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react";
4
 
5
+ import { cn } from "@/lib/utils";
6
 
7
  const Breadcrumb = React.forwardRef<
8
  HTMLElement,
9
  React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode;
11
  }
12
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
13
+ Breadcrumb.displayName = "Breadcrumb";
14
 
15
  const BreadcrumbList = React.forwardRef<
16
  HTMLOListElement,
 
20
  ref={ref}
21
  className={cn(
22
  "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
23
+ className,
24
  )}
25
  {...props}
26
  />
27
+ ));
28
+ BreadcrumbList.displayName = "BreadcrumbList";
29
 
30
  const BreadcrumbItem = React.forwardRef<
31
  HTMLLIElement,
 
36
  className={cn("inline-flex items-center gap-1.5", className)}
37
  {...props}
38
  />
39
+ ));
40
+ BreadcrumbItem.displayName = "BreadcrumbItem";
41
 
42
  const BreadcrumbLink = React.forwardRef<
43
  HTMLAnchorElement,
44
  React.ComponentPropsWithoutRef<"a"> & {
45
+ asChild?: boolean;
46
  }
47
  >(({ asChild, className, ...props }, ref) => {
48
+ const Comp = asChild ? Slot : "a";
49
 
50
  return (
51
  <Comp
 
53
  className={cn("transition-colors hover:text-foreground", className)}
54
  {...props}
55
  />
56
+ );
57
+ });
58
+ BreadcrumbLink.displayName = "BreadcrumbLink";
59
 
60
  const BreadcrumbPage = React.forwardRef<
61
  HTMLSpanElement,
 
69
  className={cn("font-normal text-foreground", className)}
70
  {...props}
71
  />
72
+ ));
73
+ BreadcrumbPage.displayName = "BreadcrumbPage";
74
 
75
  const BreadcrumbSeparator = ({
76
  children,
 
85
  >
86
  {children ?? <ChevronRight />}
87
  </li>
88
+ );
89
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
90
 
91
  const BreadcrumbEllipsis = ({
92
  className,
 
101
  <MoreHorizontal className="h-4 w-4" />
102
  <span className="sr-only">More</span>
103
  </span>
104
+ );
105
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
106
 
107
  export {
108
  Breadcrumb,
 
112
  BreadcrumbPage,
113
  BreadcrumbSeparator,
114
  BreadcrumbEllipsis,
115
+ };
src/components/ui/button.tsx CHANGED
@@ -1,8 +1,8 @@
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 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",
@@ -10,9 +10,12 @@ const buttonVariants = cva(
10
  variants: {
11
  variant: {
12
  default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
14
- outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
15
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
 
 
 
16
  ghost: "hover:bg-accent hover:text-accent-foreground",
17
  link: "text-primary underline-offset-4 hover:underline",
18
  },
@@ -28,20 +31,26 @@ const buttonVariants = cva(
28
  size: "default",
29
  },
30
  },
31
- )
32
 
33
  export interface ButtonProps
34
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
35
  VariantProps<typeof buttonVariants> {
36
- asChild?: boolean
37
  }
38
 
39
  const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
40
  ({ className, variant, size, asChild = false, ...props }, ref) => {
41
- const Comp = asChild ? Slot : "button"
42
- return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
 
 
 
 
 
 
43
  },
44
- )
45
- Button.displayName = "Button"
46
 
47
- export { Button, buttonVariants }
 
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 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",
 
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
  },
 
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 CHANGED
@@ -1,13 +1,13 @@
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,
@@ -27,7 +27,7 @@ function Calendar({
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",
@@ -39,7 +39,7 @@ function Calendar({
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:
@@ -59,8 +59,8 @@ function Calendar({
59
  }}
60
  {...props}
61
  />
62
- )
63
  }
64
- Calendar.displayName = "Calendar"
65
 
66
- export { Calendar }
 
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,
 
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",
 
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:
 
59
  }}
60
  {...props}
61
  />
62
+ );
63
  }
64
+ Calendar.displayName = "Calendar";
65
 
66
+ export { Calendar };
src/components/ui/card.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import * as React from "react"
2
 
3
- import { cn } from "@/lib/utils"
4
 
5
  const Card = React.forwardRef<
6
  HTMLDivElement,
@@ -10,12 +10,12 @@ const Card = React.forwardRef<
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,
@@ -26,8 +26,8 @@ const CardHeader = React.forwardRef<
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,
@@ -37,12 +37,12 @@ const CardTitle = React.forwardRef<
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,
@@ -53,16 +53,16 @@ const CardDescription = React.forwardRef<
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,
@@ -73,7 +73,14 @@ const CardFooter = React.forwardRef<
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 }
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
 
3
+ import { cn } from "@/lib/utils";
4
 
5
  const Card = React.forwardRef<
6
  HTMLDivElement,
 
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,
 
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,
 
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,
 
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,
 
73
  className={cn("flex items-center p-6 pt-0", className)}
74
  {...props}
75
  />
76
+ ));
77
+ CardFooter.displayName = "CardFooter";
78
 
79
+ export {
80
+ Card,
81
+ CardHeader,
82
+ CardFooter,
83
+ CardTitle,
84
+ CardDescription,
85
+ CardContent,
86
+ };
src/components/ui/carousel.tsx CHANGED
@@ -1,45 +1,45 @@
1
- "use client"
2
 
3
- import * as React from "react"
4
  import useEmblaCarousel, {
5
  type UseEmblaCarouselType,
6
- } from "embla-carousel-react"
7
- import { ArrowLeft, ArrowRight } from "lucide-react"
8
 
9
- import { cn } from "@/lib/utils"
10
- import { Button } from "@/components/ui/button"
11
 
12
- type CarouselApi = UseEmblaCarouselType[1]
13
- type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
14
- type CarouselOptions = UseCarouselParameters[0]
15
- type CarouselPlugin = UseCarouselParameters[1]
16
 
17
  type CarouselProps = {
18
- opts?: CarouselOptions
19
- plugins?: CarouselPlugin
20
- orientation?: "horizontal" | "vertical"
21
- setApi?: (api: CarouselApi) => void
22
- }
23
 
24
  type CarouselContextProps = {
25
- carouselRef: ReturnType<typeof useEmblaCarousel>[0]
26
- api: ReturnType<typeof useEmblaCarousel>[1]
27
- scrollPrev: () => void
28
- scrollNext: () => void
29
- canScrollPrev: boolean
30
- canScrollNext: boolean
31
- } & CarouselProps
32
 
33
- const CarouselContext = React.createContext<CarouselContextProps | null>(null)
34
 
35
  function useCarousel() {
36
- const context = React.useContext(CarouselContext)
37
 
38
  if (!context) {
39
- throw new Error("useCarousel must be used within a <Carousel />")
40
  }
41
 
42
- return context
43
  }
44
 
45
  const Carousel = React.forwardRef<
@@ -56,69 +56,69 @@ const Carousel = React.forwardRef<
56
  children,
57
  ...props
58
  },
59
- ref
60
  ) => {
61
  const [carouselRef, api] = useEmblaCarousel(
62
  {
63
  ...opts,
64
  axis: orientation === "horizontal" ? "x" : "y",
65
  },
66
- plugins
67
- )
68
- const [canScrollPrev, setCanScrollPrev] = React.useState(false)
69
- const [canScrollNext, setCanScrollNext] = React.useState(false)
70
 
71
  const onSelect = React.useCallback((api: CarouselApi) => {
72
  if (!api) {
73
- return
74
  }
75
 
76
- setCanScrollPrev(api.canScrollPrev())
77
- setCanScrollNext(api.canScrollNext())
78
- }, [])
79
 
80
  const scrollPrev = React.useCallback(() => {
81
- api?.scrollPrev()
82
- }, [api])
83
 
84
  const scrollNext = React.useCallback(() => {
85
- api?.scrollNext()
86
- }, [api])
87
 
88
  const handleKeyDown = React.useCallback(
89
  (event: React.KeyboardEvent<HTMLDivElement>) => {
90
  if (event.key === "ArrowLeft") {
91
- event.preventDefault()
92
- scrollPrev()
93
  } else if (event.key === "ArrowRight") {
94
- event.preventDefault()
95
- scrollNext()
96
  }
97
  },
98
- [scrollPrev, scrollNext]
99
- )
100
 
101
  React.useEffect(() => {
102
  if (!api || !setApi) {
103
- return
104
  }
105
 
106
- setApi(api)
107
- }, [api, setApi])
108
 
109
  React.useEffect(() => {
110
  if (!api) {
111
- return
112
  }
113
 
114
- onSelect(api)
115
- api.on("reInit", onSelect)
116
- api.on("select", onSelect)
117
 
118
  return () => {
119
- api?.off("select", onSelect)
120
- }
121
- }, [api, onSelect])
122
 
123
  return (
124
  <CarouselContext.Provider
@@ -145,16 +145,16 @@ const Carousel = React.forwardRef<
145
  {children}
146
  </div>
147
  </CarouselContext.Provider>
148
- )
149
- }
150
- )
151
- Carousel.displayName = "Carousel"
152
 
153
  const CarouselContent = React.forwardRef<
154
  HTMLDivElement,
155
  React.HTMLAttributes<HTMLDivElement>
156
  >(({ className, ...props }, ref) => {
157
- const { carouselRef, orientation } = useCarousel()
158
 
159
  return (
160
  <div ref={carouselRef} className="overflow-hidden">
@@ -163,20 +163,20 @@ const CarouselContent = React.forwardRef<
163
  className={cn(
164
  "flex",
165
  orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
166
- className
167
  )}
168
  {...props}
169
  />
170
  </div>
171
- )
172
- })
173
- CarouselContent.displayName = "CarouselContent"
174
 
175
  const CarouselItem = React.forwardRef<
176
  HTMLDivElement,
177
  React.HTMLAttributes<HTMLDivElement>
178
  >(({ className, ...props }, ref) => {
179
- const { orientation } = useCarousel()
180
 
181
  return (
182
  <div
@@ -186,19 +186,19 @@ const CarouselItem = React.forwardRef<
186
  className={cn(
187
  "min-w-0 shrink-0 grow-0 basis-full",
188
  orientation === "horizontal" ? "pl-4" : "pt-4",
189
- className
190
  )}
191
  {...props}
192
  />
193
- )
194
- })
195
- CarouselItem.displayName = "CarouselItem"
196
 
197
  const CarouselPrevious = React.forwardRef<
198
  HTMLButtonElement,
199
  React.ComponentProps<typeof Button>
200
  >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
201
- const { orientation, scrollPrev, canScrollPrev } = useCarousel()
202
 
203
  return (
204
  <Button
@@ -210,7 +210,7 @@ const CarouselPrevious = React.forwardRef<
210
  orientation === "horizontal"
211
  ? "-left-12 top-1/2 -translate-y-1/2"
212
  : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
213
- className
214
  )}
215
  disabled={!canScrollPrev}
216
  onClick={scrollPrev}
@@ -219,15 +219,15 @@ const CarouselPrevious = React.forwardRef<
219
  <ArrowLeft className="h-4 w-4" />
220
  <span className="sr-only">Previous slide</span>
221
  </Button>
222
- )
223
- })
224
- CarouselPrevious.displayName = "CarouselPrevious"
225
 
226
  const CarouselNext = React.forwardRef<
227
  HTMLButtonElement,
228
  React.ComponentProps<typeof Button>
229
  >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
230
- const { orientation, scrollNext, canScrollNext } = useCarousel()
231
 
232
  return (
233
  <Button
@@ -239,7 +239,7 @@ const CarouselNext = React.forwardRef<
239
  orientation === "horizontal"
240
  ? "-right-12 top-1/2 -translate-y-1/2"
241
  : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
242
- className
243
  )}
244
  disabled={!canScrollNext}
245
  onClick={scrollNext}
@@ -248,9 +248,9 @@ const CarouselNext = React.forwardRef<
248
  <ArrowRight className="h-4 w-4" />
249
  <span className="sr-only">Next slide</span>
250
  </Button>
251
- )
252
- })
253
- CarouselNext.displayName = "CarouselNext"
254
 
255
  export {
256
  type CarouselApi,
@@ -259,4 +259,4 @@ export {
259
  CarouselItem,
260
  CarouselPrevious,
261
  CarouselNext,
262
- }
 
1
+ "use client";
2
 
3
+ import * as React from "react";
4
  import useEmblaCarousel, {
5
  type UseEmblaCarouselType,
6
+ } from "embla-carousel-react";
7
+ import { ArrowLeft, ArrowRight } from "lucide-react";
8
 
9
+ import { cn } from "@/lib/utils";
10
+ import { Button } from "@/components/ui/button";
11
 
12
+ type CarouselApi = UseEmblaCarouselType[1];
13
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
14
+ type CarouselOptions = UseCarouselParameters[0];
15
+ type CarouselPlugin = UseCarouselParameters[1];
16
 
17
  type CarouselProps = {
18
+ opts?: CarouselOptions;
19
+ plugins?: CarouselPlugin;
20
+ orientation?: "horizontal" | "vertical";
21
+ setApi?: (api: CarouselApi) => void;
22
+ };
23
 
24
  type CarouselContextProps = {
25
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0];
26
+ api: ReturnType<typeof useEmblaCarousel>[1];
27
+ scrollPrev: () => void;
28
+ scrollNext: () => void;
29
+ canScrollPrev: boolean;
30
+ canScrollNext: boolean;
31
+ } & CarouselProps;
32
 
33
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null);
34
 
35
  function useCarousel() {
36
+ const context = React.useContext(CarouselContext);
37
 
38
  if (!context) {
39
+ throw new Error("useCarousel must be used within a <Carousel />");
40
  }
41
 
42
+ return context;
43
  }
44
 
45
  const Carousel = React.forwardRef<
 
56
  children,
57
  ...props
58
  },
59
+ ref,
60
  ) => {
61
  const [carouselRef, api] = useEmblaCarousel(
62
  {
63
  ...opts,
64
  axis: orientation === "horizontal" ? "x" : "y",
65
  },
66
+ plugins,
67
+ );
68
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
69
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
70
 
71
  const onSelect = React.useCallback((api: CarouselApi) => {
72
  if (!api) {
73
+ return;
74
  }
75
 
76
+ setCanScrollPrev(api.canScrollPrev());
77
+ setCanScrollNext(api.canScrollNext());
78
+ }, []);
79
 
80
  const scrollPrev = React.useCallback(() => {
81
+ api?.scrollPrev();
82
+ }, [api]);
83
 
84
  const scrollNext = React.useCallback(() => {
85
+ api?.scrollNext();
86
+ }, [api]);
87
 
88
  const handleKeyDown = React.useCallback(
89
  (event: React.KeyboardEvent<HTMLDivElement>) => {
90
  if (event.key === "ArrowLeft") {
91
+ event.preventDefault();
92
+ scrollPrev();
93
  } else if (event.key === "ArrowRight") {
94
+ event.preventDefault();
95
+ scrollNext();
96
  }
97
  },
98
+ [scrollPrev, scrollNext],
99
+ );
100
 
101
  React.useEffect(() => {
102
  if (!api || !setApi) {
103
+ return;
104
  }
105
 
106
+ setApi(api);
107
+ }, [api, setApi]);
108
 
109
  React.useEffect(() => {
110
  if (!api) {
111
+ return;
112
  }
113
 
114
+ onSelect(api);
115
+ api.on("reInit", onSelect);
116
+ api.on("select", onSelect);
117
 
118
  return () => {
119
+ api?.off("select", onSelect);
120
+ };
121
+ }, [api, onSelect]);
122
 
123
  return (
124
  <CarouselContext.Provider
 
145
  {children}
146
  </div>
147
  </CarouselContext.Provider>
148
+ );
149
+ },
150
+ );
151
+ Carousel.displayName = "Carousel";
152
 
153
  const CarouselContent = React.forwardRef<
154
  HTMLDivElement,
155
  React.HTMLAttributes<HTMLDivElement>
156
  >(({ className, ...props }, ref) => {
157
+ const { carouselRef, orientation } = useCarousel();
158
 
159
  return (
160
  <div ref={carouselRef} className="overflow-hidden">
 
163
  className={cn(
164
  "flex",
165
  orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
166
+ className,
167
  )}
168
  {...props}
169
  />
170
  </div>
171
+ );
172
+ });
173
+ CarouselContent.displayName = "CarouselContent";
174
 
175
  const CarouselItem = React.forwardRef<
176
  HTMLDivElement,
177
  React.HTMLAttributes<HTMLDivElement>
178
  >(({ className, ...props }, ref) => {
179
+ const { orientation } = useCarousel();
180
 
181
  return (
182
  <div
 
186
  className={cn(
187
  "min-w-0 shrink-0 grow-0 basis-full",
188
  orientation === "horizontal" ? "pl-4" : "pt-4",
189
+ className,
190
  )}
191
  {...props}
192
  />
193
+ );
194
+ });
195
+ CarouselItem.displayName = "CarouselItem";
196
 
197
  const CarouselPrevious = React.forwardRef<
198
  HTMLButtonElement,
199
  React.ComponentProps<typeof Button>
200
  >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
201
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
202
 
203
  return (
204
  <Button
 
210
  orientation === "horizontal"
211
  ? "-left-12 top-1/2 -translate-y-1/2"
212
  : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
213
+ className,
214
  )}
215
  disabled={!canScrollPrev}
216
  onClick={scrollPrev}
 
219
  <ArrowLeft className="h-4 w-4" />
220
  <span className="sr-only">Previous slide</span>
221
  </Button>
222
+ );
223
+ });
224
+ CarouselPrevious.displayName = "CarouselPrevious";
225
 
226
  const CarouselNext = React.forwardRef<
227
  HTMLButtonElement,
228
  React.ComponentProps<typeof Button>
229
  >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
230
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
231
 
232
  return (
233
  <Button
 
239
  orientation === "horizontal"
240
  ? "-right-12 top-1/2 -translate-y-1/2"
241
  : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
242
+ className,
243
  )}
244
  disabled={!canScrollNext}
245
  onClick={scrollNext}
 
248
  <ArrowRight className="h-4 w-4" />
249
  <span className="sr-only">Next slide</span>
250
  </Button>
251
+ );
252
+ });
253
+ CarouselNext.displayName = "CarouselNext";
254
 
255
  export {
256
  type CarouselApi,
 
259
  CarouselItem,
260
  CarouselPrevious,
261
  CarouselNext,
262
+ };
src/components/ui/chart.tsx CHANGED
@@ -1,50 +1,50 @@
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 }}>
@@ -53,7 +53,7 @@ const ChartContainer = React.forwardRef<
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
  >
@@ -63,17 +63,17 @@ const ChartContainer = React.forwardRef<
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 (
@@ -87,30 +87,30 @@ ${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
  (
@@ -129,36 +129,36 @@ const ChartTooltipContent = React.forwardRef<
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,
@@ -167,35 +167,35 @@ const ChartTooltipContent = React.forwardRef<
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 ? (
@@ -215,7 +215,7 @@ const ChartTooltipContent = React.forwardRef<
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
  {
@@ -229,7 +229,7 @@ const ChartTooltipContent = React.forwardRef<
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">
@@ -247,33 +247,33 @@ const ChartTooltipContent = React.forwardRef<
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 (
@@ -282,18 +282,18 @@ const ChartLegendContent = React.forwardRef<
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 ? (
@@ -308,22 +308,22 @@ const ChartLegendContent = React.forwardRef<
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 =
@@ -331,15 +331,15 @@ function getPayloadConfigFromPayload(
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 &&
@@ -347,12 +347,12 @@ function getPayloadConfigFromPayload(
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 {
@@ -362,4 +362,4 @@ export {
362
  ChartLegend,
363
  ChartLegendContent,
364
  ChartStyle,
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 }}>
 
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
  >
 
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 (
 
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
  (
 
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,
 
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 ? (
 
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
  {
 
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">
 
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 (
 
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 ? (
 
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 =
 
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 &&
 
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 {
 
362
  ChartLegend,
363
  ChartLegendContent,
364
  ChartStyle,
365
+ };
src/components/ui/checkbox.tsx CHANGED
@@ -1,10 +1,10 @@
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>,
@@ -14,7 +14,7 @@ const Checkbox = React.forwardRef<
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
  >
@@ -24,7 +24,7 @@ const Checkbox = React.forwardRef<
24
  <Check className="h-4 w-4" />
25
  </CheckboxPrimitive.Indicator>
26
  </CheckboxPrimitive.Root>
27
- ))
28
- Checkbox.displayName = CheckboxPrimitive.Root.displayName
29
 
30
- export { Checkbox }
 
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>,
 
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
  >
 
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/collapsible.tsx CHANGED
@@ -1,11 +1,11 @@
1
- "use client"
2
 
3
- import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4
 
5
- const Collapsible = CollapsiblePrimitive.Root
6
 
7
- const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8
 
9
- const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10
 
11
- export { Collapsible, CollapsibleTrigger, CollapsibleContent }
 
1
+ "use client";
2
 
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
4
 
5
+ const Collapsible = CollapsiblePrimitive.Root;
6
 
7
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
8
 
9
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
10
 
11
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };
src/components/ui/command.tsx CHANGED
@@ -1,12 +1,12 @@
1
- "use client"
2
 
3
- import * as React from "react"
4
- import { type DialogProps } from "@radix-ui/react-dialog"
5
- import { Command as CommandPrimitive } from "cmdk"
6
- import { Search } from "lucide-react"
7
 
8
- import { cn } from "@/lib/utils"
9
- import { Dialog, DialogContent } from "@/components/ui/dialog"
10
 
11
  const Command = React.forwardRef<
12
  React.ElementRef<typeof CommandPrimitive>,
@@ -16,12 +16,12 @@ const Command = React.forwardRef<
16
  ref={ref}
17
  className={cn(
18
  "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
19
- className
20
  )}
21
  {...props}
22
  />
23
- ))
24
- Command.displayName = CommandPrimitive.displayName
25
 
26
  const CommandDialog = ({ children, ...props }: DialogProps) => {
27
  return (
@@ -32,8 +32,8 @@ const CommandDialog = ({ children, ...props }: DialogProps) => {
32
  </Command>
33
  </DialogContent>
34
  </Dialog>
35
- )
36
- }
37
 
38
  const CommandInput = React.forwardRef<
39
  React.ElementRef<typeof CommandPrimitive.Input>,
@@ -45,14 +45,14 @@ const CommandInput = React.forwardRef<
45
  ref={ref}
46
  className={cn(
47
  "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
48
- className
49
  )}
50
  {...props}
51
  />
52
  </div>
53
- ))
54
 
55
- CommandInput.displayName = CommandPrimitive.Input.displayName
56
 
57
  const CommandList = React.forwardRef<
58
  React.ElementRef<typeof CommandPrimitive.List>,
@@ -63,9 +63,9 @@ const CommandList = React.forwardRef<
63
  className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
64
  {...props}
65
  />
66
- ))
67
 
68
- CommandList.displayName = CommandPrimitive.List.displayName
69
 
70
  const CommandEmpty = React.forwardRef<
71
  React.ElementRef<typeof CommandPrimitive.Empty>,
@@ -76,9 +76,9 @@ const CommandEmpty = React.forwardRef<
76
  className="py-6 text-center text-sm"
77
  {...props}
78
  />
79
- ))
80
 
81
- CommandEmpty.displayName = CommandPrimitive.Empty.displayName
82
 
83
  const CommandGroup = React.forwardRef<
84
  React.ElementRef<typeof CommandPrimitive.Group>,
@@ -88,13 +88,13 @@ const CommandGroup = React.forwardRef<
88
  ref={ref}
89
  className={cn(
90
  "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
91
- className
92
  )}
93
  {...props}
94
  />
95
- ))
96
 
97
- CommandGroup.displayName = CommandPrimitive.Group.displayName
98
 
99
  const CommandSeparator = React.forwardRef<
100
  React.ElementRef<typeof CommandPrimitive.Separator>,
@@ -105,8 +105,8 @@ const CommandSeparator = React.forwardRef<
105
  className={cn("-mx-1 h-px bg-border", className)}
106
  {...props}
107
  />
108
- ))
109
- CommandSeparator.displayName = CommandPrimitive.Separator.displayName
110
 
111
  const CommandItem = React.forwardRef<
112
  React.ElementRef<typeof CommandPrimitive.Item>,
@@ -116,13 +116,13 @@ const CommandItem = React.forwardRef<
116
  ref={ref}
117
  className={cn(
118
  "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
119
- className
120
  )}
121
  {...props}
122
  />
123
- ))
124
 
125
- CommandItem.displayName = CommandPrimitive.Item.displayName
126
 
127
  const CommandShortcut = ({
128
  className,
@@ -132,13 +132,13 @@ const CommandShortcut = ({
132
  <span
133
  className={cn(
134
  "ml-auto text-xs tracking-widest text-muted-foreground",
135
- className
136
  )}
137
  {...props}
138
  />
139
- )
140
- }
141
- CommandShortcut.displayName = "CommandShortcut"
142
 
143
  export {
144
  Command,
@@ -150,4 +150,4 @@ export {
150
  CommandItem,
151
  CommandShortcut,
152
  CommandSeparator,
153
- }
 
1
+ "use client";
2
 
3
+ import * as React from "react";
4
+ import { type DialogProps } from "@radix-ui/react-dialog";
5
+ import { Command as CommandPrimitive } from "cmdk";
6
+ import { Search } from "lucide-react";
7
 
8
+ import { cn } from "@/lib/utils";
9
+ import { Dialog, DialogContent } from "@/components/ui/dialog";
10
 
11
  const Command = React.forwardRef<
12
  React.ElementRef<typeof CommandPrimitive>,
 
16
  ref={ref}
17
  className={cn(
18
  "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
19
+ className,
20
  )}
21
  {...props}
22
  />
23
+ ));
24
+ Command.displayName = CommandPrimitive.displayName;
25
 
26
  const CommandDialog = ({ children, ...props }: DialogProps) => {
27
  return (
 
32
  </Command>
33
  </DialogContent>
34
  </Dialog>
35
+ );
36
+ };
37
 
38
  const CommandInput = React.forwardRef<
39
  React.ElementRef<typeof CommandPrimitive.Input>,
 
45
  ref={ref}
46
  className={cn(
47
  "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
48
+ className,
49
  )}
50
  {...props}
51
  />
52
  </div>
53
+ ));
54
 
55
+ CommandInput.displayName = CommandPrimitive.Input.displayName;
56
 
57
  const CommandList = React.forwardRef<
58
  React.ElementRef<typeof CommandPrimitive.List>,
 
63
  className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
64
  {...props}
65
  />
66
+ ));
67
 
68
+ CommandList.displayName = CommandPrimitive.List.displayName;
69
 
70
  const CommandEmpty = React.forwardRef<
71
  React.ElementRef<typeof CommandPrimitive.Empty>,
 
76
  className="py-6 text-center text-sm"
77
  {...props}
78
  />
79
+ ));
80
 
81
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
82
 
83
  const CommandGroup = React.forwardRef<
84
  React.ElementRef<typeof CommandPrimitive.Group>,
 
88
  ref={ref}
89
  className={cn(
90
  "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
91
+ className,
92
  )}
93
  {...props}
94
  />
95
+ ));
96
 
97
+ CommandGroup.displayName = CommandPrimitive.Group.displayName;
98
 
99
  const CommandSeparator = React.forwardRef<
100
  React.ElementRef<typeof CommandPrimitive.Separator>,
 
105
  className={cn("-mx-1 h-px bg-border", className)}
106
  {...props}
107
  />
108
+ ));
109
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
110
 
111
  const CommandItem = React.forwardRef<
112
  React.ElementRef<typeof CommandPrimitive.Item>,
 
116
  ref={ref}
117
  className={cn(
118
  "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
119
+ className,
120
  )}
121
  {...props}
122
  />
123
+ ));
124
 
125
+ CommandItem.displayName = CommandPrimitive.Item.displayName;
126
 
127
  const CommandShortcut = ({
128
  className,
 
132
  <span
133
  className={cn(
134
  "ml-auto text-xs tracking-widest text-muted-foreground",
135
+ className,
136
  )}
137
  {...props}
138
  />
139
+ );
140
+ };
141
+ CommandShortcut.displayName = "CommandShortcut";
142
 
143
  export {
144
  Command,
 
150
  CommandItem,
151
  CommandShortcut,
152
  CommandSeparator,
153
+ };
src/components/ui/context-menu.tsx CHANGED
@@ -1,27 +1,27 @@
1
- "use client"
2
 
3
- import * as React from "react"
4
- import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
5
- import { Check, ChevronRight, Circle } from "lucide-react"
6
 
7
- import { cn } from "@/lib/utils"
8
 
9
- const ContextMenu = ContextMenuPrimitive.Root
10
 
11
- const ContextMenuTrigger = ContextMenuPrimitive.Trigger
12
 
13
- const ContextMenuGroup = ContextMenuPrimitive.Group
14
 
15
- const ContextMenuPortal = ContextMenuPrimitive.Portal
16
 
17
- const ContextMenuSub = ContextMenuPrimitive.Sub
18
 
19
- const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
20
 
21
  const ContextMenuSubTrigger = React.forwardRef<
22
  React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
23
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
24
- inset?: boolean
25
  }
26
  >(({ className, inset, children, ...props }, ref) => (
27
  <ContextMenuPrimitive.SubTrigger
@@ -29,15 +29,15 @@ const ContextMenuSubTrigger = React.forwardRef<
29
  className={cn(
30
  "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",
31
  inset && "pl-8",
32
- className
33
  )}
34
  {...props}
35
  >
36
  {children}
37
  <ChevronRight className="ml-auto h-4 w-4" />
38
  </ContextMenuPrimitive.SubTrigger>
39
- ))
40
- ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
41
 
42
  const ContextMenuSubContent = React.forwardRef<
43
  React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
@@ -47,12 +47,12 @@ const ContextMenuSubContent = React.forwardRef<
47
  ref={ref}
48
  className={cn(
49
  "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",
50
- className
51
  )}
52
  {...props}
53
  />
54
- ))
55
- ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
56
 
57
  const ContextMenuContent = React.forwardRef<
58
  React.ElementRef<typeof ContextMenuPrimitive.Content>,
@@ -63,18 +63,18 @@ const ContextMenuContent = React.forwardRef<
63
  ref={ref}
64
  className={cn(
65
  "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
66
- className
67
  )}
68
  {...props}
69
  />
70
  </ContextMenuPrimitive.Portal>
71
- ))
72
- ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
73
 
74
  const ContextMenuItem = React.forwardRef<
75
  React.ElementRef<typeof ContextMenuPrimitive.Item>,
76
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
77
- inset?: boolean
78
  }
79
  >(({ className, inset, ...props }, ref) => (
80
  <ContextMenuPrimitive.Item
@@ -82,12 +82,12 @@ const ContextMenuItem = React.forwardRef<
82
  className={cn(
83
  "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",
84
  inset && "pl-8",
85
- className
86
  )}
87
  {...props}
88
  />
89
- ))
90
- ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
91
 
92
  const ContextMenuCheckboxItem = React.forwardRef<
93
  React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
@@ -97,7 +97,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
97
  ref={ref}
98
  className={cn(
99
  "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",
100
- className
101
  )}
102
  checked={checked}
103
  {...props}
@@ -109,9 +109,9 @@ const ContextMenuCheckboxItem = React.forwardRef<
109
  </span>
110
  {children}
111
  </ContextMenuPrimitive.CheckboxItem>
112
- ))
113
  ContextMenuCheckboxItem.displayName =
114
- ContextMenuPrimitive.CheckboxItem.displayName
115
 
116
  const ContextMenuRadioItem = React.forwardRef<
117
  React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
@@ -121,7 +121,7 @@ const ContextMenuRadioItem = React.forwardRef<
121
  ref={ref}
122
  className={cn(
123
  "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",
124
- className
125
  )}
126
  {...props}
127
  >
@@ -132,13 +132,13 @@ const ContextMenuRadioItem = React.forwardRef<
132
  </span>
133
  {children}
134
  </ContextMenuPrimitive.RadioItem>
135
- ))
136
- ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
137
 
138
  const ContextMenuLabel = React.forwardRef<
139
  React.ElementRef<typeof ContextMenuPrimitive.Label>,
140
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
141
- inset?: boolean
142
  }
143
  >(({ className, inset, ...props }, ref) => (
144
  <ContextMenuPrimitive.Label
@@ -146,12 +146,12 @@ const ContextMenuLabel = React.forwardRef<
146
  className={cn(
147
  "px-2 py-1.5 text-sm font-semibold text-foreground",
148
  inset && "pl-8",
149
- className
150
  )}
151
  {...props}
152
  />
153
- ))
154
- ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
155
 
156
  const ContextMenuSeparator = React.forwardRef<
157
  React.ElementRef<typeof ContextMenuPrimitive.Separator>,
@@ -162,8 +162,8 @@ const ContextMenuSeparator = React.forwardRef<
162
  className={cn("-mx-1 my-1 h-px bg-border", className)}
163
  {...props}
164
  />
165
- ))
166
- ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
167
 
168
  const ContextMenuShortcut = ({
169
  className,
@@ -173,13 +173,13 @@ const ContextMenuShortcut = ({
173
  <span
174
  className={cn(
175
  "ml-auto text-xs tracking-widest text-muted-foreground",
176
- className
177
  )}
178
  {...props}
179
  />
180
- )
181
- }
182
- ContextMenuShortcut.displayName = "ContextMenuShortcut"
183
 
184
  export {
185
  ContextMenu,
@@ -197,4 +197,4 @@ export {
197
  ContextMenuSubContent,
198
  ContextMenuSubTrigger,
199
  ContextMenuRadioGroup,
200
- }
 
1
+ "use client";
2
 
3
+ import * as React from "react";
4
+ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
5
+ import { Check, ChevronRight, Circle } from "lucide-react";
6
 
7
+ import { cn } from "@/lib/utils";
8
 
9
+ const ContextMenu = ContextMenuPrimitive.Root;
10
 
11
+ const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
12
 
13
+ const ContextMenuGroup = ContextMenuPrimitive.Group;
14
 
15
+ const ContextMenuPortal = ContextMenuPrimitive.Portal;
16
 
17
+ const ContextMenuSub = ContextMenuPrimitive.Sub;
18
 
19
+ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
20
 
21
  const ContextMenuSubTrigger = React.forwardRef<
22
  React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
23
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
24
+ inset?: boolean;
25
  }
26
  >(({ className, inset, children, ...props }, ref) => (
27
  <ContextMenuPrimitive.SubTrigger
 
29
  className={cn(
30
  "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",
31
  inset && "pl-8",
32
+ className,
33
  )}
34
  {...props}
35
  >
36
  {children}
37
  <ChevronRight className="ml-auto h-4 w-4" />
38
  </ContextMenuPrimitive.SubTrigger>
39
+ ));
40
+ ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
41
 
42
  const ContextMenuSubContent = React.forwardRef<
43
  React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
 
47
  ref={ref}
48
  className={cn(
49
  "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",
50
+ className,
51
  )}
52
  {...props}
53
  />
54
+ ));
55
+ ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
56
 
57
  const ContextMenuContent = React.forwardRef<
58
  React.ElementRef<typeof ContextMenuPrimitive.Content>,
 
63
  ref={ref}
64
  className={cn(
65
  "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
66
+ className,
67
  )}
68
  {...props}
69
  />
70
  </ContextMenuPrimitive.Portal>
71
+ ));
72
+ ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
73
 
74
  const ContextMenuItem = React.forwardRef<
75
  React.ElementRef<typeof ContextMenuPrimitive.Item>,
76
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
77
+ inset?: boolean;
78
  }
79
  >(({ className, inset, ...props }, ref) => (
80
  <ContextMenuPrimitive.Item
 
82
  className={cn(
83
  "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",
84
  inset && "pl-8",
85
+ className,
86
  )}
87
  {...props}
88
  />
89
+ ));
90
+ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
91
 
92
  const ContextMenuCheckboxItem = React.forwardRef<
93
  React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
 
97
  ref={ref}
98
  className={cn(
99
  "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",
100
+ className,
101
  )}
102
  checked={checked}
103
  {...props}
 
109
  </span>
110
  {children}
111
  </ContextMenuPrimitive.CheckboxItem>
112
+ ));
113
  ContextMenuCheckboxItem.displayName =
114
+ ContextMenuPrimitive.CheckboxItem.displayName;
115
 
116
  const ContextMenuRadioItem = React.forwardRef<
117
  React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
 
121
  ref={ref}
122
  className={cn(
123
  "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",
124
+ className,
125
  )}
126
  {...props}
127
  >
 
132
  </span>
133
  {children}
134
  </ContextMenuPrimitive.RadioItem>
135
+ ));
136
+ ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
137
 
138
  const ContextMenuLabel = React.forwardRef<
139
  React.ElementRef<typeof ContextMenuPrimitive.Label>,
140
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
141
+ inset?: boolean;
142
  }
143
  >(({ className, inset, ...props }, ref) => (
144
  <ContextMenuPrimitive.Label
 
146
  className={cn(
147
  "px-2 py-1.5 text-sm font-semibold text-foreground",
148
  inset && "pl-8",
149
+ className,
150
  )}
151
  {...props}
152
  />
153
+ ));
154
+ ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
155
 
156
  const ContextMenuSeparator = React.forwardRef<
157
  React.ElementRef<typeof ContextMenuPrimitive.Separator>,
 
162
  className={cn("-mx-1 my-1 h-px bg-border", className)}
163
  {...props}
164
  />
165
+ ));
166
+ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
167
 
168
  const ContextMenuShortcut = ({
169
  className,
 
173
  <span
174
  className={cn(
175
  "ml-auto text-xs tracking-widest text-muted-foreground",
176
+ className,
177
  )}
178
  {...props}
179
  />
180
+ );
181
+ };
182
+ ContextMenuShortcut.displayName = "ContextMenuShortcut";
183
 
184
  export {
185
  ContextMenu,
 
197
  ContextMenuSubContent,
198
  ContextMenuSubTrigger,
199
  ContextMenuRadioGroup,
200
+ };
src/components/ui/dialog.tsx CHANGED
@@ -1,18 +1,18 @@
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>,
@@ -22,12 +22,12 @@ const DialogOverlay = React.forwardRef<
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>,
@@ -39,7 +39,7 @@ const DialogContent = React.forwardRef<
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
  >
@@ -50,8 +50,8 @@ const DialogContent = React.forwardRef<
50
  </DialogPrimitive.Close>
51
  </DialogPrimitive.Content>
52
  </DialogPortal>
53
- ))
54
- DialogContent.displayName = DialogPrimitive.Content.displayName
55
 
56
  const DialogHeader = ({
57
  className,
@@ -60,12 +60,12 @@ const DialogHeader = ({
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,
@@ -74,12 +74,12 @@ const DialogFooter = ({
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>,
@@ -89,12 +89,12 @@ const DialogTitle = React.forwardRef<
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>,
@@ -105,8 +105,8 @@ const DialogDescription = React.forwardRef<
105
  className={cn("text-sm text-muted-foreground", className)}
106
  {...props}
107
  />
108
- ))
109
- DialogDescription.displayName = DialogPrimitive.Description.displayName
110
 
111
  export {
112
  Dialog,
@@ -119,4 +119,4 @@ export {
119
  DialogFooter,
120
  DialogTitle,
121
  DialogDescription,
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>,
 
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>,
 
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
  >
 
50
  </DialogPrimitive.Close>
51
  </DialogPrimitive.Content>
52
  </DialogPortal>
53
+ ));
54
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
55
 
56
  const DialogHeader = ({
57
  className,
 
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,
 
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>,
 
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>,
 
105
  className={cn("text-sm text-muted-foreground", className)}
106
  {...props}
107
  />
108
+ ));
109
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
110
 
111
  export {
112
  Dialog,
 
119
  DialogFooter,
120
  DialogTitle,
121
  DialogDescription,
122
+ };
src/components/ui/drawer.tsx CHANGED
@@ -1,9 +1,9 @@
1
- "use client"
2
 
3
- import * as React from "react"
4
- import { Drawer as DrawerPrimitive } from "vaul"
5
 
6
- import { cn } from "@/lib/utils"
7
 
8
  const Drawer = ({
9
  shouldScaleBackground = true,
@@ -13,14 +13,14 @@ const Drawer = ({
13
  shouldScaleBackground={shouldScaleBackground}
14
  {...props}
15
  />
16
- )
17
- Drawer.displayName = "Drawer"
18
 
19
- const DrawerTrigger = DrawerPrimitive.Trigger
20
 
21
- const DrawerPortal = DrawerPrimitive.Portal
22
 
23
- const DrawerClose = DrawerPrimitive.Close
24
 
25
  const DrawerOverlay = React.forwardRef<
26
  React.ElementRef<typeof DrawerPrimitive.Overlay>,
@@ -31,8 +31,8 @@ const DrawerOverlay = React.forwardRef<
31
  className={cn("fixed inset-0 z-50 bg-black/80", className)}
32
  {...props}
33
  />
34
- ))
35
- DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
36
 
37
  const DrawerContent = React.forwardRef<
38
  React.ElementRef<typeof DrawerPrimitive.Content>,
@@ -44,7 +44,7 @@ const DrawerContent = React.forwardRef<
44
  ref={ref}
45
  className={cn(
46
  "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
47
- className
48
  )}
49
  {...props}
50
  >
@@ -52,8 +52,8 @@ const DrawerContent = React.forwardRef<
52
  {children}
53
  </DrawerPrimitive.Content>
54
  </DrawerPortal>
55
- ))
56
- DrawerContent.displayName = "DrawerContent"
57
 
58
  const DrawerHeader = ({
59
  className,
@@ -63,8 +63,8 @@ const DrawerHeader = ({
63
  className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
64
  {...props}
65
  />
66
- )
67
- DrawerHeader.displayName = "DrawerHeader"
68
 
69
  const DrawerFooter = ({
70
  className,
@@ -74,8 +74,8 @@ const DrawerFooter = ({
74
  className={cn("mt-auto flex flex-col gap-2 p-4", className)}
75
  {...props}
76
  />
77
- )
78
- DrawerFooter.displayName = "DrawerFooter"
79
 
80
  const DrawerTitle = React.forwardRef<
81
  React.ElementRef<typeof DrawerPrimitive.Title>,
@@ -85,12 +85,12 @@ const DrawerTitle = React.forwardRef<
85
  ref={ref}
86
  className={cn(
87
  "text-lg font-semibold leading-none tracking-tight",
88
- className
89
  )}
90
  {...props}
91
  />
92
- ))
93
- DrawerTitle.displayName = DrawerPrimitive.Title.displayName
94
 
95
  const DrawerDescription = React.forwardRef<
96
  React.ElementRef<typeof DrawerPrimitive.Description>,
@@ -101,8 +101,8 @@ const DrawerDescription = React.forwardRef<
101
  className={cn("text-sm text-muted-foreground", className)}
102
  {...props}
103
  />
104
- ))
105
- DrawerDescription.displayName = DrawerPrimitive.Description.displayName
106
 
107
  export {
108
  Drawer,
@@ -115,4 +115,4 @@ export {
115
  DrawerFooter,
116
  DrawerTitle,
117
  DrawerDescription,
118
- }
 
1
+ "use client";
2
 
3
+ import * as React from "react";
4
+ import { Drawer as DrawerPrimitive } from "vaul";
5
 
6
+ import { cn } from "@/lib/utils";
7
 
8
  const Drawer = ({
9
  shouldScaleBackground = true,
 
13
  shouldScaleBackground={shouldScaleBackground}
14
  {...props}
15
  />
16
+ );
17
+ Drawer.displayName = "Drawer";
18
 
19
+ const DrawerTrigger = DrawerPrimitive.Trigger;
20
 
21
+ const DrawerPortal = DrawerPrimitive.Portal;
22
 
23
+ const DrawerClose = DrawerPrimitive.Close;
24
 
25
  const DrawerOverlay = React.forwardRef<
26
  React.ElementRef<typeof DrawerPrimitive.Overlay>,
 
31
  className={cn("fixed inset-0 z-50 bg-black/80", className)}
32
  {...props}
33
  />
34
+ ));
35
+ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
36
 
37
  const DrawerContent = React.forwardRef<
38
  React.ElementRef<typeof DrawerPrimitive.Content>,
 
44
  ref={ref}
45
  className={cn(
46
  "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
47
+ className,
48
  )}
49
  {...props}
50
  >
 
52
  {children}
53
  </DrawerPrimitive.Content>
54
  </DrawerPortal>
55
+ ));
56
+ DrawerContent.displayName = "DrawerContent";
57
 
58
  const DrawerHeader = ({
59
  className,
 
63
  className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
64
  {...props}
65
  />
66
+ );
67
+ DrawerHeader.displayName = "DrawerHeader";
68
 
69
  const DrawerFooter = ({
70
  className,
 
74
  className={cn("mt-auto flex flex-col gap-2 p-4", className)}
75
  {...props}
76
  />
77
+ );
78
+ DrawerFooter.displayName = "DrawerFooter";
79
 
80
  const DrawerTitle = React.forwardRef<
81
  React.ElementRef<typeof DrawerPrimitive.Title>,
 
85
  ref={ref}
86
  className={cn(
87
  "text-lg font-semibold leading-none tracking-tight",
88
+ className,
89
  )}
90
  {...props}
91
  />
92
+ ));
93
+ DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
94
 
95
  const DrawerDescription = React.forwardRef<
96
  React.ElementRef<typeof DrawerPrimitive.Description>,
 
101
  className={cn("text-sm text-muted-foreground", className)}
102
  {...props}
103
  />
104
+ ));
105
+ DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
106
 
107
  export {
108
  Drawer,
 
115
  DrawerFooter,
116
  DrawerTitle,
117
  DrawerDescription,
118
+ };
src/components/ui/dropdown-menu.tsx CHANGED
@@ -1,27 +1,27 @@
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
@@ -29,16 +29,16 @@ const DropdownMenuSubTrigger = React.forwardRef<
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>,
@@ -48,13 +48,13 @@ const DropdownMenuSubContent = React.forwardRef<
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>,
@@ -66,18 +66,18 @@ const DropdownMenuContent = React.forwardRef<
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
@@ -85,12 +85,12 @@ const DropdownMenuItem = React.forwardRef<
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>,
@@ -100,7 +100,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
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}
@@ -112,9 +112,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
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>,
@@ -124,7 +124,7 @@ const DropdownMenuRadioItem = React.forwardRef<
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
  >
@@ -135,13 +135,13 @@ const DropdownMenuRadioItem = React.forwardRef<
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
@@ -149,12 +149,12 @@ const DropdownMenuLabel = React.forwardRef<
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>,
@@ -165,8 +165,8 @@ const DropdownMenuSeparator = React.forwardRef<
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,
@@ -177,9 +177,9 @@ const DropdownMenuShortcut = ({
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,
@@ -197,4 +197,4 @@ export {
197
  DropdownMenuSubContent,
198
  DropdownMenuSubTrigger,
199
  DropdownMenuRadioGroup,
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
 
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>,
 
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>,
 
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
 
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>,
 
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}
 
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>,
 
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
  >
 
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
 
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>,
 
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,
 
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,
 
197
  DropdownMenuSubContent,
198
  DropdownMenuSubTrigger,
199
  DropdownMenuRadioGroup,
200
+ };
src/components/ui/error-message.tsx CHANGED
@@ -6,18 +6,34 @@ interface ErrorMessageProps {
6
  className?: string;
7
  }
8
 
9
- export function ErrorMessage({ message, onClose, className = "" }: ErrorMessageProps) {
 
 
 
 
10
  if (!message) return null;
11
-
12
  return (
13
- <div className={`p-2 bg-red-600/20 text-red-200 text-sm text-center relative ${className}`}>
 
 
14
  {onClose && (
15
- <button
16
- onClick={onClose}
17
  className="absolute right-2 top-1/2 -translate-y-1/2 text-red-200 hover:text-white"
18
  aria-label="Close error message"
19
  >
20
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
 
 
 
 
 
 
 
 
 
 
21
  <line x1="18" y1="6" x2="6" y2="18"></line>
22
  <line x1="6" y1="6" x2="18" y2="18"></line>
23
  </svg>
@@ -26,4 +42,4 @@ export function ErrorMessage({ message, onClose, className = "" }: ErrorMessageP
26
  {message}
27
  </div>
28
  );
29
- }
 
6
  className?: string;
7
  }
8
 
9
+ export function ErrorMessage({
10
+ message,
11
+ onClose,
12
+ className = "",
13
+ }: ErrorMessageProps) {
14
  if (!message) return null;
15
+
16
  return (
17
+ <div
18
+ className={`p-2 bg-red-600/20 text-red-200 text-sm text-center relative ${className}`}
19
+ >
20
  {onClose && (
21
+ <button
22
+ onClick={onClose}
23
  className="absolute right-2 top-1/2 -translate-y-1/2 text-red-200 hover:text-white"
24
  aria-label="Close error message"
25
  >
26
+ <svg
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ width="16"
29
+ height="16"
30
+ viewBox="0 0 24 24"
31
+ fill="none"
32
+ stroke="currentColor"
33
+ strokeWidth="2"
34
+ strokeLinecap="round"
35
+ strokeLinejoin="round"
36
+ >
37
  <line x1="18" y1="6" x2="6" y2="18"></line>
38
  <line x1="6" y1="6" x2="18" y2="18"></line>
39
  </svg>
 
42
  {message}
43
  </div>
44
  );
45
+ }
src/components/ui/form.tsx CHANGED
@@ -1,8 +1,8 @@
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
  ControllerProps,
@@ -10,27 +10,27 @@ import {
10
  FieldValues,
11
  FormProvider,
12
  useFormContext,
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>) => {
@@ -38,21 +38,21 @@ const FormField = <
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,
@@ -61,36 +61,36 @@ const useFormField = () => {
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
@@ -99,15 +99,16 @@ const FormLabel = React.forwardRef<
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
@@ -121,15 +122,15 @@ const FormControl = React.forwardRef<
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
@@ -138,19 +139,19 @@ const FormDescription = React.forwardRef<
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 (
@@ -162,9 +163,9 @@ const FormMessage = React.forwardRef<
162
  >
163
  {body}
164
  </p>
165
- )
166
- })
167
- FormMessage.displayName = "FormMessage"
168
 
169
  export {
170
  useFormField,
@@ -175,4 +176,4 @@ export {
175
  FormDescription,
176
  FormMessage,
177
  FormField,
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
  ControllerProps,
 
10
  FieldValues,
11
  FormProvider,
12
  useFormContext,
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>) => {
 
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,
 
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
 
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 } =
111
+ useFormField();
112
 
113
  return (
114
  <Slot
 
122
  aria-invalid={!!error}
123
  {...props}
124
  />
125
+ );
126
+ });
127
+ FormControl.displayName = "FormControl";
128
 
129
  const FormDescription = React.forwardRef<
130
  HTMLParagraphElement,
131
  React.HTMLAttributes<HTMLParagraphElement>
132
  >(({ className, ...props }, ref) => {
133
+ const { formDescriptionId } = useFormField();
134
 
135
  return (
136
  <p
 
139
  className={cn("text-sm text-muted-foreground", className)}
140
  {...props}
141
  />
142
+ );
143
+ });
144
+ FormDescription.displayName = "FormDescription";
145
 
146
  const FormMessage = React.forwardRef<
147
  HTMLParagraphElement,
148
  React.HTMLAttributes<HTMLParagraphElement>
149
  >(({ className, children, ...props }, ref) => {
150
+ const { error, formMessageId } = useFormField();
151
+ const body = error ? String(error?.message) : children;
152
 
153
  if (!body) {
154
+ return null;
155
  }
156
 
157
  return (
 
163
  >
164
  {body}
165
  </p>
166
+ );
167
+ });
168
+ FormMessage.displayName = "FormMessage";
169
 
170
  export {
171
  useFormField,
 
176
  FormDescription,
177
  FormMessage,
178
  FormField,
179
+ };
src/components/ui/fullscreen-toggle.tsx CHANGED
@@ -1,16 +1,25 @@
1
- "use client"
2
 
3
- import { Maximize2, Minimize2 } from "lucide-react"
4
- import { Button } from "@/components/ui/button"
5
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
 
 
 
 
 
6
 
7
  interface FullscreenToggleProps {
8
- isFullScreen: boolean
9
- onClick: () => void
10
- className?: string
11
  }
12
 
13
- export function FullscreenToggle({ isFullScreen, onClick, className = "" }: FullscreenToggleProps) {
 
 
 
 
14
  return (
15
  <TooltipProvider>
16
  <Tooltip>
@@ -31,9 +40,9 @@ export function FullscreenToggle({ isFullScreen, onClick, className = "" }: Full
31
  </Button>
32
  </TooltipTrigger>
33
  <TooltipContent className="bg-novita-gray text-white">
34
- <p>{isFullScreen ? 'Exit full screen' : 'Enter full screen'}</p>
35
  </TooltipContent>
36
  </Tooltip>
37
  </TooltipProvider>
38
- )
39
- }
 
1
+ "use client";
2
 
3
+ import { Maximize2, Minimize2 } from "lucide-react";
4
+ import { Button } from "@/components/ui/button";
5
+ import {
6
+ Tooltip,
7
+ TooltipContent,
8
+ TooltipProvider,
9
+ TooltipTrigger,
10
+ } from "@/components/ui/tooltip";
11
 
12
  interface FullscreenToggleProps {
13
+ isFullScreen: boolean;
14
+ onClick: () => void;
15
+ className?: string;
16
  }
17
 
18
+ export function FullscreenToggle({
19
+ isFullScreen,
20
+ onClick,
21
+ className = "",
22
+ }: FullscreenToggleProps) {
23
  return (
24
  <TooltipProvider>
25
  <Tooltip>
 
40
  </Button>
41
  </TooltipTrigger>
42
  <TooltipContent className="bg-novita-gray text-white">
43
+ <p>{isFullScreen ? "Exit full screen" : "Enter full screen"}</p>
44
  </TooltipContent>
45
  </Tooltip>
46
  </TooltipProvider>
47
+ );
48
+ }
src/components/ui/hover-card.tsx CHANGED
@@ -1,13 +1,13 @@
1
- "use client"
2
 
3
- import * as React from "react"
4
- import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
5
 
6
- import { cn } from "@/lib/utils"
7
 
8
- const HoverCard = HoverCardPrimitive.Root
9
 
10
- const HoverCardTrigger = HoverCardPrimitive.Trigger
11
 
12
  const HoverCardContent = React.forwardRef<
13
  React.ElementRef<typeof HoverCardPrimitive.Content>,
@@ -19,11 +19,11 @@ const HoverCardContent = React.forwardRef<
19
  sideOffset={sideOffset}
20
  className={cn(
21
  "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
22
- className
23
  )}
24
  {...props}
25
  />
26
- ))
27
- HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
28
 
29
- export { HoverCard, HoverCardTrigger, HoverCardContent }
 
1
+ "use client";
2
 
3
+ import * as React from "react";
4
+ import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
5
 
6
+ import { cn } from "@/lib/utils";
7
 
8
+ const HoverCard = HoverCardPrimitive.Root;
9
 
10
+ const HoverCardTrigger = HoverCardPrimitive.Trigger;
11
 
12
  const HoverCardContent = React.forwardRef<
13
  React.ElementRef<typeof HoverCardPrimitive.Content>,
 
19
  sideOffset={sideOffset}
20
  className={cn(
21
  "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
22
+ className,
23
  )}
24
  {...props}
25
  />
26
+ ));
27
+ HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
28
 
29
+ export { HoverCard, HoverCardTrigger, HoverCardContent };
src/components/ui/icons.tsx CHANGED
@@ -1,42 +1,82 @@
1
  export function MinimizeIcon() {
2
  return (
3
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
 
 
 
 
 
 
 
 
 
 
4
  <path d="M8 3v3a2 2 0 0 1-2 2H3"></path>
5
  <path d="M21 8h-3a2 2 0 0 1-2-2V3"></path>
6
  <path d="M3 16h3a2 2 0 0 1 2 2v3"></path>
7
  <path d="M16 21v-3a2 2 0 0 1 2-2h3"></path>
8
  </svg>
9
- )
10
  }
11
 
12
  export function MaximizeIcon() {
13
  return (
14
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
 
 
 
 
 
 
 
 
 
 
15
  <polyline points="15 3 21 3 21 9"></polyline>
16
  <polyline points="9 21 3 21 3 15"></polyline>
17
  <line x1="21" y1="3" x2="14" y2="10"></line>
18
  <line x1="3" y1="21" x2="10" y2="14"></line>
19
  </svg>
20
- )
21
  }
22
 
23
  export function DownloadIcon() {
24
  return (
25
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
 
 
 
 
 
 
 
 
 
 
26
  <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
27
  <polyline points="7 10 12 15 17 10"></polyline>
28
  <line x1="12" y1="15" x2="12" y2="3"></line>
29
  </svg>
30
- )
31
  }
32
 
33
  export function RefreshIcon() {
34
  return (
35
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
 
 
 
 
 
 
 
 
 
 
36
  <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path>
37
  <path d="M21 3v5h-5"></path>
38
  <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path>
39
  <path d="M3 21v-5h5"></path>
40
  </svg>
41
- )
42
- }
 
1
  export function MinimizeIcon() {
2
  return (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ width="16"
6
+ height="16"
7
+ viewBox="0 0 24 24"
8
+ fill="none"
9
+ stroke="currentColor"
10
+ strokeWidth="2"
11
+ strokeLinecap="round"
12
+ strokeLinejoin="round"
13
+ >
14
  <path d="M8 3v3a2 2 0 0 1-2 2H3"></path>
15
  <path d="M21 8h-3a2 2 0 0 1-2-2V3"></path>
16
  <path d="M3 16h3a2 2 0 0 1 2 2v3"></path>
17
  <path d="M16 21v-3a2 2 0 0 1 2-2h3"></path>
18
  </svg>
19
+ );
20
  }
21
 
22
  export function MaximizeIcon() {
23
  return (
24
+ <svg
25
+ xmlns="http://www.w3.org/2000/svg"
26
+ width="16"
27
+ height="16"
28
+ viewBox="0 0 24 24"
29
+ fill="none"
30
+ stroke="currentColor"
31
+ strokeWidth="2"
32
+ strokeLinecap="round"
33
+ strokeLinejoin="round"
34
+ >
35
  <polyline points="15 3 21 3 21 9"></polyline>
36
  <polyline points="9 21 3 21 3 15"></polyline>
37
  <line x1="21" y1="3" x2="14" y2="10"></line>
38
  <line x1="3" y1="21" x2="10" y2="14"></line>
39
  </svg>
40
+ );
41
  }
42
 
43
  export function DownloadIcon() {
44
  return (
45
+ <svg
46
+ xmlns="http://www.w3.org/2000/svg"
47
+ width="16"
48
+ height="16"
49
+ viewBox="0 0 24 24"
50
+ fill="none"
51
+ stroke="currentColor"
52
+ strokeWidth="2"
53
+ strokeLinecap="round"
54
+ strokeLinejoin="round"
55
+ >
56
  <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
57
  <polyline points="7 10 12 15 17 10"></polyline>
58
  <line x1="12" y1="15" x2="12" y2="3"></line>
59
  </svg>
60
+ );
61
  }
62
 
63
  export function RefreshIcon() {
64
  return (
65
+ <svg
66
+ xmlns="http://www.w3.org/2000/svg"
67
+ width="16"
68
+ height="16"
69
+ viewBox="0 0 24 24"
70
+ fill="none"
71
+ stroke="currentColor"
72
+ strokeWidth="2"
73
+ strokeLinecap="round"
74
+ strokeLinejoin="round"
75
+ >
76
  <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path>
77
  <path d="M21 3v5h-5"></path>
78
  <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path>
79
  <path d="M3 21v-5h5"></path>
80
  </svg>
81
+ );
82
+ }
src/components/ui/input-otp.tsx CHANGED
@@ -1,10 +1,10 @@
1
- "use client"
2
 
3
- import * as React from "react"
4
- import { OTPInput, OTPInputContext } from "input-otp"
5
- import { Dot } from "lucide-react"
6
 
7
- import { cn } from "@/lib/utils"
8
 
9
  const InputOTP = React.forwardRef<
10
  React.ElementRef<typeof OTPInput>,
@@ -14,28 +14,28 @@ const InputOTP = React.forwardRef<
14
  ref={ref}
15
  containerClassName={cn(
16
  "flex items-center gap-2 has-[:disabled]:opacity-50",
17
- containerClassName
18
  )}
19
  className={cn("disabled:cursor-not-allowed", className)}
20
  {...props}
21
  />
22
- ))
23
- InputOTP.displayName = "InputOTP"
24
 
25
  const InputOTPGroup = React.forwardRef<
26
  React.ElementRef<"div">,
27
  React.ComponentPropsWithoutRef<"div">
28
  >(({ className, ...props }, ref) => (
29
  <div ref={ref} className={cn("flex items-center", className)} {...props} />
30
- ))
31
- InputOTPGroup.displayName = "InputOTPGroup"
32
 
33
  const InputOTPSlot = React.forwardRef<
34
  React.ElementRef<"div">,
35
  React.ComponentPropsWithoutRef<"div"> & { index: number }
36
  >(({ index, className, ...props }, ref) => {
37
- const inputOTPContext = React.useContext(OTPInputContext)
38
- const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
39
 
40
  return (
41
  <div
@@ -43,7 +43,7 @@ const InputOTPSlot = React.forwardRef<
43
  className={cn(
44
  "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
45
  isActive && "z-10 ring-2 ring-ring ring-offset-background",
46
- className
47
  )}
48
  {...props}
49
  >
@@ -54,9 +54,9 @@ const InputOTPSlot = React.forwardRef<
54
  </div>
55
  )}
56
  </div>
57
- )
58
- })
59
- InputOTPSlot.displayName = "InputOTPSlot"
60
 
61
  const InputOTPSeparator = React.forwardRef<
62
  React.ElementRef<"div">,
@@ -65,7 +65,7 @@ const InputOTPSeparator = React.forwardRef<
65
  <div ref={ref} role="separator" {...props}>
66
  <Dot />
67
  </div>
68
- ))
69
- InputOTPSeparator.displayName = "InputOTPSeparator"
70
 
71
- export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
 
1
+ "use client";
2
 
3
+ import * as React from "react";
4
+ import { OTPInput, OTPInputContext } from "input-otp";
5
+ import { Dot } from "lucide-react";
6
 
7
+ import { cn } from "@/lib/utils";
8
 
9
  const InputOTP = React.forwardRef<
10
  React.ElementRef<typeof OTPInput>,
 
14
  ref={ref}
15
  containerClassName={cn(
16
  "flex items-center gap-2 has-[:disabled]:opacity-50",
17
+ containerClassName,
18
  )}
19
  className={cn("disabled:cursor-not-allowed", className)}
20
  {...props}
21
  />
22
+ ));
23
+ InputOTP.displayName = "InputOTP";
24
 
25
  const InputOTPGroup = React.forwardRef<
26
  React.ElementRef<"div">,
27
  React.ComponentPropsWithoutRef<"div">
28
  >(({ className, ...props }, ref) => (
29
  <div ref={ref} className={cn("flex items-center", className)} {...props} />
30
+ ));
31
+ InputOTPGroup.displayName = "InputOTPGroup";
32
 
33
  const InputOTPSlot = React.forwardRef<
34
  React.ElementRef<"div">,
35
  React.ComponentPropsWithoutRef<"div"> & { index: number }
36
  >(({ index, className, ...props }, ref) => {
37
+ const inputOTPContext = React.useContext(OTPInputContext);
38
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
39
 
40
  return (
41
  <div
 
43
  className={cn(
44
  "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
45
  isActive && "z-10 ring-2 ring-ring ring-offset-background",
46
+ className,
47
  )}
48
  {...props}
49
  >
 
54
  </div>
55
  )}
56
  </div>
57
+ );
58
+ });
59
+ InputOTPSlot.displayName = "InputOTPSlot";
60
 
61
  const InputOTPSeparator = React.forwardRef<
62
  React.ElementRef<"div">,
 
65
  <div ref={ref} role="separator" {...props}>
66
  <Dot />
67
  </div>
68
+ ));
69
+ InputOTPSeparator.displayName = "InputOTPSeparator";
70
 
71
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };