shipd 0.1.0
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/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1366 -0
- package/docs-template/README.md +255 -0
- package/docs-template/[slug]/[subslug]/page.tsx +1242 -0
- package/docs-template/[slug]/page.tsx +422 -0
- package/docs-template/api/page.tsx +47 -0
- package/docs-template/components/docs/docs-category-page.tsx +162 -0
- package/docs-template/components/docs/docs-code-card.tsx +135 -0
- package/docs-template/components/docs/docs-header.tsx +69 -0
- package/docs-template/components/docs/docs-nav.ts +95 -0
- package/docs-template/components/docs/docs-sidebar.tsx +112 -0
- package/docs-template/components/docs/docs-toc.tsx +38 -0
- package/docs-template/components/ui/badge.tsx +47 -0
- package/docs-template/components/ui/button.tsx +60 -0
- package/docs-template/components/ui/card.tsx +93 -0
- package/docs-template/components/ui/sheet.tsx +140 -0
- package/docs-template/documentation/page.tsx +80 -0
- package/docs-template/layout.tsx +27 -0
- package/docs-template/lib/utils.ts +7 -0
- package/docs-template/page.tsx +360 -0
- package/package.json +66 -0
- package/template/.env.example +45 -0
- package/template/README.md +239 -0
- package/template/app/api/auth/[...all]/route.ts +4 -0
- package/template/app/api/chat/route.ts +16 -0
- package/template/app/api/subscription/route.ts +25 -0
- package/template/app/api/upload-image/route.ts +64 -0
- package/template/app/blog/[slug]/page.tsx +314 -0
- package/template/app/blog/page.tsx +107 -0
- package/template/app/dashboard/_components/chart-interactive.tsx +289 -0
- package/template/app/dashboard/_components/chatbot.tsx +39 -0
- package/template/app/dashboard/_components/mode-toggle.tsx +46 -0
- package/template/app/dashboard/_components/navbar.tsx +84 -0
- package/template/app/dashboard/_components/section-cards.tsx +102 -0
- package/template/app/dashboard/_components/sidebar.tsx +90 -0
- package/template/app/dashboard/_components/subscribe-button.tsx +49 -0
- package/template/app/dashboard/billing/page.tsx +277 -0
- package/template/app/dashboard/chat/page.tsx +73 -0
- package/template/app/dashboard/cli/page.tsx +260 -0
- package/template/app/dashboard/layout.tsx +24 -0
- package/template/app/dashboard/page.tsx +216 -0
- package/template/app/dashboard/payment/_components/manage-subscription.tsx +22 -0
- package/template/app/dashboard/payment/page.tsx +126 -0
- package/template/app/dashboard/settings/page.tsx +613 -0
- package/template/app/dashboard/upload/page.tsx +324 -0
- package/template/app/error.tsx +78 -0
- package/template/app/favicon.ico +0 -0
- package/template/app/globals.css +126 -0
- package/template/app/layout.tsx +135 -0
- package/template/app/not-found.tsx +45 -0
- package/template/app/page.tsx +28 -0
- package/template/app/pricing/_component/pricing-table.tsx +276 -0
- package/template/app/pricing/page.tsx +23 -0
- package/template/app/privacy-policy/page.tsx +280 -0
- package/template/app/robots.txt +12 -0
- package/template/app/sign-in/page.tsx +228 -0
- package/template/app/sign-up/page.tsx +243 -0
- package/template/app/sitemap.ts +62 -0
- package/template/app/success/page.tsx +123 -0
- package/template/app/terms-of-service/page.tsx +212 -0
- package/template/auth-schema.ts +47 -0
- package/template/components/homepage/cli-workflow-section.tsx +138 -0
- package/template/components/homepage/features-section.tsx +150 -0
- package/template/components/homepage/footer.tsx +53 -0
- package/template/components/homepage/hero-section.tsx +112 -0
- package/template/components/homepage/integrations.tsx +124 -0
- package/template/components/homepage/navigation.tsx +116 -0
- package/template/components/homepage/news-section.tsx +82 -0
- package/template/components/homepage/testimonials-section.tsx +34 -0
- package/template/components/logos/BetterAuth.tsx +21 -0
- package/template/components/logos/NeonPostgres.tsx +41 -0
- package/template/components/logos/Nextjs.tsx +72 -0
- package/template/components/logos/Polar.tsx +7 -0
- package/template/components/logos/TailwindCSS.tsx +27 -0
- package/template/components/logos/index.ts +6 -0
- package/template/components/logos/shadcnui.tsx +8 -0
- package/template/components/provider.tsx +8 -0
- package/template/components/ui/avatar.tsx +53 -0
- package/template/components/ui/badge.tsx +46 -0
- package/template/components/ui/button.tsx +59 -0
- package/template/components/ui/card.tsx +92 -0
- package/template/components/ui/chart.tsx +353 -0
- package/template/components/ui/checkbox.tsx +32 -0
- package/template/components/ui/dialog.tsx +135 -0
- package/template/components/ui/dropdown-menu.tsx +257 -0
- package/template/components/ui/form.tsx +167 -0
- package/template/components/ui/input.tsx +21 -0
- package/template/components/ui/label.tsx +24 -0
- package/template/components/ui/progress.tsx +31 -0
- package/template/components/ui/resizable.tsx +56 -0
- package/template/components/ui/select.tsx +185 -0
- package/template/components/ui/separator.tsx +28 -0
- package/template/components/ui/sheet.tsx +139 -0
- package/template/components/ui/skeleton.tsx +13 -0
- package/template/components/ui/sonner.tsx +25 -0
- package/template/components/ui/switch.tsx +31 -0
- package/template/components/ui/tabs.tsx +66 -0
- package/template/components/ui/textarea.tsx +18 -0
- package/template/components/ui/toggle-group.tsx +73 -0
- package/template/components/ui/toggle.tsx +47 -0
- package/template/components/ui/tooltip.tsx +61 -0
- package/template/components/user-profile.tsx +139 -0
- package/template/components.json +21 -0
- package/template/db/drizzle.ts +14 -0
- package/template/db/migrations/0000_worried_rawhide_kid.sql +77 -0
- package/template/db/migrations/meta/0000_snapshot.json +494 -0
- package/template/db/migrations/meta/_journal.json +13 -0
- package/template/db/schema.ts +85 -0
- package/template/drizzle.config.ts +13 -0
- package/template/emails/components/layout.tsx +181 -0
- package/template/emails/password-reset.tsx +67 -0
- package/template/emails/payment-failed.tsx +167 -0
- package/template/emails/subscription-confirmation.tsx +129 -0
- package/template/emails/welcome.tsx +100 -0
- package/template/eslint.config.mjs +16 -0
- package/template/hooks/use-mobile.ts +21 -0
- package/template/lib/auth-client.ts +8 -0
- package/template/lib/auth.ts +276 -0
- package/template/lib/email.ts +118 -0
- package/template/lib/polar-products.ts +49 -0
- package/template/lib/subscription.ts +148 -0
- package/template/lib/upload-image.ts +28 -0
- package/template/lib/utils.ts +6 -0
- package/template/middleware.ts +30 -0
- package/template/next-env.d.ts +5 -0
- package/template/next.config.ts +27 -0
- package/template/package.json +99 -0
- package/template/postcss.config.mjs +5 -0
- package/template/public/add.png +0 -0
- package/template/public/favicon.svg +4 -0
- package/template/public/file.svg +1 -0
- package/template/public/globe.svg +1 -0
- package/template/public/iphone.png +0 -0
- package/template/public/logo.png +0 -0
- package/template/public/next.svg +1 -0
- package/template/public/polar-sh.svg +1 -0
- package/template/public/shadcn-ui.svg +1 -0
- package/template/public/site.webmanifest +21 -0
- package/template/public/vercel.svg +1 -0
- package/template/public/window.svg +1 -0
- package/template/tailwind.config.ts +89 -0
- package/template/template.config.json +138 -0
- package/template/tsconfig.json +27 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import { authClient } from "@/lib/auth-client";
|
|
5
|
+
import { Zap } from "lucide-react";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { toast } from "sonner";
|
|
8
|
+
|
|
9
|
+
interface SubscribeButtonProps {
|
|
10
|
+
productId?: string;
|
|
11
|
+
slug?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function SubscribeButton({
|
|
15
|
+
productId = process.env.NEXT_PUBLIC_STARTER_TIER!,
|
|
16
|
+
slug = process.env.NEXT_PUBLIC_STARTER_SLUG!
|
|
17
|
+
}: SubscribeButtonProps = {}) {
|
|
18
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
19
|
+
|
|
20
|
+
const handleSubscribe = async () => {
|
|
21
|
+
setIsLoading(true);
|
|
22
|
+
try {
|
|
23
|
+
console.log("Starting checkout with:", { productId, slug });
|
|
24
|
+
|
|
25
|
+
await authClient.checkout({
|
|
26
|
+
products: [productId],
|
|
27
|
+
slug: slug,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// If we reach here, checkout was initiated
|
|
31
|
+
console.log("Checkout initiated successfully");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("Checkout error:", error);
|
|
34
|
+
toast.error("Failed to start checkout. Please try again.");
|
|
35
|
+
setIsLoading(false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Button
|
|
41
|
+
onClick={handleSubscribe}
|
|
42
|
+
disabled={isLoading}
|
|
43
|
+
className="bg-[#ff5722] hover:bg-[#d84315] text-white w-full"
|
|
44
|
+
>
|
|
45
|
+
<Zap className="w-4 h-4 mr-2" />
|
|
46
|
+
{isLoading ? "Loading..." : "Subscribe Now"}
|
|
47
|
+
</Button>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { auth } from "@/lib/auth";
|
|
2
|
+
import { headers } from "next/headers";
|
|
3
|
+
import { redirect } from "next/navigation";
|
|
4
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Badge } from "@/components/ui/badge";
|
|
7
|
+
import { CreditCard, Calendar, AlertCircle, CheckCircle2, ExternalLink } from "lucide-react";
|
|
8
|
+
import { getSubscriptionDetails } from "@/lib/subscription";
|
|
9
|
+
import Link from "next/link";
|
|
10
|
+
|
|
11
|
+
export default async function BillingPage() {
|
|
12
|
+
const session = await auth.api.getSession({
|
|
13
|
+
headers: await headers(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (!session?.session?.userId) {
|
|
17
|
+
redirect("/sign-in");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const subscription = await getSubscriptionDetails();
|
|
21
|
+
const sub = subscription.subscription;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<section className="flex flex-col items-start justify-start p-6 w-full max-w-5xl mx-auto">
|
|
25
|
+
{/* Header */}
|
|
26
|
+
<div className="w-full mb-8">
|
|
27
|
+
<h1 className="text-3xl font-bold tracking-tight text-white">Billing & Subscription</h1>
|
|
28
|
+
<p className="text-gray-400 mt-2">
|
|
29
|
+
Manage your subscription, payment methods, and billing history
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
{/* No Subscription State */}
|
|
34
|
+
{!subscription.hasActiveSubscription ? (
|
|
35
|
+
<div className="w-full">
|
|
36
|
+
<Card className="bg-[#1a1a1a] border-[#2a2a2a]">
|
|
37
|
+
<CardContent className="p-12 text-center">
|
|
38
|
+
<div className="max-w-md mx-auto">
|
|
39
|
+
<div className="w-16 h-16 bg-[#ff5722]/10 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
40
|
+
<CreditCard className="w-8 h-8 text-[#ff5722]" />
|
|
41
|
+
</div>
|
|
42
|
+
<h3 className="text-2xl font-semibold text-white mb-2">
|
|
43
|
+
No Active Subscription
|
|
44
|
+
</h3>
|
|
45
|
+
<p className="text-gray-400 mb-6">
|
|
46
|
+
Subscribe to SaaS Scaffold to access the CLI and start building your SaaS application.
|
|
47
|
+
</p>
|
|
48
|
+
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
|
49
|
+
<Link href="/pricing">
|
|
50
|
+
<Button className="bg-[#ff5722] hover:bg-[#d84315] text-white w-full sm:w-auto">
|
|
51
|
+
View Pricing Plans
|
|
52
|
+
</Button>
|
|
53
|
+
</Link>
|
|
54
|
+
<Link href="/docs">
|
|
55
|
+
<Button variant="outline" className="border-[#2a2a2a] hover:bg-[#0a0a0a] w-full sm:w-auto">
|
|
56
|
+
Learn More
|
|
57
|
+
</Button>
|
|
58
|
+
</Link>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</CardContent>
|
|
62
|
+
</Card>
|
|
63
|
+
</div>
|
|
64
|
+
) : (
|
|
65
|
+
<div className="w-full space-y-6">
|
|
66
|
+
{/* Current Plan Card */}
|
|
67
|
+
<Card className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] border-[#2a2a2a]">
|
|
68
|
+
<CardHeader>
|
|
69
|
+
<div className="flex items-center justify-between">
|
|
70
|
+
<div>
|
|
71
|
+
<CardTitle className="text-white">Current Plan</CardTitle>
|
|
72
|
+
<CardDescription>Your active subscription details</CardDescription>
|
|
73
|
+
</div>
|
|
74
|
+
<Badge className="bg-green-500/20 text-green-400 border-green-500/30">
|
|
75
|
+
<CheckCircle2 className="w-3 h-3 mr-1" />
|
|
76
|
+
Active
|
|
77
|
+
</Badge>
|
|
78
|
+
</div>
|
|
79
|
+
</CardHeader>
|
|
80
|
+
<CardContent>
|
|
81
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
82
|
+
<div>
|
|
83
|
+
<p className="text-sm text-gray-400 mb-1">Plan</p>
|
|
84
|
+
<p className="text-2xl font-bold text-white">
|
|
85
|
+
{sub?.productId || 'Premium'}
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
<div>
|
|
89
|
+
<p className="text-sm text-gray-400 mb-1">Price</p>
|
|
90
|
+
<p className="text-2xl font-bold text-white">
|
|
91
|
+
${(sub?.amount || 0) / 100}
|
|
92
|
+
<span className="text-base font-normal text-gray-400">
|
|
93
|
+
/{sub?.recurringInterval || 'month'}
|
|
94
|
+
</span>
|
|
95
|
+
</p>
|
|
96
|
+
</div>
|
|
97
|
+
<div>
|
|
98
|
+
<p className="text-sm text-gray-400 mb-1">Status</p>
|
|
99
|
+
<p className="text-2xl font-bold text-white capitalize">
|
|
100
|
+
{sub?.status || 'Active'}
|
|
101
|
+
</p>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{sub?.cancelAtPeriodEnd && (
|
|
106
|
+
<div className="mt-6 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg flex items-start gap-3">
|
|
107
|
+
<AlertCircle className="w-5 h-5 text-yellow-400 flex-shrink-0 mt-0.5" />
|
|
108
|
+
<div>
|
|
109
|
+
<p className="text-sm font-semibold text-yellow-400 mb-1">
|
|
110
|
+
Subscription Ending
|
|
111
|
+
</p>
|
|
112
|
+
<p className="text-sm text-gray-400">
|
|
113
|
+
Your subscription will end on{' '}
|
|
114
|
+
<span className="text-white font-medium">
|
|
115
|
+
{sub.currentPeriodEnd && new Date(sub.currentPeriodEnd).toLocaleDateString('en-US', {
|
|
116
|
+
month: 'long',
|
|
117
|
+
day: 'numeric',
|
|
118
|
+
year: 'numeric'
|
|
119
|
+
})}
|
|
120
|
+
</span>
|
|
121
|
+
. You can reactivate it anytime before then.
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</CardContent>
|
|
127
|
+
</Card>
|
|
128
|
+
|
|
129
|
+
{/* Billing Details Card */}
|
|
130
|
+
<Card className="bg-[#0a0a0a] border-[#2a2a2a]">
|
|
131
|
+
<CardHeader>
|
|
132
|
+
<CardTitle className="text-white flex items-center">
|
|
133
|
+
<Calendar className="w-5 h-5 mr-2 text-[#ff5722]" />
|
|
134
|
+
Billing Cycle
|
|
135
|
+
</CardTitle>
|
|
136
|
+
<CardDescription>Your current billing period and renewal date</CardDescription>
|
|
137
|
+
</CardHeader>
|
|
138
|
+
<CardContent>
|
|
139
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
140
|
+
<div>
|
|
141
|
+
<p className="text-sm text-gray-400 mb-2">Current Period</p>
|
|
142
|
+
<div className="flex items-center gap-2">
|
|
143
|
+
<p className="text-white">
|
|
144
|
+
{sub?.currentPeriodStart && new Date(sub.currentPeriodStart).toLocaleDateString('en-US', {
|
|
145
|
+
month: 'short',
|
|
146
|
+
day: 'numeric',
|
|
147
|
+
year: 'numeric'
|
|
148
|
+
})}
|
|
149
|
+
</p>
|
|
150
|
+
<span className="text-gray-500">→</span>
|
|
151
|
+
<p className="text-white">
|
|
152
|
+
{sub?.currentPeriodEnd && new Date(sub.currentPeriodEnd).toLocaleDateString('en-US', {
|
|
153
|
+
month: 'short',
|
|
154
|
+
day: 'numeric',
|
|
155
|
+
year: 'numeric'
|
|
156
|
+
})}
|
|
157
|
+
</p>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
<div>
|
|
161
|
+
<p className="text-sm text-gray-400 mb-2">Next Billing Date</p>
|
|
162
|
+
<p className="text-white font-semibold">
|
|
163
|
+
{sub?.currentPeriodEnd && !sub?.cancelAtPeriodEnd &&
|
|
164
|
+
new Date(sub.currentPeriodEnd).toLocaleDateString('en-US', {
|
|
165
|
+
month: 'long',
|
|
166
|
+
day: 'numeric',
|
|
167
|
+
year: 'numeric'
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
{sub?.cancelAtPeriodEnd && (
|
|
171
|
+
<span className="text-yellow-400">Subscription Ending</span>
|
|
172
|
+
)}
|
|
173
|
+
</p>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</CardContent>
|
|
177
|
+
</Card>
|
|
178
|
+
|
|
179
|
+
{/* Manage Subscription Card */}
|
|
180
|
+
<Card className="bg-[#0a0a0a] border-[#2a2a2a]">
|
|
181
|
+
<CardHeader>
|
|
182
|
+
<CardTitle className="text-white">Manage Subscription</CardTitle>
|
|
183
|
+
<CardDescription>Update your plan, payment method, or cancel your subscription</CardDescription>
|
|
184
|
+
</CardHeader>
|
|
185
|
+
<CardContent className="space-y-4">
|
|
186
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
187
|
+
{/* Polar Customer Portal */}
|
|
188
|
+
<form action="/api/subscription/portal" method="POST" className="w-full">
|
|
189
|
+
<Button
|
|
190
|
+
type="submit"
|
|
191
|
+
variant="outline"
|
|
192
|
+
className="w-full border-[#2a2a2a] hover:bg-[#1a1a1a] justify-between"
|
|
193
|
+
>
|
|
194
|
+
<span className="flex items-center">
|
|
195
|
+
<CreditCard className="w-4 h-4 mr-2" />
|
|
196
|
+
Manage Payment
|
|
197
|
+
</span>
|
|
198
|
+
<ExternalLink className="w-4 h-4" />
|
|
199
|
+
</Button>
|
|
200
|
+
</form>
|
|
201
|
+
|
|
202
|
+
{/* Change Plan */}
|
|
203
|
+
<Link href="/pricing" className="w-full">
|
|
204
|
+
<Button
|
|
205
|
+
variant="outline"
|
|
206
|
+
className="w-full border-[#2a2a2a] hover:bg-[#1a1a1a] justify-between"
|
|
207
|
+
>
|
|
208
|
+
<span>Change Plan</span>
|
|
209
|
+
<ExternalLink className="w-4 h-4" />
|
|
210
|
+
</Button>
|
|
211
|
+
</Link>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<div className="pt-4 border-t border-[#2a2a2a]">
|
|
215
|
+
<p className="text-sm text-gray-400 mb-3">
|
|
216
|
+
Need to cancel? You can cancel your subscription anytime. You'll retain access until the end of your billing period.
|
|
217
|
+
</p>
|
|
218
|
+
{!sub?.cancelAtPeriodEnd ? (
|
|
219
|
+
<form action="/api/subscription/cancel" method="POST">
|
|
220
|
+
<Button
|
|
221
|
+
type="submit"
|
|
222
|
+
variant="outline"
|
|
223
|
+
className="border-red-500/30 text-red-400 hover:bg-red-500/10"
|
|
224
|
+
>
|
|
225
|
+
Cancel Subscription
|
|
226
|
+
</Button>
|
|
227
|
+
</form>
|
|
228
|
+
) : (
|
|
229
|
+
<form action="/api/subscription/reactivate" method="POST">
|
|
230
|
+
<Button
|
|
231
|
+
type="submit"
|
|
232
|
+
className="bg-[#ff5722] hover:bg-[#d84315]"
|
|
233
|
+
>
|
|
234
|
+
Reactivate Subscription
|
|
235
|
+
</Button>
|
|
236
|
+
</form>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
</CardContent>
|
|
240
|
+
</Card>
|
|
241
|
+
|
|
242
|
+
{/* Billing Information */}
|
|
243
|
+
<Card className="bg-[#0a0a0a] border-[#2a2a2a]">
|
|
244
|
+
<CardHeader>
|
|
245
|
+
<CardTitle className="text-white">Billing Information</CardTitle>
|
|
246
|
+
<CardDescription>Your billing details and invoice history</CardDescription>
|
|
247
|
+
</CardHeader>
|
|
248
|
+
<CardContent className="space-y-4">
|
|
249
|
+
<div>
|
|
250
|
+
<p className="text-sm text-gray-400 mb-2">Payment Method</p>
|
|
251
|
+
<div className="flex items-center gap-2">
|
|
252
|
+
<CreditCard className="w-4 h-4 text-gray-400" />
|
|
253
|
+
<p className="text-white">Managed via Polar.sh</p>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
<div>
|
|
257
|
+
<p className="text-sm text-gray-400 mb-2">Invoices</p>
|
|
258
|
+
<p className="text-white text-sm">
|
|
259
|
+
Access your invoice history through the{' '}
|
|
260
|
+
<form action="/api/subscription/portal" method="POST" className="inline">
|
|
261
|
+
<Button
|
|
262
|
+
type="submit"
|
|
263
|
+
variant="link"
|
|
264
|
+
className="text-[#ff5722] hover:text-[#d84315] p-0 h-auto"
|
|
265
|
+
>
|
|
266
|
+
customer portal
|
|
267
|
+
</Button>
|
|
268
|
+
</form>
|
|
269
|
+
</p>
|
|
270
|
+
</div>
|
|
271
|
+
</CardContent>
|
|
272
|
+
</Card>
|
|
273
|
+
</div>
|
|
274
|
+
)}
|
|
275
|
+
</section>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import { Input } from "@/components/ui/input";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import { useChat } from "@ai-sdk/react";
|
|
7
|
+
import Markdown from "react-markdown";
|
|
8
|
+
|
|
9
|
+
export default function Chat() {
|
|
10
|
+
const { messages, input, handleInputChange, handleSubmit } = useChat({
|
|
11
|
+
maxSteps: 10,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex flex-col w-full py-24 justify-center items-center">
|
|
16
|
+
<div className="w-full max-w-xl space-y-4 mb-20">
|
|
17
|
+
{messages.map((message, i) => (
|
|
18
|
+
<div
|
|
19
|
+
key={message.id}
|
|
20
|
+
className={cn(
|
|
21
|
+
"flex",
|
|
22
|
+
message.role === "user" ? "justify-end" : "justify-start",
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
<div
|
|
26
|
+
className={cn(
|
|
27
|
+
"max-w-[65%] px-3 py-1.5 text-sm shadow-sm",
|
|
28
|
+
message.role === "user"
|
|
29
|
+
? "bg-[#0B93F6] text-white rounded-2xl rounded-br-sm"
|
|
30
|
+
: "bg-[#E9E9EB] text-black rounded-2xl rounded-bl-sm",
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
{message.parts.map((part) => {
|
|
34
|
+
switch (part.type) {
|
|
35
|
+
case "text":
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
key={`${message.id}-${i}`}
|
|
39
|
+
className="prose-sm prose-p:my-0.5 prose-li:my-0.5 prose-ul:my-1 prose-ol:my-1"
|
|
40
|
+
>
|
|
41
|
+
<Markdown>{part.text}</Markdown>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
default:
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
})}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<form
|
|
54
|
+
className="flex gap-2 justify-center w-full items-center fixed bottom-0"
|
|
55
|
+
onSubmit={handleSubmit}
|
|
56
|
+
>
|
|
57
|
+
<div className="flex flex-col gap-2 justify-center items-start mb-8 max-w-xl w-full border p-2 rounded-lg bg-white ">
|
|
58
|
+
<Input
|
|
59
|
+
className="w-full border-0 shadow-none !ring-transparent "
|
|
60
|
+
value={input}
|
|
61
|
+
placeholder="Say something..."
|
|
62
|
+
onChange={handleInputChange}
|
|
63
|
+
/>
|
|
64
|
+
<div className="flex justify-end gap-3 items-center w-full">
|
|
65
|
+
<Button size="sm" className="text-xs">
|
|
66
|
+
Send
|
|
67
|
+
</Button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</form>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { auth } from "@/lib/auth";
|
|
2
|
+
import { headers } from "next/headers";
|
|
3
|
+
import { redirect } from "next/navigation";
|
|
4
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Badge } from "@/components/ui/badge";
|
|
7
|
+
import { Terminal, Copy, CheckCircle2, Download, BookOpen, Github } from "lucide-react";
|
|
8
|
+
import { getSubscriptionDetails } from "@/lib/subscription";
|
|
9
|
+
import Link from "next/link";
|
|
10
|
+
|
|
11
|
+
export default async function CLIPage() {
|
|
12
|
+
const session = await auth.api.getSession({
|
|
13
|
+
headers: await headers(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (!session?.session?.userId) {
|
|
17
|
+
redirect("/sign-in");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const subscription = await getSubscriptionDetails();
|
|
21
|
+
const user = session.user;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<section className="flex flex-col items-start justify-start p-6 w-full max-w-7xl mx-auto">
|
|
25
|
+
{/* Header */}
|
|
26
|
+
<div className="w-full mb-8">
|
|
27
|
+
<h1 className="text-3xl font-bold tracking-tight text-white">CLI Access</h1>
|
|
28
|
+
<p className="text-gray-400 mt-2">
|
|
29
|
+
Everything you need to get started with the SaaS Scaffold CLI
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
{/* Subscription Check */}
|
|
34
|
+
{!subscription.hasActiveSubscription && (
|
|
35
|
+
<div className="w-full mb-6">
|
|
36
|
+
<Card className="bg-red-500/10 border-red-500/30">
|
|
37
|
+
<CardContent className="p-6">
|
|
38
|
+
<div className="flex items-center gap-4">
|
|
39
|
+
<div className="flex-1">
|
|
40
|
+
<h3 className="text-lg font-semibold text-red-400 mb-1">
|
|
41
|
+
Active Subscription Required
|
|
42
|
+
</h3>
|
|
43
|
+
<p className="text-gray-400 text-sm">
|
|
44
|
+
You need an active subscription to use the SaaS Scaffold CLI.
|
|
45
|
+
</p>
|
|
46
|
+
</div>
|
|
47
|
+
<Link href="/pricing">
|
|
48
|
+
<Button className="bg-[#ff5722] hover:bg-[#d84315]">
|
|
49
|
+
View Pricing
|
|
50
|
+
</Button>
|
|
51
|
+
</Link>
|
|
52
|
+
</div>
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
|
|
58
|
+
{/* Installation */}
|
|
59
|
+
<div className="w-full mb-6">
|
|
60
|
+
<Card className="bg-[#0a0a0a] border-[#2a2a2a]">
|
|
61
|
+
<CardHeader>
|
|
62
|
+
<CardTitle className="text-white flex items-center">
|
|
63
|
+
<Download className="w-5 h-5 mr-2 text-[#ff5722]" />
|
|
64
|
+
Installation
|
|
65
|
+
</CardTitle>
|
|
66
|
+
<CardDescription>Install the SaaS Scaffold CLI globally via npm</CardDescription>
|
|
67
|
+
</CardHeader>
|
|
68
|
+
<CardContent className="space-y-4">
|
|
69
|
+
<div>
|
|
70
|
+
<p className="text-sm text-gray-400 mb-2">npm (recommended)</p>
|
|
71
|
+
<div className="bg-black/50 p-4 rounded-lg border border-[#2a2a2a] flex items-center justify-between">
|
|
72
|
+
<code className="text-[#ff5722] font-mono text-sm">
|
|
73
|
+
npx saas-scaffold init my-project
|
|
74
|
+
</code>
|
|
75
|
+
<Button
|
|
76
|
+
variant="ghost"
|
|
77
|
+
size="sm"
|
|
78
|
+
className="text-gray-400 hover:text-white"
|
|
79
|
+
onClick={() => navigator.clipboard.writeText('npx saas-scaffold init my-project')}
|
|
80
|
+
>
|
|
81
|
+
<Copy className="w-4 h-4" />
|
|
82
|
+
</Button>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<div className="text-sm text-gray-400">
|
|
86
|
+
<p>No installation required! Use <code className="text-[#ff5722] bg-black/50 px-2 py-1 rounded">npx</code> to run the latest version directly.</p>
|
|
87
|
+
</div>
|
|
88
|
+
</CardContent>
|
|
89
|
+
</Card>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{/* Authentication */}
|
|
93
|
+
<div className="w-full mb-6">
|
|
94
|
+
<Card className="bg-[#0a0a0a] border-[#2a2a2a]">
|
|
95
|
+
<CardHeader>
|
|
96
|
+
<CardTitle className="text-white flex items-center">
|
|
97
|
+
<Terminal className="w-5 h-5 mr-2 text-[#ff5722]" />
|
|
98
|
+
Authentication
|
|
99
|
+
</CardTitle>
|
|
100
|
+
<CardDescription>Authenticate your CLI to access premium features</CardDescription>
|
|
101
|
+
</CardHeader>
|
|
102
|
+
<CardContent className="space-y-6">
|
|
103
|
+
{/* Step 1 */}
|
|
104
|
+
<div>
|
|
105
|
+
<div className="flex items-center gap-2 mb-2">
|
|
106
|
+
<div className="w-6 h-6 rounded-full bg-[#ff5722] flex items-center justify-center text-white text-sm font-semibold">
|
|
107
|
+
1
|
|
108
|
+
</div>
|
|
109
|
+
<h4 className="text-white font-semibold">Run the login command</h4>
|
|
110
|
+
</div>
|
|
111
|
+
<div className="ml-8 space-y-2">
|
|
112
|
+
<p className="text-sm text-gray-400">Execute the following command in your terminal:</p>
|
|
113
|
+
<div className="bg-black/50 p-4 rounded-lg border border-[#2a2a2a] flex items-center justify-between">
|
|
114
|
+
<code className="text-[#ff5722] font-mono text-sm">
|
|
115
|
+
npx saas-scaffold login
|
|
116
|
+
</code>
|
|
117
|
+
<Button
|
|
118
|
+
variant="ghost"
|
|
119
|
+
size="sm"
|
|
120
|
+
className="text-gray-400 hover:text-white"
|
|
121
|
+
onClick={() => navigator.clipboard.writeText('npx saas-scaffold login')}
|
|
122
|
+
>
|
|
123
|
+
<Copy className="w-4 h-4" />
|
|
124
|
+
</Button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{/* Step 2 */}
|
|
130
|
+
<div>
|
|
131
|
+
<div className="flex items-center gap-2 mb-2">
|
|
132
|
+
<div className="w-6 h-6 rounded-full bg-[#ff5722] flex items-center justify-center text-white text-sm font-semibold">
|
|
133
|
+
2
|
|
134
|
+
</div>
|
|
135
|
+
<h4 className="text-white font-semibold">Complete browser authentication</h4>
|
|
136
|
+
</div>
|
|
137
|
+
<div className="ml-8 space-y-2">
|
|
138
|
+
<p className="text-sm text-gray-400">
|
|
139
|
+
Your browser will open automatically. Sign in with your account (you're already signed in as <span className="text-white">{user?.email}</span>).
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
{/* Step 3 */}
|
|
145
|
+
<div>
|
|
146
|
+
<div className="flex items-center gap-2 mb-2">
|
|
147
|
+
<div className="w-6 h-6 rounded-full bg-[#ff5722] flex items-center justify-center text-white text-sm font-semibold">
|
|
148
|
+
3
|
|
149
|
+
</div>
|
|
150
|
+
<h4 className="text-white font-semibold">Start building</h4>
|
|
151
|
+
</div>
|
|
152
|
+
<div className="ml-8 space-y-2">
|
|
153
|
+
<p className="text-sm text-gray-400">You're now authenticated! Create your first project:</p>
|
|
154
|
+
<div className="bg-black/50 p-4 rounded-lg border border-[#2a2a2a] flex items-center justify-between">
|
|
155
|
+
<code className="text-[#ff5722] font-mono text-sm">
|
|
156
|
+
npx saas-scaffold init my-saas-app
|
|
157
|
+
</code>
|
|
158
|
+
<Button
|
|
159
|
+
variant="ghost"
|
|
160
|
+
size="sm"
|
|
161
|
+
className="text-gray-400 hover:text-white"
|
|
162
|
+
onClick={() => navigator.clipboard.writeText('npx saas-scaffold init my-saas-app')}
|
|
163
|
+
>
|
|
164
|
+
<Copy className="w-4 h-4" />
|
|
165
|
+
</Button>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{subscription.hasActiveSubscription && (
|
|
171
|
+
<div className="pt-4 border-t border-[#2a2a2a]">
|
|
172
|
+
<div className="flex items-center gap-2 text-green-400">
|
|
173
|
+
<CheckCircle2 className="w-5 h-5" />
|
|
174
|
+
<p className="text-sm font-medium">Active subscription detected - CLI access enabled</p>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
</CardContent>
|
|
179
|
+
</Card>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* Available Commands */}
|
|
183
|
+
<div className="w-full mb-6">
|
|
184
|
+
<Card className="bg-[#0a0a0a] border-[#2a2a2a]">
|
|
185
|
+
<CardHeader>
|
|
186
|
+
<CardTitle className="text-white">Available Commands</CardTitle>
|
|
187
|
+
<CardDescription>All commands available in the SaaS Scaffold CLI</CardDescription>
|
|
188
|
+
</CardHeader>
|
|
189
|
+
<CardContent className="space-y-4">
|
|
190
|
+
<div className="grid gap-4">
|
|
191
|
+
{/* Init Command */}
|
|
192
|
+
<div className="p-4 bg-black/30 rounded-lg border border-[#2a2a2a]">
|
|
193
|
+
<div className="flex items-start justify-between mb-2">
|
|
194
|
+
<code className="text-[#ff5722] font-mono">saas-scaffold init [project-name]</code>
|
|
195
|
+
<Badge variant="outline" className="border-green-500/30 text-green-400">
|
|
196
|
+
Primary
|
|
197
|
+
</Badge>
|
|
198
|
+
</div>
|
|
199
|
+
<p className="text-sm text-gray-400">
|
|
200
|
+
Generate a new SaaS project with authentication, billing, and premium features pre-configured.
|
|
201
|
+
</p>
|
|
202
|
+
<div className="mt-2 text-xs text-gray-500">
|
|
203
|
+
Options: <code>--mock</code> (skip authentication for testing)
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
{/* Login Command */}
|
|
208
|
+
<div className="p-4 bg-black/30 rounded-lg border border-[#2a2a2a]">
|
|
209
|
+
<code className="text-[#ff5722] font-mono mb-2 block">saas-scaffold login</code>
|
|
210
|
+
<p className="text-sm text-gray-400">
|
|
211
|
+
Authenticate your CLI with your SaaS Scaffold account. Opens browser for OAuth flow.
|
|
212
|
+
</p>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{/* Logout Command */}
|
|
216
|
+
<div className="p-4 bg-black/30 rounded-lg border border-[#2a2a2a]">
|
|
217
|
+
<code className="text-[#ff5722] font-mono mb-2 block">saas-scaffold logout</code>
|
|
218
|
+
<p className="text-sm text-gray-400">
|
|
219
|
+
Remove authentication token and log out of the CLI.
|
|
220
|
+
</p>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</CardContent>
|
|
224
|
+
</Card>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
{/* Resources */}
|
|
228
|
+
<div className="w-full">
|
|
229
|
+
<h2 className="text-xl font-semibold text-white mb-4">Resources</h2>
|
|
230
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
231
|
+
{/* Documentation */}
|
|
232
|
+
<Link href="/docs">
|
|
233
|
+
<Card className="bg-[#0a0a0a] border-[#2a2a2a] hover:border-[#ff5722]/50 transition-colors cursor-pointer h-full">
|
|
234
|
+
<CardHeader>
|
|
235
|
+
<BookOpen className="w-8 h-8 text-[#ff5722] mb-2" />
|
|
236
|
+
<CardTitle className="text-white">Documentation</CardTitle>
|
|
237
|
+
<CardDescription>
|
|
238
|
+
Complete guides, API reference, and tutorials
|
|
239
|
+
</CardDescription>
|
|
240
|
+
</CardHeader>
|
|
241
|
+
</Card>
|
|
242
|
+
</Link>
|
|
243
|
+
|
|
244
|
+
{/* GitHub */}
|
|
245
|
+
<a href="https://github.com/saas-scaffold/cli" target="_blank" rel="noopener noreferrer">
|
|
246
|
+
<Card className="bg-[#0a0a0a] border-[#2a2a2a] hover:border-[#ff5722]/50 transition-colors cursor-pointer h-full">
|
|
247
|
+
<CardHeader>
|
|
248
|
+
<Github className="w-8 h-8 text-[#ff5722] mb-2" />
|
|
249
|
+
<CardTitle className="text-white">GitHub Repository</CardTitle>
|
|
250
|
+
<CardDescription>
|
|
251
|
+
Source code, issues, and contributions
|
|
252
|
+
</CardDescription>
|
|
253
|
+
</CardHeader>
|
|
254
|
+
</Card>
|
|
255
|
+
</a>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</section>
|
|
259
|
+
);
|
|
260
|
+
}
|