@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.
- package/package.json +5 -2
- package/src/commands/down.ts +11 -1
- package/src/commands/init.ts +19 -6
- package/src/commands/new.ts +56 -4
- package/src/commands/publish.ts +1 -1
- package/src/lib/agents.ts +3 -1
- package/src/lib/auth/ensure-auth.test.ts +3 -3
- package/src/lib/control-plane.ts +15 -1
- package/src/lib/deploy-upload.ts +26 -1
- package/src/lib/hooks.ts +232 -1
- package/src/lib/managed-deploy.ts +13 -6
- package/src/lib/managed-down.ts +66 -45
- package/src/lib/progress.ts +76 -5
- package/src/lib/project-list.ts +6 -1
- package/src/lib/project-operations.ts +21 -31
- package/src/lib/project-resolver.ts +1 -1
- package/src/lib/zip-packager.ts +36 -7
- package/src/templates/index.ts +1 -1
- package/src/templates/types.ts +16 -0
- package/templates/CLAUDE.md +172 -5
- package/templates/miniapp/.jack.json +1 -3
- package/templates/saas/.jack.json +154 -0
- package/templates/saas/AGENTS.md +333 -0
- package/templates/saas/bun.lock +925 -0
- package/templates/saas/components.json +21 -0
- package/templates/saas/index.html +12 -0
- package/templates/saas/package.json +75 -0
- package/templates/saas/public/icon.png +0 -0
- package/templates/saas/public/og.png +0 -0
- package/templates/saas/schema.sql +73 -0
- package/templates/saas/src/auth.ts +77 -0
- package/templates/saas/src/client/App.tsx +63 -0
- package/templates/saas/src/client/components/ProtectedRoute.tsx +29 -0
- package/templates/saas/src/client/components/ThemeToggle.tsx +32 -0
- package/templates/saas/src/client/components/ui/accordion.tsx +62 -0
- package/templates/saas/src/client/components/ui/alert-dialog.tsx +133 -0
- package/templates/saas/src/client/components/ui/alert.tsx +60 -0
- package/templates/saas/src/client/components/ui/aspect-ratio.tsx +9 -0
- package/templates/saas/src/client/components/ui/avatar.tsx +39 -0
- package/templates/saas/src/client/components/ui/badge.tsx +39 -0
- package/templates/saas/src/client/components/ui/breadcrumb.tsx +102 -0
- package/templates/saas/src/client/components/ui/button-group.tsx +78 -0
- package/templates/saas/src/client/components/ui/button.tsx +60 -0
- package/templates/saas/src/client/components/ui/card.tsx +75 -0
- package/templates/saas/src/client/components/ui/carousel.tsx +228 -0
- package/templates/saas/src/client/components/ui/chart.tsx +326 -0
- package/templates/saas/src/client/components/ui/checkbox.tsx +29 -0
- package/templates/saas/src/client/components/ui/collapsible.tsx +19 -0
- package/templates/saas/src/client/components/ui/command.tsx +159 -0
- package/templates/saas/src/client/components/ui/context-menu.tsx +224 -0
- package/templates/saas/src/client/components/ui/dialog.tsx +127 -0
- package/templates/saas/src/client/components/ui/drawer.tsx +124 -0
- package/templates/saas/src/client/components/ui/dropdown-menu.tsx +226 -0
- package/templates/saas/src/client/components/ui/empty.tsx +94 -0
- package/templates/saas/src/client/components/ui/field.tsx +232 -0
- package/templates/saas/src/client/components/ui/form.tsx +152 -0
- package/templates/saas/src/client/components/ui/hover-card.tsx +38 -0
- package/templates/saas/src/client/components/ui/input-group.tsx +158 -0
- package/templates/saas/src/client/components/ui/input-otp.tsx +68 -0
- package/templates/saas/src/client/components/ui/input.tsx +21 -0
- package/templates/saas/src/client/components/ui/item.tsx +172 -0
- package/templates/saas/src/client/components/ui/kbd.tsx +28 -0
- package/templates/saas/src/client/components/ui/label.tsx +21 -0
- package/templates/saas/src/client/components/ui/menubar.tsx +250 -0
- package/templates/saas/src/client/components/ui/navigation-menu.tsx +161 -0
- package/templates/saas/src/client/components/ui/pagination.tsx +106 -0
- package/templates/saas/src/client/components/ui/popover.tsx +42 -0
- package/templates/saas/src/client/components/ui/progress.tsx +26 -0
- package/templates/saas/src/client/components/ui/radio-group.tsx +45 -0
- package/templates/saas/src/client/components/ui/resizable.tsx +46 -0
- package/templates/saas/src/client/components/ui/scroll-area.tsx +56 -0
- package/templates/saas/src/client/components/ui/select.tsx +173 -0
- package/templates/saas/src/client/components/ui/separator.tsx +28 -0
- package/templates/saas/src/client/components/ui/sheet.tsx +128 -0
- package/templates/saas/src/client/components/ui/sidebar.tsx +694 -0
- package/templates/saas/src/client/components/ui/skeleton.tsx +13 -0
- package/templates/saas/src/client/components/ui/slider.tsx +58 -0
- package/templates/saas/src/client/components/ui/sonner.tsx +38 -0
- package/templates/saas/src/client/components/ui/spinner.tsx +16 -0
- package/templates/saas/src/client/components/ui/switch.tsx +28 -0
- package/templates/saas/src/client/components/ui/table.tsx +90 -0
- package/templates/saas/src/client/components/ui/tabs.tsx +54 -0
- package/templates/saas/src/client/components/ui/textarea.tsx +18 -0
- package/templates/saas/src/client/components/ui/toggle-group.tsx +80 -0
- package/templates/saas/src/client/components/ui/toggle.tsx +44 -0
- package/templates/saas/src/client/components/ui/tooltip.tsx +57 -0
- package/templates/saas/src/client/hooks/use-mobile.ts +19 -0
- package/templates/saas/src/client/hooks/useAuth.ts +14 -0
- package/templates/saas/src/client/hooks/useSubscription.ts +86 -0
- package/templates/saas/src/client/index.css +165 -0
- package/templates/saas/src/client/lib/auth-client.ts +7 -0
- package/templates/saas/src/client/lib/plans.ts +82 -0
- package/templates/saas/src/client/lib/utils.ts +6 -0
- package/templates/saas/src/client/main.tsx +15 -0
- package/templates/saas/src/client/pages/DashboardPage.tsx +394 -0
- package/templates/saas/src/client/pages/ForgotPasswordPage.tsx +153 -0
- package/templates/saas/src/client/pages/HomePage.tsx +285 -0
- package/templates/saas/src/client/pages/LoginPage.tsx +169 -0
- package/templates/saas/src/client/pages/PricingPage.tsx +467 -0
- package/templates/saas/src/client/pages/ResetPasswordPage.tsx +200 -0
- package/templates/saas/src/client/pages/SignupPage.tsx +192 -0
- package/templates/saas/src/index.ts +208 -0
- package/templates/saas/tsconfig.json +18 -0
- package/templates/saas/vite.config.ts +14 -0
- 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,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,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
|
+
}
|