@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,165 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ /*
7
+ * Theme: Zed-inspired with blue undertones
8
+ * Uses modern OKLCH color space for perceptually uniform colors
9
+ * Light mode: clean whites with subtle blue undertones
10
+ * Dark mode: blue-tinted blacks
11
+ */
12
+
13
+ :root {
14
+ /* Light mode - clean whites with blue undertones (Zed-inspired) */
15
+ --background: oklch(0.995 0.003 250);
16
+ --foreground: oklch(0.2 0.025 250);
17
+ --card: oklch(1 0 0);
18
+ --card-foreground: oklch(0.2 0.025 250);
19
+ --popover: oklch(1 0 0);
20
+ --popover-foreground: oklch(0.2 0.025 250);
21
+ --primary: oklch(0.55 0.16 230);
22
+ --primary-foreground: oklch(0.98 0.01 230);
23
+ --secondary: oklch(0.95 0.01 250);
24
+ --secondary-foreground: oklch(0.25 0.02 250);
25
+ --muted: oklch(0.96 0.008 250);
26
+ --muted-foreground: oklch(0.45 0.02 250);
27
+ --accent: oklch(0.94 0.015 230);
28
+ --accent-foreground: oklch(0.25 0.02 250);
29
+ --destructive: oklch(0.55 0.22 25);
30
+ --border: oklch(0.9 0.01 250);
31
+ --input: oklch(0.92 0.01 250);
32
+ --ring: oklch(0.55 0.16 230);
33
+ --chart-1: oklch(0.65 0.15 230);
34
+ --chart-2: oklch(0.6 0.14 235);
35
+ --chart-3: oklch(0.55 0.13 240);
36
+ --chart-4: oklch(0.5 0.12 245);
37
+ --chart-5: oklch(0.45 0.11 250);
38
+ --radius: 0.375rem;
39
+ --sidebar: oklch(0.97 0.008 250);
40
+ --sidebar-foreground: oklch(0.2 0.025 250);
41
+ --sidebar-primary: oklch(0.55 0.16 230);
42
+ --sidebar-primary-foreground: oklch(0.98 0.01 230);
43
+ --sidebar-accent: oklch(0.94 0.012 230);
44
+ --sidebar-accent-foreground: oklch(0.25 0.02 250);
45
+ --sidebar-border: oklch(0.9 0.01 250);
46
+ --sidebar-ring: oklch(0.55 0.16 230);
47
+ /* Shadows & interaction */
48
+ --shadow-card: 0 1px 3px oklch(0.2 0.02 250 / 0.08), 0 1px 2px oklch(0.2 0.02 250 / 0.06);
49
+ --shadow-card-hover: 0 4px 12px oklch(0.2 0.02 250 / 0.1), 0 2px 4px oklch(0.2 0.02 250 / 0.06);
50
+ --shadow-popover: 0 4px 16px oklch(0.2 0.02 250 / 0.12), 0 2px 6px oklch(0.2 0.02 250 / 0.08);
51
+ --selection-bg: oklch(0.55 0.16 230 / 0.15);
52
+ --focus-ring: oklch(0.55 0.16 230 / 0.5);
53
+ }
54
+
55
+ .dark {
56
+ /* Dark mode - blue-tinted blacks (Zed-inspired) */
57
+ --background: oklch(0.14 0.02 250);
58
+ --foreground: oklch(0.88 0.01 250);
59
+ --card: oklch(0.18 0.022 250);
60
+ --card-foreground: oklch(0.88 0.01 250);
61
+ --popover: oklch(0.18 0.022 250);
62
+ --popover-foreground: oklch(0.88 0.01 250);
63
+ --primary: oklch(0.7 0.14 220);
64
+ --primary-foreground: oklch(0.15 0.03 220);
65
+ --secondary: oklch(0.22 0.02 250);
66
+ --secondary-foreground: oklch(0.88 0.01 250);
67
+ --muted: oklch(0.22 0.018 250);
68
+ --muted-foreground: oklch(0.55 0.02 250);
69
+ --accent: oklch(0.24 0.025 230);
70
+ --accent-foreground: oklch(0.88 0.01 250);
71
+ --destructive: oklch(0.65 0.2 25);
72
+ --border: oklch(0.28 0.02 250);
73
+ --input: oklch(0.25 0.02 250);
74
+ --ring: oklch(0.7 0.14 220);
75
+ --chart-1: oklch(0.75 0.14 220);
76
+ --chart-2: oklch(0.7 0.13 225);
77
+ --chart-3: oklch(0.65 0.12 230);
78
+ --chart-4: oklch(0.6 0.11 235);
79
+ --chart-5: oklch(0.55 0.1 240);
80
+ --sidebar: oklch(0.16 0.02 250);
81
+ --sidebar-foreground: oklch(0.88 0.01 250);
82
+ --sidebar-primary: oklch(0.7 0.14 220);
83
+ --sidebar-primary-foreground: oklch(0.15 0.03 220);
84
+ --sidebar-accent: oklch(0.24 0.025 230);
85
+ --sidebar-accent-foreground: oklch(0.88 0.01 250);
86
+ --sidebar-border: oklch(0.28 0.02 250);
87
+ --sidebar-ring: oklch(0.7 0.14 220);
88
+ /* Shadows & interaction */
89
+ --shadow-card: 0 1px 3px oklch(0 0 0 / 0.3), 0 1px 2px oklch(0 0 0 / 0.2);
90
+ --shadow-card-hover: 0 4px 12px oklch(0 0 0 / 0.4), 0 2px 4px oklch(0 0 0 / 0.2);
91
+ --shadow-popover: 0 4px 16px oklch(0 0 0 / 0.5), 0 2px 6px oklch(0 0 0 / 0.3);
92
+ --selection-bg: oklch(0.7 0.14 220 / 0.2);
93
+ --focus-ring: oklch(0.7 0.14 220 / 0.6);
94
+ }
95
+
96
+ @theme inline {
97
+ --color-sidebar-ring: var(--sidebar-ring);
98
+ --color-sidebar-border: var(--sidebar-border);
99
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
100
+ --color-sidebar-accent: var(--sidebar-accent);
101
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
102
+ --color-sidebar-primary: var(--sidebar-primary);
103
+ --color-sidebar-foreground: var(--sidebar-foreground);
104
+ --color-sidebar: var(--sidebar);
105
+ --color-chart-5: var(--chart-5);
106
+ --color-chart-4: var(--chart-4);
107
+ --color-chart-3: var(--chart-3);
108
+ --color-chart-2: var(--chart-2);
109
+ --color-chart-1: var(--chart-1);
110
+ --color-ring: var(--ring);
111
+ --color-input: var(--input);
112
+ --color-border: var(--border);
113
+ --color-destructive: var(--destructive);
114
+ --color-accent-foreground: var(--accent-foreground);
115
+ --color-accent: var(--accent);
116
+ --color-muted-foreground: var(--muted-foreground);
117
+ --color-muted: var(--muted);
118
+ --color-secondary-foreground: var(--secondary-foreground);
119
+ --color-secondary: var(--secondary);
120
+ --color-primary-foreground: var(--primary-foreground);
121
+ --color-primary: var(--primary);
122
+ --color-popover-foreground: var(--popover-foreground);
123
+ --color-popover: var(--popover);
124
+ --color-card-foreground: var(--card-foreground);
125
+ --color-card: var(--card);
126
+ --color-foreground: var(--foreground);
127
+ --color-background: var(--background);
128
+ --radius-sm: calc(var(--radius) - 4px);
129
+ --radius-md: calc(var(--radius) - 2px);
130
+ --radius-lg: var(--radius);
131
+ --radius-xl: calc(var(--radius) + 4px);
132
+ --radius-2xl: calc(var(--radius) + 8px);
133
+ --radius-3xl: calc(var(--radius) + 12px);
134
+ --radius-4xl: calc(var(--radius) + 16px);
135
+ }
136
+
137
+ @layer base {
138
+ * {
139
+ @apply border-border outline-ring/50;
140
+ }
141
+ body {
142
+ @apply bg-background text-foreground;
143
+ }
144
+
145
+ /* Text selection */
146
+ ::selection {
147
+ background: var(--selection-bg);
148
+ }
149
+
150
+ /* Enhanced focus states */
151
+ :focus-visible {
152
+ outline: 2px solid var(--focus-ring);
153
+ outline-offset: 2px;
154
+ }
155
+
156
+ /* Smooth transitions for interactive elements */
157
+ button,
158
+ a,
159
+ [role="button"],
160
+ input,
161
+ select,
162
+ textarea {
163
+ @apply transition-colors duration-150;
164
+ }
165
+ }
@@ -0,0 +1,7 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+ import { stripeClient } from "@better-auth/stripe/client";
3
+
4
+ export const authClient = createAuthClient({
5
+ baseURL: window.location.origin,
6
+ plugins: [stripeClient({ subscription: true })],
7
+ });
@@ -0,0 +1,82 @@
1
+ // Centralized plan configuration
2
+ // Edit this file to customize plans for your SaaS
3
+
4
+ export type PlanId = "free" | "pro" | "enterprise";
5
+
6
+ export interface PlanConfig {
7
+ id: PlanId;
8
+ name: string;
9
+ price: string;
10
+ priceMonthly: number; // For calculations, 0 for free
11
+ description: string;
12
+ features: string[];
13
+ highlighted?: boolean;
14
+ }
15
+
16
+ export const plans: PlanConfig[] = [
17
+ {
18
+ id: "free",
19
+ name: "Free",
20
+ price: "$0",
21
+ priceMonthly: 0,
22
+ description: "Perfect for getting started",
23
+ features: [
24
+ "Up to 100 users",
25
+ "Basic analytics",
26
+ "Community support",
27
+ "1 project",
28
+ ],
29
+ },
30
+ {
31
+ id: "pro",
32
+ name: "Pro",
33
+ price: "$19",
34
+ priceMonthly: 19,
35
+ description: "For growing businesses",
36
+ features: [
37
+ "Unlimited users",
38
+ "Advanced analytics",
39
+ "Priority support",
40
+ "Unlimited projects",
41
+ "Custom integrations",
42
+ "API access",
43
+ ],
44
+ highlighted: true,
45
+ },
46
+ {
47
+ id: "enterprise",
48
+ name: "Enterprise",
49
+ price: "$99",
50
+ priceMonthly: 99,
51
+ description: "For large scale operations",
52
+ features: [
53
+ "Everything in Pro",
54
+ "Dedicated support",
55
+ "Custom SLA",
56
+ "On-premise option",
57
+ "Advanced security",
58
+ "Custom contracts",
59
+ ],
60
+ },
61
+ ];
62
+
63
+ // Helper functions
64
+ export function getPlan(id: PlanId | string): PlanConfig | undefined {
65
+ return plans.find((p) => p.id === id);
66
+ }
67
+
68
+ export function getPlanName(id: PlanId | string): string {
69
+ return getPlan(id)?.name ?? "Free";
70
+ }
71
+
72
+ export function isPaidPlan(id: PlanId | string): boolean {
73
+ const plan = getPlan(id);
74
+ return plan ? plan.priceMonthly > 0 : false;
75
+ }
76
+
77
+ export function canUpgrade(from: PlanId | string, to: PlanId | string): boolean {
78
+ const fromPlan = getPlan(from);
79
+ const toPlan = getPlan(to);
80
+ if (!fromPlan || !toPlan) return false;
81
+ return toPlan.priceMonthly > fromPlan.priceMonthly;
82
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,15 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { ThemeProvider } from "next-themes";
4
+ import App from "./App";
5
+ import { Toaster } from "./components/ui/sonner";
6
+ import "./index.css";
7
+
8
+ createRoot(document.getElementById("root")!).render(
9
+ <StrictMode>
10
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
11
+ <App />
12
+ <Toaster />
13
+ </ThemeProvider>
14
+ </StrictMode>,
15
+ );
@@ -0,0 +1,394 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useAuth } from "../hooks/useAuth";
3
+ import { useSubscription } from "../hooks/useSubscription";
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
5
+ import { Button } from "../components/ui/button";
6
+ import { ThemeToggle } from "../components/ThemeToggle";
7
+ import { getPlanName } from "../lib/plans";
8
+
9
+ interface DashboardPageProps {
10
+ navigate: (route: "/" | "/login" | "/signup" | "/pricing" | "/dashboard" | "/forgot-password" | "/reset-password") => void;
11
+ }
12
+
13
+ export default function DashboardPage({ navigate }: DashboardPageProps) {
14
+ const { user, signOut } = useAuth();
15
+ const { plan, isSubscribed, isCancelling, periodEnd, isLoading: isSubscriptionLoading } = useSubscription();
16
+ const [showUpgradeSuccess, setShowUpgradeSuccess] = useState(false);
17
+
18
+ // Check for upgrade success
19
+ useEffect(() => {
20
+ const hash = window.location.hash;
21
+ if (hash.includes("upgraded=true")) {
22
+ setShowUpgradeSuccess(true);
23
+ // Clean up URL
24
+ window.history.replaceState(null, "", window.location.pathname + "#/dashboard");
25
+ }
26
+ }, []);
27
+
28
+ const handleSignOut = async () => {
29
+ await signOut();
30
+ navigate("/");
31
+ };
32
+
33
+ const getPlanBadgeClasses = (planName: string) => {
34
+ switch (planName) {
35
+ case "pro":
36
+ return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200";
37
+ case "enterprise":
38
+ return "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200";
39
+ default:
40
+ return "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200";
41
+ }
42
+ };
43
+
44
+ // Stats based on plan
45
+ const getStats = () => {
46
+ const currentPlan = isSubscribed ? plan : "free";
47
+ switch (currentPlan) {
48
+ case "enterprise":
49
+ return {
50
+ apiCalls: { value: "Unlimited", limit: null },
51
+ projects: { value: "42", limit: "Unlimited" },
52
+ teamMembers: { value: "18", limit: "Unlimited" },
53
+ };
54
+ case "pro":
55
+ return {
56
+ apiCalls: { value: "8,234", limit: "50,000" },
57
+ projects: { value: "7", limit: "Unlimited" },
58
+ teamMembers: { value: "5", limit: "10" },
59
+ };
60
+ default:
61
+ return {
62
+ apiCalls: { value: "847", limit: "1,000" },
63
+ projects: { value: "1", limit: "1" },
64
+ teamMembers: { value: "1", limit: "1" },
65
+ };
66
+ }
67
+ };
68
+
69
+ const stats = getStats();
70
+ const currentPlan = isSubscribed ? plan : "free";
71
+
72
+ return (
73
+ <div className="min-h-screen bg-background">
74
+ {/* Header */}
75
+ <header className="border-b border-border">
76
+ <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
77
+ <div className="flex justify-between items-center h-16">
78
+ <button
79
+ type="button"
80
+ onClick={() => navigate("/")}
81
+ className="text-xl font-bold hover:opacity-80 transition-opacity"
82
+ >
83
+ jack-template
84
+ </button>
85
+ <div className="flex items-center gap-4">
86
+ <span className="text-sm text-muted-foreground">{user?.email}</span>
87
+ <ThemeToggle />
88
+ <Button variant="ghost" size="sm" onClick={handleSignOut}>
89
+ Sign out
90
+ </Button>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </header>
95
+
96
+ <main className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
97
+ {/* Upgrade Success Celebration */}
98
+ {showUpgradeSuccess && (
99
+ <div className="mb-8 relative overflow-hidden rounded-xl bg-gradient-to-r from-green-500 to-emerald-600 p-6 text-white shadow-lg">
100
+ <button
101
+ type="button"
102
+ onClick={() => setShowUpgradeSuccess(false)}
103
+ className="absolute top-4 right-4 text-white/80 hover:text-white"
104
+ >
105
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
106
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
107
+ </svg>
108
+ </button>
109
+ <div className="flex items-start gap-4">
110
+ <div className="flex-shrink-0 w-12 h-12 bg-white/20 rounded-full flex items-center justify-center">
111
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
112
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
113
+ </svg>
114
+ </div>
115
+ <div className="flex-1">
116
+ <h2 className="text-xl font-bold mb-1">You're all set!</h2>
117
+ <p className="text-white/90 mb-4">
118
+ Your upgrade to <span className="font-semibold">{plan === "enterprise" ? "Enterprise" : "Pro"}</span> is complete. Here's what you just unlocked:
119
+ </p>
120
+ <div className="grid gap-2 sm:grid-cols-3 mb-4">
121
+ <div className="bg-white/10 rounded-lg p-3">
122
+ <div className="font-semibold">Unlimited Projects</div>
123
+ <div className="text-sm text-white/80">Create as many as you need</div>
124
+ </div>
125
+ <div className="bg-white/10 rounded-lg p-3">
126
+ <div className="font-semibold">Advanced Analytics</div>
127
+ <div className="text-sm text-white/80">Deep insights into your data</div>
128
+ </div>
129
+ <div className="bg-white/10 rounded-lg p-3">
130
+ <div className="font-semibold">Priority Support</div>
131
+ <div className="text-sm text-white/80">Get help when you need it</div>
132
+ </div>
133
+ </div>
134
+ <Button
135
+ variant="secondary"
136
+ className="bg-white text-green-700 hover:bg-white/90"
137
+ onClick={() => setShowUpgradeSuccess(false)}
138
+ >
139
+ Start Using Pro Features
140
+ </Button>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ )}
145
+
146
+ {/* Welcome Section */}
147
+ <div className="mb-8">
148
+ <h1 className="text-2xl font-bold mb-2">Welcome back, {user?.name || "there"}!</h1>
149
+ <p className="text-muted-foreground">Here's what's happening with your account today.</p>
150
+ </div>
151
+
152
+ {/* Plan Status Card */}
153
+ <Card className="mb-8">
154
+ <CardHeader className="pb-3">
155
+ <div className="flex items-center justify-between">
156
+ <div>
157
+ <CardTitle className="text-lg">Subscription Status</CardTitle>
158
+ <CardDescription>Your current plan and usage</CardDescription>
159
+ </div>
160
+ {isSubscriptionLoading ? (
161
+ <div className="h-6 w-16 bg-muted animate-pulse rounded-full" />
162
+ ) : (
163
+ <div className="flex items-center gap-2">
164
+ <span
165
+ className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${getPlanBadgeClasses(currentPlan)}`}
166
+ >
167
+ {getPlanName(currentPlan)}
168
+ </span>
169
+ {isCancelling && (
170
+ <span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200">
171
+ Cancelling
172
+ </span>
173
+ )}
174
+ </div>
175
+ )}
176
+ </div>
177
+ </CardHeader>
178
+ <CardContent>
179
+ {isCancelling && (
180
+ <div className="flex items-center justify-between p-4 mb-4 bg-yellow-50 dark:bg-yellow-950/30 border border-yellow-200 dark:border-yellow-800 rounded-lg">
181
+ <div>
182
+ <p className="font-medium text-yellow-800 dark:text-yellow-200">
183
+ Your subscription is set to cancel
184
+ </p>
185
+ <p className="text-sm text-yellow-700 dark:text-yellow-300">
186
+ You'll have access to {getPlanName(currentPlan)} features until{" "}
187
+ {periodEnd ? new Date(periodEnd).toLocaleDateString() : "the end of your billing period"}.
188
+ </p>
189
+ </div>
190
+ <Button variant="outline" onClick={() => navigate("/pricing")}>
191
+ Resubscribe
192
+ </Button>
193
+ </div>
194
+ )}
195
+ {!isSubscribed && !isSubscriptionLoading && (
196
+ <div className="flex items-center justify-between p-4 bg-muted/50 rounded-lg">
197
+ <div>
198
+ <p className="font-medium">Upgrade to unlock more features</p>
199
+ <p className="text-sm text-muted-foreground">
200
+ Get unlimited projects, advanced analytics, and priority support.
201
+ </p>
202
+ </div>
203
+ <Button onClick={() => navigate("/pricing")}>View Plans</Button>
204
+ </div>
205
+ )}
206
+ {isSubscribed && currentPlan === "pro" && (
207
+ <div className="flex items-center justify-between p-4 bg-blue-50 dark:bg-blue-950/30 rounded-lg">
208
+ <div>
209
+ <p className="font-medium">You're on the Pro plan</p>
210
+ <p className="text-sm text-muted-foreground">
211
+ Enjoying unlimited projects and advanced analytics.
212
+ </p>
213
+ </div>
214
+ <div className="flex gap-2">
215
+ <Button variant="outline" asChild>
216
+ <a href="/api/billing-portal">Manage Billing</a>
217
+ </Button>
218
+ <Button variant="outline" onClick={() => navigate("/pricing")}>
219
+ Upgrade
220
+ </Button>
221
+ </div>
222
+ </div>
223
+ )}
224
+ {isSubscribed && currentPlan === "enterprise" && (
225
+ <div className="flex items-center justify-between p-4 bg-purple-50 dark:bg-purple-950/30 rounded-lg">
226
+ <div>
227
+ <p className="font-medium">Enterprise plan active</p>
228
+ <p className="text-sm text-muted-foreground">
229
+ Full access to all features with dedicated support.
230
+ </p>
231
+ </div>
232
+ <div className="flex gap-2">
233
+ <Button variant="outline" asChild>
234
+ <a href="/api/billing-portal">Manage Billing</a>
235
+ </Button>
236
+ <Button variant="outline" disabled>
237
+ Contact Support
238
+ </Button>
239
+ </div>
240
+ </div>
241
+ )}
242
+ </CardContent>
243
+ </Card>
244
+
245
+ {/* Stats Grid */}
246
+ <div className="grid gap-4 md:grid-cols-3 mb-8">
247
+ <Card>
248
+ <CardHeader className="pb-2">
249
+ <CardDescription>API Calls</CardDescription>
250
+ <CardTitle className="text-3xl">{stats.apiCalls.value}</CardTitle>
251
+ </CardHeader>
252
+ <CardContent>
253
+ {stats.apiCalls.limit && (
254
+ <p className="text-xs text-muted-foreground">
255
+ of {stats.apiCalls.limit} this month
256
+ </p>
257
+ )}
258
+ </CardContent>
259
+ </Card>
260
+ <Card>
261
+ <CardHeader className="pb-2">
262
+ <CardDescription>Projects</CardDescription>
263
+ <CardTitle className="text-3xl">{stats.projects.value}</CardTitle>
264
+ </CardHeader>
265
+ <CardContent>
266
+ <p className="text-xs text-muted-foreground">
267
+ {stats.projects.limit === "Unlimited"
268
+ ? "Unlimited"
269
+ : `of ${stats.projects.limit} available`}
270
+ </p>
271
+ </CardContent>
272
+ </Card>
273
+ <Card>
274
+ <CardHeader className="pb-2">
275
+ <CardDescription>Team Members</CardDescription>
276
+ <CardTitle className="text-3xl">{stats.teamMembers.value}</CardTitle>
277
+ </CardHeader>
278
+ <CardContent>
279
+ <p className="text-xs text-muted-foreground">
280
+ {stats.teamMembers.limit === "Unlimited"
281
+ ? "Unlimited seats"
282
+ : `of ${stats.teamMembers.limit} seats`}
283
+ </p>
284
+ </CardContent>
285
+ </Card>
286
+ </div>
287
+
288
+ {/* Quick Actions */}
289
+ <Card className="mb-8">
290
+ <CardHeader>
291
+ <CardTitle>Quick Actions</CardTitle>
292
+ <CardDescription>Common tasks and shortcuts</CardDescription>
293
+ </CardHeader>
294
+ <CardContent>
295
+ <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
296
+ <Button variant="outline" className="h-auto py-4 flex flex-col items-center gap-2">
297
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
298
+ <path
299
+ strokeLinecap="round"
300
+ strokeLinejoin="round"
301
+ strokeWidth={2}
302
+ d="M12 6v6m0 0v6m0-6h6m-6 0H6"
303
+ />
304
+ </svg>
305
+ <span>New Project</span>
306
+ </Button>
307
+ <Button variant="outline" className="h-auto py-4 flex flex-col items-center gap-2">
308
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
309
+ <path
310
+ strokeLinecap="round"
311
+ strokeLinejoin="round"
312
+ strokeWidth={2}
313
+ d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
314
+ />
315
+ </svg>
316
+ <span>View Analytics</span>
317
+ </Button>
318
+ <Button variant="outline" className="h-auto py-4 flex flex-col items-center gap-2">
319
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
320
+ <path
321
+ strokeLinecap="round"
322
+ strokeLinejoin="round"
323
+ strokeWidth={2}
324
+ d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
325
+ />
326
+ </svg>
327
+ <span>Invite Team</span>
328
+ </Button>
329
+ <Button variant="outline" className="h-auto py-4 flex flex-col items-center gap-2">
330
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
331
+ <path
332
+ strokeLinecap="round"
333
+ strokeLinejoin="round"
334
+ strokeWidth={2}
335
+ d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
336
+ />
337
+ <path
338
+ strokeLinecap="round"
339
+ strokeLinejoin="round"
340
+ strokeWidth={2}
341
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
342
+ />
343
+ </svg>
344
+ <span>Settings</span>
345
+ </Button>
346
+ </div>
347
+ </CardContent>
348
+ </Card>
349
+
350
+ {/* Recent Activity */}
351
+ <Card>
352
+ <CardHeader>
353
+ <CardTitle>Recent Activity</CardTitle>
354
+ <CardDescription>Your latest actions and updates</CardDescription>
355
+ </CardHeader>
356
+ <CardContent>
357
+ <div className="space-y-4">
358
+ <div className="flex items-center gap-4">
359
+ <div className="w-2 h-2 bg-green-500 rounded-full" />
360
+ <div className="flex-1">
361
+ <p className="text-sm font-medium">Signed in successfully</p>
362
+ <p className="text-xs text-muted-foreground">Just now</p>
363
+ </div>
364
+ </div>
365
+ <div className="flex items-center gap-4">
366
+ <div className="w-2 h-2 bg-blue-500 rounded-full" />
367
+ <div className="flex-1">
368
+ <p className="text-sm font-medium">Account created</p>
369
+ <p className="text-xs text-muted-foreground">Welcome to the platform!</p>
370
+ </div>
371
+ </div>
372
+ <div className="border-t pt-4">
373
+ <p className="text-sm text-muted-foreground text-center">
374
+ More activity will appear here as you use the app.
375
+ </p>
376
+ </div>
377
+ </div>
378
+ </CardContent>
379
+ </Card>
380
+
381
+ {/* Back to home link */}
382
+ <div className="mt-8 text-center">
383
+ <button
384
+ type="button"
385
+ onClick={() => navigate("/")}
386
+ className="text-sm text-muted-foreground hover:text-foreground transition-colors"
387
+ >
388
+ Back to home
389
+ </button>
390
+ </div>
391
+ </main>
392
+ </div>
393
+ );
394
+ }