@getjack/jack 0.1.19 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/package.json +5 -2
  2. package/src/commands/down.ts +11 -1
  3. package/src/commands/init.ts +19 -6
  4. package/src/commands/new.ts +56 -4
  5. package/src/commands/publish.ts +1 -1
  6. package/src/lib/agents.ts +3 -1
  7. package/src/lib/auth/ensure-auth.test.ts +3 -3
  8. package/src/lib/control-plane.ts +15 -1
  9. package/src/lib/deploy-upload.ts +26 -1
  10. package/src/lib/hooks.ts +232 -1
  11. package/src/lib/managed-deploy.ts +13 -6
  12. package/src/lib/managed-down.ts +66 -45
  13. package/src/lib/progress.ts +76 -5
  14. package/src/lib/project-list.ts +6 -1
  15. package/src/lib/project-operations.ts +21 -31
  16. package/src/lib/project-resolver.ts +1 -1
  17. package/src/lib/zip-packager.ts +36 -7
  18. package/src/templates/index.ts +1 -1
  19. package/src/templates/types.ts +16 -0
  20. package/templates/CLAUDE.md +172 -5
  21. package/templates/miniapp/.jack.json +1 -3
  22. package/templates/saas/.jack.json +154 -0
  23. package/templates/saas/AGENTS.md +333 -0
  24. package/templates/saas/bun.lock +925 -0
  25. package/templates/saas/components.json +21 -0
  26. package/templates/saas/index.html +12 -0
  27. package/templates/saas/package.json +75 -0
  28. package/templates/saas/public/icon.png +0 -0
  29. package/templates/saas/public/og.png +0 -0
  30. package/templates/saas/schema.sql +73 -0
  31. package/templates/saas/src/auth.ts +77 -0
  32. package/templates/saas/src/client/App.tsx +63 -0
  33. package/templates/saas/src/client/components/ProtectedRoute.tsx +29 -0
  34. package/templates/saas/src/client/components/ThemeToggle.tsx +32 -0
  35. package/templates/saas/src/client/components/ui/accordion.tsx +62 -0
  36. package/templates/saas/src/client/components/ui/alert-dialog.tsx +133 -0
  37. package/templates/saas/src/client/components/ui/alert.tsx +60 -0
  38. package/templates/saas/src/client/components/ui/aspect-ratio.tsx +9 -0
  39. package/templates/saas/src/client/components/ui/avatar.tsx +39 -0
  40. package/templates/saas/src/client/components/ui/badge.tsx +39 -0
  41. package/templates/saas/src/client/components/ui/breadcrumb.tsx +102 -0
  42. package/templates/saas/src/client/components/ui/button-group.tsx +78 -0
  43. package/templates/saas/src/client/components/ui/button.tsx +60 -0
  44. package/templates/saas/src/client/components/ui/card.tsx +75 -0
  45. package/templates/saas/src/client/components/ui/carousel.tsx +228 -0
  46. package/templates/saas/src/client/components/ui/chart.tsx +326 -0
  47. package/templates/saas/src/client/components/ui/checkbox.tsx +29 -0
  48. package/templates/saas/src/client/components/ui/collapsible.tsx +19 -0
  49. package/templates/saas/src/client/components/ui/command.tsx +159 -0
  50. package/templates/saas/src/client/components/ui/context-menu.tsx +224 -0
  51. package/templates/saas/src/client/components/ui/dialog.tsx +127 -0
  52. package/templates/saas/src/client/components/ui/drawer.tsx +124 -0
  53. package/templates/saas/src/client/components/ui/dropdown-menu.tsx +226 -0
  54. package/templates/saas/src/client/components/ui/empty.tsx +94 -0
  55. package/templates/saas/src/client/components/ui/field.tsx +232 -0
  56. package/templates/saas/src/client/components/ui/form.tsx +152 -0
  57. package/templates/saas/src/client/components/ui/hover-card.tsx +38 -0
  58. package/templates/saas/src/client/components/ui/input-group.tsx +158 -0
  59. package/templates/saas/src/client/components/ui/input-otp.tsx +68 -0
  60. package/templates/saas/src/client/components/ui/input.tsx +21 -0
  61. package/templates/saas/src/client/components/ui/item.tsx +172 -0
  62. package/templates/saas/src/client/components/ui/kbd.tsx +28 -0
  63. package/templates/saas/src/client/components/ui/label.tsx +21 -0
  64. package/templates/saas/src/client/components/ui/menubar.tsx +250 -0
  65. package/templates/saas/src/client/components/ui/navigation-menu.tsx +161 -0
  66. package/templates/saas/src/client/components/ui/pagination.tsx +106 -0
  67. package/templates/saas/src/client/components/ui/popover.tsx +42 -0
  68. package/templates/saas/src/client/components/ui/progress.tsx +26 -0
  69. package/templates/saas/src/client/components/ui/radio-group.tsx +45 -0
  70. package/templates/saas/src/client/components/ui/resizable.tsx +46 -0
  71. package/templates/saas/src/client/components/ui/scroll-area.tsx +56 -0
  72. package/templates/saas/src/client/components/ui/select.tsx +173 -0
  73. package/templates/saas/src/client/components/ui/separator.tsx +28 -0
  74. package/templates/saas/src/client/components/ui/sheet.tsx +128 -0
  75. package/templates/saas/src/client/components/ui/sidebar.tsx +694 -0
  76. package/templates/saas/src/client/components/ui/skeleton.tsx +13 -0
  77. package/templates/saas/src/client/components/ui/slider.tsx +58 -0
  78. package/templates/saas/src/client/components/ui/sonner.tsx +38 -0
  79. package/templates/saas/src/client/components/ui/spinner.tsx +16 -0
  80. package/templates/saas/src/client/components/ui/switch.tsx +28 -0
  81. package/templates/saas/src/client/components/ui/table.tsx +90 -0
  82. package/templates/saas/src/client/components/ui/tabs.tsx +54 -0
  83. package/templates/saas/src/client/components/ui/textarea.tsx +18 -0
  84. package/templates/saas/src/client/components/ui/toggle-group.tsx +80 -0
  85. package/templates/saas/src/client/components/ui/toggle.tsx +44 -0
  86. package/templates/saas/src/client/components/ui/tooltip.tsx +57 -0
  87. package/templates/saas/src/client/hooks/use-mobile.ts +19 -0
  88. package/templates/saas/src/client/hooks/useAuth.ts +14 -0
  89. package/templates/saas/src/client/hooks/useSubscription.ts +86 -0
  90. package/templates/saas/src/client/index.css +165 -0
  91. package/templates/saas/src/client/lib/auth-client.ts +7 -0
  92. package/templates/saas/src/client/lib/plans.ts +82 -0
  93. package/templates/saas/src/client/lib/utils.ts +6 -0
  94. package/templates/saas/src/client/main.tsx +15 -0
  95. package/templates/saas/src/client/pages/DashboardPage.tsx +394 -0
  96. package/templates/saas/src/client/pages/ForgotPasswordPage.tsx +153 -0
  97. package/templates/saas/src/client/pages/HomePage.tsx +285 -0
  98. package/templates/saas/src/client/pages/LoginPage.tsx +169 -0
  99. package/templates/saas/src/client/pages/PricingPage.tsx +467 -0
  100. package/templates/saas/src/client/pages/ResetPasswordPage.tsx +200 -0
  101. package/templates/saas/src/client/pages/SignupPage.tsx +192 -0
  102. package/templates/saas/src/index.ts +208 -0
  103. package/templates/saas/tsconfig.json +18 -0
  104. package/templates/saas/vite.config.ts +14 -0
  105. package/templates/saas/wrangler.jsonc +20 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/client/index.css",
9
+ "baseColor": "stone",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "aliases": {
15
+ "components": "@/components",
16
+ "utils": "@/lib/utils",
17
+ "ui": "@/components/ui",
18
+ "lib": "@/lib",
19
+ "hooks": "@/hooks"
20
+ }
21
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>jack-template</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/client/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "jack-template",
3
+ "type": "module",
4
+ "scripts": {
5
+ "dev": "vite",
6
+ "build": "tsc && vite build",
7
+ "preview": "vite preview",
8
+ "typecheck": "tsc --noEmit",
9
+ "db:migrate": "wrangler d1 execute DB --file=schema.sql --remote",
10
+ "db:migrate:local": "wrangler d1 execute DB --file=schema.sql --local"
11
+ },
12
+ "dependencies": {
13
+ "@better-auth/stripe": "^1.4.15",
14
+ "clsx": "^2.1.1",
15
+ "@hookform/resolvers": "^5.2.2",
16
+ "@radix-ui/react-accordion": "^1.2.12",
17
+ "@radix-ui/react-alert-dialog": "^1.1.15",
18
+ "@radix-ui/react-aspect-ratio": "^1.1.8",
19
+ "@radix-ui/react-avatar": "^1.1.11",
20
+ "@radix-ui/react-checkbox": "^1.3.3",
21
+ "@radix-ui/react-collapsible": "^1.1.12",
22
+ "@radix-ui/react-context-menu": "^2.2.16",
23
+ "@radix-ui/react-dialog": "^1.1.15",
24
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
25
+ "@radix-ui/react-hover-card": "^1.1.15",
26
+ "@radix-ui/react-label": "^2.1.8",
27
+ "@radix-ui/react-menubar": "^1.1.16",
28
+ "@radix-ui/react-navigation-menu": "^1.2.14",
29
+ "@radix-ui/react-popover": "^1.1.15",
30
+ "@radix-ui/react-progress": "^1.1.8",
31
+ "@radix-ui/react-radio-group": "^1.3.8",
32
+ "@radix-ui/react-scroll-area": "^1.2.10",
33
+ "@radix-ui/react-select": "^2.2.6",
34
+ "@radix-ui/react-separator": "^1.1.8",
35
+ "@radix-ui/react-slider": "^1.3.6",
36
+ "@radix-ui/react-slot": "^1.2.4",
37
+ "@radix-ui/react-switch": "^1.2.6",
38
+ "@radix-ui/react-tabs": "^1.1.13",
39
+ "@radix-ui/react-toggle": "^1.1.10",
40
+ "@radix-ui/react-toggle-group": "^1.1.11",
41
+ "@radix-ui/react-tooltip": "^1.2.8",
42
+ "better-auth": "^1.4.15",
43
+ "class-variance-authority": "^0.7.1",
44
+ "cmdk": "^1.1.1",
45
+ "embla-carousel-react": "^8.6.0",
46
+ "kysely": "^0.28.0",
47
+ "kysely-d1": "^0.4.0",
48
+ "hono": "^4.6.0",
49
+ "input-otp": "^1.4.2",
50
+ "lucide-react": "^0.562.0",
51
+ "next-themes": "^0.4.6",
52
+ "react": "^18.3.1",
53
+ "react-dom": "^18.3.1",
54
+ "react-hook-form": "^7.71.1",
55
+ "react-resizable-panels": "^4.4.1",
56
+ "recharts": "2.15.4",
57
+ "sonner": "^2.0.7",
58
+ "stripe": "^20.0.0",
59
+ "tailwind-merge": "^3.0.2",
60
+ "tw-animate-css": "^1.3.4",
61
+ "vaul": "^1.1.2",
62
+ "zod": "^4.3.5"
63
+ },
64
+ "devDependencies": {
65
+ "@cloudflare/vite-plugin": "^1.0.0",
66
+ "@cloudflare/workers-types": "^4.20241205.0",
67
+ "@tailwindcss/vite": "^4.0.0",
68
+ "@types/react": "^18.3.0",
69
+ "@types/react-dom": "^18.3.0",
70
+ "@vitejs/plugin-react": "^4.3.0",
71
+ "tailwindcss": "^4.0.0",
72
+ "typescript": "^5.6.0",
73
+ "vite": "^6.0.0"
74
+ }
75
+ }
Binary file
Binary file
@@ -0,0 +1,73 @@
1
+ -- Better Auth core tables (using Better Auth's expected schema)
2
+ -- Column names use camelCase to match Better Auth's defaults
3
+
4
+ CREATE TABLE IF NOT EXISTS user (
5
+ id TEXT PRIMARY KEY,
6
+ email TEXT UNIQUE NOT NULL,
7
+ emailVerified INTEGER DEFAULT 0,
8
+ name TEXT,
9
+ image TEXT,
10
+ stripeCustomerId TEXT,
11
+ createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
12
+ updatedAt TEXT DEFAULT CURRENT_TIMESTAMP
13
+ );
14
+
15
+ CREATE TABLE IF NOT EXISTS session (
16
+ id TEXT PRIMARY KEY,
17
+ userId TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE,
18
+ token TEXT UNIQUE NOT NULL,
19
+ expiresAt TEXT NOT NULL,
20
+ ipAddress TEXT,
21
+ userAgent TEXT,
22
+ createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
23
+ updatedAt TEXT DEFAULT CURRENT_TIMESTAMP
24
+ );
25
+
26
+ CREATE TABLE IF NOT EXISTS account (
27
+ id TEXT PRIMARY KEY,
28
+ userId TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE,
29
+ accountId TEXT NOT NULL,
30
+ providerId TEXT NOT NULL,
31
+ accessToken TEXT,
32
+ refreshToken TEXT,
33
+ accessTokenExpiresAt TEXT,
34
+ refreshTokenExpiresAt TEXT,
35
+ scope TEXT,
36
+ idToken TEXT,
37
+ password TEXT,
38
+ createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
39
+ updatedAt TEXT DEFAULT CURRENT_TIMESTAMP
40
+ );
41
+
42
+ CREATE TABLE IF NOT EXISTS verification (
43
+ id TEXT PRIMARY KEY,
44
+ identifier TEXT NOT NULL,
45
+ value TEXT NOT NULL,
46
+ expiresAt TEXT NOT NULL,
47
+ createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
48
+ updatedAt TEXT DEFAULT CURRENT_TIMESTAMP
49
+ );
50
+
51
+ -- Stripe plugin subscription table
52
+ CREATE TABLE IF NOT EXISTS subscription (
53
+ id TEXT PRIMARY KEY,
54
+ plan TEXT NOT NULL,
55
+ referenceId TEXT NOT NULL,
56
+ stripeCustomerId TEXT,
57
+ stripeSubscriptionId TEXT,
58
+ status TEXT DEFAULT 'incomplete',
59
+ periodStart TEXT,
60
+ periodEnd TEXT,
61
+ cancelAtPeriodEnd INTEGER DEFAULT 0,
62
+ cancelAt TEXT,
63
+ canceledAt TEXT,
64
+ endedAt TEXT,
65
+ seats INTEGER DEFAULT 1,
66
+ trialStart TEXT,
67
+ trialEnd TEXT,
68
+ createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
69
+ updatedAt TEXT DEFAULT CURRENT_TIMESTAMP
70
+ );
71
+
72
+ CREATE INDEX IF NOT EXISTS idx_subscription_reference ON subscription(referenceId);
73
+ CREATE INDEX IF NOT EXISTS idx_subscription_stripe ON subscription(stripeSubscriptionId);
@@ -0,0 +1,77 @@
1
+ import { betterAuth } from "better-auth";
2
+ import { stripe } from "@better-auth/stripe";
3
+ import Stripe from "stripe";
4
+ import { Kysely } from "kysely";
5
+ import { D1Dialect } from "kysely-d1";
6
+
7
+ // Env type is defined in index.ts and passed from the worker
8
+ type Env = {
9
+ DB: D1Database;
10
+ BETTER_AUTH_SECRET: string;
11
+ STRIPE_SECRET_KEY: string;
12
+ STRIPE_WEBHOOK_SECRET?: string; // Optional until webhook is configured
13
+ STRIPE_PRO_PRICE_ID?: string;
14
+ STRIPE_ENTERPRISE_PRICE_ID?: string;
15
+ };
16
+
17
+ export function createAuth(env: Env) {
18
+ const stripeClient = env.STRIPE_SECRET_KEY ? new Stripe(env.STRIPE_SECRET_KEY) : null;
19
+
20
+ // Build plugins array - Stripe plugin only if we have the API key
21
+ const plugins = [];
22
+
23
+ if (env.STRIPE_SECRET_KEY && stripeClient) {
24
+ // Validate required Stripe configuration
25
+ const missingConfig: string[] = [];
26
+ if (!env.STRIPE_PRO_PRICE_ID) missingConfig.push("STRIPE_PRO_PRICE_ID");
27
+ if (!env.STRIPE_ENTERPRISE_PRICE_ID) missingConfig.push("STRIPE_ENTERPRISE_PRICE_ID");
28
+ if (!env.STRIPE_WEBHOOK_SECRET) missingConfig.push("STRIPE_WEBHOOK_SECRET");
29
+
30
+ if (missingConfig.length > 0) {
31
+ console.error(`[Stripe] Missing required config: ${missingConfig.join(", ")}`);
32
+ console.error("[Stripe] Subscriptions will not work correctly. Set these secrets via: jack secrets set <KEY> <value>");
33
+ }
34
+
35
+ // Only enable Stripe plugin if we have the minimum required config
36
+ if (env.STRIPE_WEBHOOK_SECRET) {
37
+ plugins.push(
38
+ stripe({
39
+ stripeClient,
40
+ stripeWebhookSecret: env.STRIPE_WEBHOOK_SECRET,
41
+ createCustomerOnSignUp: true,
42
+ subscription: {
43
+ enabled: true,
44
+ plans: [
45
+ { name: "pro", priceId: env.STRIPE_PRO_PRICE_ID || "" },
46
+ { name: "enterprise", priceId: env.STRIPE_ENTERPRISE_PRICE_ID || "" },
47
+ ],
48
+ },
49
+ }),
50
+ );
51
+ } else {
52
+ console.error("[Stripe] Plugin DISABLED - STRIPE_WEBHOOK_SECRET is required for reliable subscription sync");
53
+ }
54
+ }
55
+
56
+ // Use Kysely with D1 dialect - Better Auth uses Kysely internally for D1
57
+ const db = new Kysely<any>({
58
+ dialect: new D1Dialect({ database: env.DB }),
59
+ });
60
+
61
+ return betterAuth({
62
+ database: {
63
+ db,
64
+ type: "sqlite",
65
+ },
66
+ emailAndPassword: {
67
+ enabled: true,
68
+ sendResetPassword: async ({ user, url }) => {
69
+ // TODO: Configure email sending (Resend, SendGrid, etc.)
70
+ // For now, log the reset URL for development
71
+ console.log(`[Password Reset] User: ${user.email}, URL: ${url}`);
72
+ },
73
+ },
74
+ secret: env.BETTER_AUTH_SECRET,
75
+ plugins,
76
+ });
77
+ }
@@ -0,0 +1,63 @@
1
+ import { useState, useEffect } from "react";
2
+
3
+ // Page imports
4
+ import HomePage from "./pages/HomePage";
5
+ import LoginPage from "./pages/LoginPage";
6
+ import SignupPage from "./pages/SignupPage";
7
+ import PricingPage from "./pages/PricingPage";
8
+ import DashboardPage from "./pages/DashboardPage";
9
+ import ForgotPasswordPage from "./pages/ForgotPasswordPage";
10
+ import ResetPasswordPage from "./pages/ResetPasswordPage";
11
+ import { ProtectedRoute } from "./components/ProtectedRoute";
12
+
13
+ type Route = "/" | "/login" | "/signup" | "/pricing" | "/dashboard" | "/forgot-password" | "/reset-password";
14
+
15
+ function getRouteFromHash(): Route {
16
+ const hash = window.location.hash.split("?")[0].slice(1) || "/";
17
+ const validRoutes: Route[] = ["/", "/login", "/signup", "/pricing", "/dashboard", "/forgot-password", "/reset-password"];
18
+ return validRoutes.includes(hash as Route) ? (hash as Route) : "/";
19
+ }
20
+
21
+ export default function App() {
22
+ const [route, setRoute] = useState<Route>(getRouteFromHash);
23
+
24
+ useEffect(() => {
25
+ const handleHashChange = () => {
26
+ setRoute(getRouteFromHash());
27
+ };
28
+
29
+ window.addEventListener("hashchange", handleHashChange);
30
+ return () => window.removeEventListener("hashchange", handleHashChange);
31
+ }, []);
32
+
33
+ const navigate = (newRoute: Route) => {
34
+ window.location.hash = newRoute;
35
+ };
36
+
37
+ const renderPage = () => {
38
+ switch (route) {
39
+ case "/":
40
+ return <HomePage navigate={navigate} />;
41
+ case "/login":
42
+ return <LoginPage navigate={navigate} />;
43
+ case "/signup":
44
+ return <SignupPage navigate={navigate} />;
45
+ case "/pricing":
46
+ return <PricingPage navigate={navigate} />;
47
+ case "/forgot-password":
48
+ return <ForgotPasswordPage navigate={navigate} />;
49
+ case "/reset-password":
50
+ return <ResetPasswordPage navigate={navigate} />;
51
+ case "/dashboard":
52
+ return (
53
+ <ProtectedRoute navigate={navigate}>
54
+ <DashboardPage navigate={navigate} />
55
+ </ProtectedRoute>
56
+ );
57
+ default:
58
+ return <HomePage navigate={navigate} />;
59
+ }
60
+ };
61
+
62
+ return <div className="min-h-screen">{renderPage()}</div>;
63
+ }
@@ -0,0 +1,29 @@
1
+ import { ReactNode, useEffect } from "react";
2
+ import { authClient } from "../lib/auth-client";
3
+
4
+ type Route = "/" | "/login" | "/signup" | "/pricing" | "/dashboard" | "/forgot-password" | "/reset-password";
5
+
6
+ interface ProtectedRouteProps {
7
+ children: ReactNode;
8
+ navigate: (path: Route) => void;
9
+ }
10
+
11
+ export function ProtectedRoute({ children, navigate }: ProtectedRouteProps) {
12
+ const { data: session, isPending } = authClient.useSession();
13
+
14
+ useEffect(() => {
15
+ if (!isPending && !session) {
16
+ navigate("/login" as Route);
17
+ }
18
+ }, [session, isPending, navigate]);
19
+
20
+ if (isPending) {
21
+ return <div className="flex items-center justify-center min-h-screen">Loading...</div>;
22
+ }
23
+
24
+ if (!session) {
25
+ return null;
26
+ }
27
+
28
+ return <>{children}</>;
29
+ }
@@ -0,0 +1,32 @@
1
+ import { Moon, Sun } from "lucide-react";
2
+ import { useTheme } from "next-themes";
3
+ import { Button } from "./ui/button";
4
+
5
+ export function ThemeToggle() {
6
+ const { theme, setTheme } = useTheme();
7
+
8
+ const toggleTheme = () => {
9
+ if (theme === "dark") {
10
+ setTheme("light");
11
+ } else if (theme === "light") {
12
+ setTheme("dark");
13
+ } else {
14
+ // system theme - toggle to opposite of current appearance
15
+ const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
16
+ setTheme(isDark ? "light" : "dark");
17
+ }
18
+ };
19
+
20
+ return (
21
+ <Button
22
+ variant="ghost"
23
+ size="icon"
24
+ className="h-9 w-9"
25
+ onClick={toggleTheme}
26
+ >
27
+ <Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
28
+ <Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
29
+ <span className="sr-only">Toggle theme</span>
30
+ </Button>
31
+ );
32
+ }
@@ -0,0 +1,62 @@
1
+ import * as React from "react";
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion";
3
+ import { ChevronDownIcon } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function Accordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
8
+ return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
9
+ }
10
+
11
+ function AccordionItem({
12
+ className,
13
+ ...props
14
+ }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
15
+ return (
16
+ <AccordionPrimitive.Item
17
+ data-slot="accordion-item"
18
+ className={cn("border-b last:border-b-0", className)}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ function AccordionTrigger({
25
+ className,
26
+ children,
27
+ ...props
28
+ }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
29
+ return (
30
+ <AccordionPrimitive.Header className="flex">
31
+ <AccordionPrimitive.Trigger
32
+ data-slot="accordion-trigger"
33
+ className={cn(
34
+ "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
35
+ className,
36
+ )}
37
+ {...props}
38
+ >
39
+ {children}
40
+ <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
41
+ </AccordionPrimitive.Trigger>
42
+ </AccordionPrimitive.Header>
43
+ );
44
+ }
45
+
46
+ function AccordionContent({
47
+ className,
48
+ children,
49
+ ...props
50
+ }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
51
+ return (
52
+ <AccordionPrimitive.Content
53
+ data-slot="accordion-content"
54
+ className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
55
+ {...props}
56
+ >
57
+ <div className={cn("pt-0 pb-4", className)}>{children}</div>
58
+ </AccordionPrimitive.Content>
59
+ );
60
+ }
61
+
62
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
@@ -0,0 +1,133 @@
1
+ import * as React from "react";
2
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
3
+
4
+ import { cn } from "@/lib/utils";
5
+ import { buttonVariants } from "@/components/ui/button";
6
+
7
+ function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
8
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
9
+ }
10
+
11
+ function AlertDialogTrigger({
12
+ ...props
13
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
14
+ return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />;
15
+ }
16
+
17
+ function AlertDialogPortal({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
18
+ return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />;
19
+ }
20
+
21
+ function AlertDialogOverlay({
22
+ className,
23
+ ...props
24
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
25
+ return (
26
+ <AlertDialogPrimitive.Overlay
27
+ data-slot="alert-dialog-overlay"
28
+ className={cn(
29
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ function AlertDialogContent({
38
+ className,
39
+ ...props
40
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
41
+ return (
42
+ <AlertDialogPortal>
43
+ <AlertDialogOverlay />
44
+ <AlertDialogPrimitive.Content
45
+ data-slot="alert-dialog-content"
46
+ className={cn(
47
+ "bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
48
+ className,
49
+ )}
50
+ {...props}
51
+ />
52
+ </AlertDialogPortal>
53
+ );
54
+ }
55
+
56
+ function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) {
57
+ return (
58
+ <div
59
+ data-slot="alert-dialog-header"
60
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
61
+ {...props}
62
+ />
63
+ );
64
+ }
65
+
66
+ function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) {
67
+ return (
68
+ <div
69
+ data-slot="alert-dialog-footer"
70
+ className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
71
+ {...props}
72
+ />
73
+ );
74
+ }
75
+
76
+ function AlertDialogTitle({
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
80
+ return (
81
+ <AlertDialogPrimitive.Title
82
+ data-slot="alert-dialog-title"
83
+ className={cn("text-lg font-semibold", className)}
84
+ {...props}
85
+ />
86
+ );
87
+ }
88
+
89
+ function AlertDialogDescription({
90
+ className,
91
+ ...props
92
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
93
+ return (
94
+ <AlertDialogPrimitive.Description
95
+ data-slot="alert-dialog-description"
96
+ className={cn("text-muted-foreground text-sm", className)}
97
+ {...props}
98
+ />
99
+ );
100
+ }
101
+
102
+ function AlertDialogAction({
103
+ className,
104
+ ...props
105
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
106
+ return <AlertDialogPrimitive.Action className={cn(buttonVariants(), className)} {...props} />;
107
+ }
108
+
109
+ function AlertDialogCancel({
110
+ className,
111
+ ...props
112
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
113
+ return (
114
+ <AlertDialogPrimitive.Cancel
115
+ className={cn(buttonVariants({ variant: "outline" }), className)}
116
+ {...props}
117
+ />
118
+ );
119
+ }
120
+
121
+ export {
122
+ AlertDialog,
123
+ AlertDialogPortal,
124
+ AlertDialogOverlay,
125
+ AlertDialogTrigger,
126
+ AlertDialogContent,
127
+ AlertDialogHeader,
128
+ AlertDialogFooter,
129
+ AlertDialogTitle,
130
+ AlertDialogDescription,
131
+ AlertDialogAction,
132
+ AlertDialogCancel,
133
+ };
@@ -0,0 +1,60 @@
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 px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-card text-card-foreground",
12
+ destructive:
13
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ },
20
+ );
21
+
22
+ function Alert({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
27
+ return (
28
+ <div
29
+ data-slot="alert"
30
+ role="alert"
31
+ className={cn(alertVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
+ return (
39
+ <div
40
+ data-slot="alert-title"
41
+ className={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)}
42
+ {...props}
43
+ />
44
+ );
45
+ }
46
+
47
+ function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
48
+ return (
49
+ <div
50
+ data-slot="alert-description"
51
+ className={cn(
52
+ "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
53
+ className,
54
+ )}
55
+ {...props}
56
+ />
57
+ );
58
+ }
59
+
60
+ export { Alert, AlertTitle, AlertDescription };
@@ -0,0 +1,9 @@
1
+ "use client";
2
+
3
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
4
+
5
+ function AspectRatio({ ...props }: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
6
+ return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />;
7
+ }
8
+
9
+ export { AspectRatio };