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,422 @@
|
|
|
1
|
+
import DocsToc from "@/components/docs/docs-toc";
|
|
2
|
+
import DocsCategoryPage, { type CategoryPageModel } from "@/components/docs/docs-category-page";
|
|
3
|
+
|
|
4
|
+
const pages: Record<
|
|
5
|
+
string,
|
|
6
|
+
{
|
|
7
|
+
title: string;
|
|
8
|
+
updated: string;
|
|
9
|
+
toc: { id: string; title: string }[];
|
|
10
|
+
sections: { id: string; heading: string; body: string }[];
|
|
11
|
+
}
|
|
12
|
+
> = {
|
|
13
|
+
authentication: {
|
|
14
|
+
title: "Authentication",
|
|
15
|
+
updated: "December 18, 2025",
|
|
16
|
+
toc: [
|
|
17
|
+
{ id: "better-auth", title: "Better Auth" },
|
|
18
|
+
{ id: "supabase-auth", title: "Supabase Auth" },
|
|
19
|
+
{ id: "nextauth", title: "NextAuth" },
|
|
20
|
+
],
|
|
21
|
+
sections: [
|
|
22
|
+
{ id: "better-auth", heading: "Better Auth", body: "Modern auth with database-backed sessions and provider linking." },
|
|
23
|
+
{ id: "supabase-auth", heading: "Supabase Auth", body: "Use Supabase Auth for magic links, OAuth, and session management." },
|
|
24
|
+
{ id: "nextauth", heading: "NextAuth", body: "Classic Next.js auth with OAuth providers and adapters." },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
payments: {
|
|
28
|
+
title: "Payments",
|
|
29
|
+
updated: "December 18, 2025",
|
|
30
|
+
toc: [
|
|
31
|
+
{ id: "stripe", title: "Stripe" },
|
|
32
|
+
{ id: "lemon-squeezy", title: "Lemon Squeezy" },
|
|
33
|
+
{ id: "polar", title: "Polar" },
|
|
34
|
+
{ id: "webhooks", title: "Webhooks" },
|
|
35
|
+
],
|
|
36
|
+
sections: [
|
|
37
|
+
{ id: "stripe", heading: "Stripe", body: "Create checkout sessions and manage subscriptions with Stripe." },
|
|
38
|
+
{ id: "lemon-squeezy", heading: "Lemon Squeezy", body: "Sell subscriptions and one-time payments with Lemon Squeezy." },
|
|
39
|
+
{ id: "polar", heading: "Polar", body: "Subscriptions + customer portal with Polar (current default in this project)." },
|
|
40
|
+
{ id: "webhooks", heading: "Webhooks", body: "Handle payment events to update user access and subscription state." },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
emails: {
|
|
44
|
+
title: "Emails",
|
|
45
|
+
updated: "December 18, 2025",
|
|
46
|
+
toc: [
|
|
47
|
+
{ id: "resend", title: "Resend" },
|
|
48
|
+
{ id: "mailgun", title: "Mailgun" },
|
|
49
|
+
{ id: "inbound-webhooks", title: "Inbound webhooks" },
|
|
50
|
+
{ id: "dns", title: "DNS (DKIM/DMARC/SPF)" },
|
|
51
|
+
],
|
|
52
|
+
sections: [
|
|
53
|
+
{ id: "resend", heading: "Resend", body: "Send transactional emails with Resend and keep deliverability high." },
|
|
54
|
+
{ id: "mailgun", heading: "Mailgun", body: "Send transactional emails with Mailgun and configure sending domains." },
|
|
55
|
+
{ id: "inbound-webhooks", heading: "Inbound webhooks", body: "Receive inbound emails and forward them to your app/webhook endpoint." },
|
|
56
|
+
{ id: "dns", heading: "DNS (DKIM/DMARC/SPF)", body: "Set up DNS records on a subdomain to avoid the spam folder." },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
database: {
|
|
60
|
+
title: "Database",
|
|
61
|
+
updated: "December 18, 2025",
|
|
62
|
+
toc: [
|
|
63
|
+
{ id: "supabase-postgres", title: "Supabase Postgres" },
|
|
64
|
+
{ id: "neon-postgres", title: "Neon Postgres" },
|
|
65
|
+
{ id: "mongodb", title: "MongoDB" },
|
|
66
|
+
{ id: "schema", title: "Schema & migrations" },
|
|
67
|
+
],
|
|
68
|
+
sections: [
|
|
69
|
+
{ id: "supabase-postgres", heading: "Supabase Postgres", body: "Use Supabase’s hosted Postgres and connection strings." },
|
|
70
|
+
{ id: "neon-postgres", heading: "Neon Postgres", body: "Use Neon serverless Postgres with Drizzle ORM." },
|
|
71
|
+
{ id: "mongodb", heading: "MongoDB", body: "Use MongoDB with Mongoose schemas + useful plugins." },
|
|
72
|
+
{ id: "schema", heading: "Schema & migrations", body: "Create tables/schemas and keep them in sync across environments." },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
seo: {
|
|
76
|
+
title: "SEO",
|
|
77
|
+
updated: "December 18, 2025",
|
|
78
|
+
toc: [
|
|
79
|
+
{ id: "blog", title: "Blog starter" },
|
|
80
|
+
{ id: "metadata", title: "Meta tags" },
|
|
81
|
+
{ id: "opengraph", title: "OpenGraph" },
|
|
82
|
+
{ id: "sitemap", title: "Sitemap" },
|
|
83
|
+
{ id: "structured-data", title: "Structured data" },
|
|
84
|
+
],
|
|
85
|
+
sections: [
|
|
86
|
+
{ id: "blog", heading: "Blog starter", body: "A starter blog structure and example posts." },
|
|
87
|
+
{ id: "metadata", heading: "Meta tags", body: "Defaults for title/description and page-level overrides." },
|
|
88
|
+
{ id: "opengraph", heading: "OpenGraph", body: "OpenGraph tags for beautiful social sharing previews." },
|
|
89
|
+
{ id: "sitemap", heading: "Sitemap", body: "Automated sitemap generation for faster indexing." },
|
|
90
|
+
{ id: "structured-data", heading: "Structured data", body: "Schema.org markup for rich snippets." },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
style: {
|
|
94
|
+
title: "Style",
|
|
95
|
+
updated: "December 18, 2025",
|
|
96
|
+
toc: [
|
|
97
|
+
{ id: "tailwind", title: "Tailwind CSS" },
|
|
98
|
+
{ id: "daisyui", title: "daisyUI themes" },
|
|
99
|
+
{ id: "dark-mode", title: "Automatic dark mode" },
|
|
100
|
+
{ id: "components", title: "UI components" },
|
|
101
|
+
],
|
|
102
|
+
sections: [
|
|
103
|
+
{ id: "tailwind", heading: "Tailwind CSS", body: "Utility-first styling with a good default design system." },
|
|
104
|
+
{ id: "daisyui", heading: "daisyUI themes", body: "Prebuilt themes to quickly re-skin the app." },
|
|
105
|
+
{ id: "dark-mode", heading: "Automatic dark mode", body: "Theme toggle + persisted preference." },
|
|
106
|
+
{ id: "components", heading: "UI components", body: "A component library with common sections, forms, and layout." },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
"production-checklist": {
|
|
110
|
+
title: "Production Checklist",
|
|
111
|
+
updated: "December 18, 2025",
|
|
112
|
+
toc: [
|
|
113
|
+
{ id: "overview", title: "Overview" },
|
|
114
|
+
{ id: "environment-variables", title: "Environment variables" },
|
|
115
|
+
{ id: "webhooks", title: "Webhooks" },
|
|
116
|
+
],
|
|
117
|
+
sections: [
|
|
118
|
+
{
|
|
119
|
+
id: "overview",
|
|
120
|
+
heading: "Overview",
|
|
121
|
+
body: "A short checklist to make sure your SaaS is ready for production deployment.",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: "environment-variables",
|
|
125
|
+
heading: "Environment variables",
|
|
126
|
+
body: "Set DATABASE_URL, BETTER_AUTH_SECRET, POLAR_WEBHOOK_SECRET, and any provider keys used by your selected features.",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "webhooks",
|
|
130
|
+
heading: "Webhooks",
|
|
131
|
+
body: "Verify Polar webhook endpoints, signature validation, and idempotency handling before launch.",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
"knowledge-base": {
|
|
136
|
+
title: "Knowledge Base",
|
|
137
|
+
updated: "December 18, 2025",
|
|
138
|
+
toc: [
|
|
139
|
+
{ id: "common-issues", title: "Common issues" },
|
|
140
|
+
{ id: "database-setup", title: "Database setup" },
|
|
141
|
+
],
|
|
142
|
+
sections: [
|
|
143
|
+
{ id: "common-issues", heading: "Common issues", body: "Troubleshooting notes and known gotchas." },
|
|
144
|
+
{ id: "database-setup", heading: "Database setup", body: "How to configure DATABASE_URL for Neon or Supabase." },
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
"build-your-applications": {
|
|
148
|
+
title: "Build your applications",
|
|
149
|
+
updated: "December 18, 2025",
|
|
150
|
+
toc: [{ id: "structure", title: "Project structure" }],
|
|
151
|
+
sections: [{ id: "structure", heading: "Project structure", body: "How the app is organized and where to extend." }],
|
|
152
|
+
},
|
|
153
|
+
"deploy-and-scale": {
|
|
154
|
+
title: "Deploy and scale",
|
|
155
|
+
updated: "December 18, 2025",
|
|
156
|
+
toc: [{ id: "vercel", title: "Vercel deploy" }],
|
|
157
|
+
sections: [{ id: "vercel", heading: "Vercel deploy", body: "Connect your repo, set env vars, deploy." }],
|
|
158
|
+
},
|
|
159
|
+
secure: {
|
|
160
|
+
title: "Secure your applications",
|
|
161
|
+
updated: "December 18, 2025",
|
|
162
|
+
toc: [{ id: "best-practices", title: "Best practices" }],
|
|
163
|
+
sections: [{ id: "best-practices", heading: "Best practices", body: "Security fundamentals for SaaS apps." }],
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const categoryPages: Record<string, CategoryPageModel> = {
|
|
168
|
+
authentication: {
|
|
169
|
+
title: "Authentication",
|
|
170
|
+
subtitle: "Choose an auth provider and ship login, sessions, protected pages, and OAuth without reinventing the wheel.",
|
|
171
|
+
updated: "December 18, 2025",
|
|
172
|
+
atAGlance: {
|
|
173
|
+
whatYouGet: ["Login + signup UX", "OAuth support (e.g. Google)", "Protected routes and sessions"],
|
|
174
|
+
youConfigure: ["Auth provider keys", "Redirect URLs", "Session secret"],
|
|
175
|
+
},
|
|
176
|
+
quickStart: [
|
|
177
|
+
{ title: "Add environment variables", description: "Add your auth secrets and OAuth keys to .env.local (BETTER_AUTH_SECRET, GOOGLE_CLIENT_ID, etc.)." },
|
|
178
|
+
{ title: "Configure OAuth redirects", description: "Set allowed callback URLs in your provider dashboard (Google, GitHub, etc.)." },
|
|
179
|
+
{ title: "Restart the dev server", description: "Restart npm run dev so environment variables are loaded." },
|
|
180
|
+
{ title: "Test sign-in flow", description: "Visit /sign-up, create an account, then access a protected page to verify everything works." },
|
|
181
|
+
],
|
|
182
|
+
variants: [
|
|
183
|
+
{ title: "Better Auth", href: "/docs/authentication/better-auth", summary: "Default in the current scaffold. Database-backed sessions and provider linking.", status: "available" },
|
|
184
|
+
{ title: "Supabase Auth", href: "/docs/authentication/supabase-auth", summary: "Magic links + OAuth via Supabase Auth, with a managed user system.", status: "coming_soon" },
|
|
185
|
+
{ title: "NextAuth", href: "/docs/authentication/nextauth", summary: "Popular Next.js auth option with providers and adapter-based persistence.", status: "coming_soon" },
|
|
186
|
+
],
|
|
187
|
+
snippets: [
|
|
188
|
+
{
|
|
189
|
+
title: "Scaffold a new app",
|
|
190
|
+
description: "Generate a new project and start the dev server.",
|
|
191
|
+
variant: "terminal",
|
|
192
|
+
code: `$ npx shipd init my-app\n$ cd my-app\n$ npm run dev`,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
title: ".env.local (auth essentials)",
|
|
196
|
+
description: "Minimal keys you’ll set for Better Auth + Google OAuth.",
|
|
197
|
+
variant: "env",
|
|
198
|
+
code: `# Auth\nBETTER_AUTH_SECRET=\"please-change-me\"\nGOOGLE_CLIENT_ID=\"your-google-client-id\"\nGOOGLE_CLIENT_SECRET=\"your-google-client-secret\"`,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
payments: {
|
|
203
|
+
title: "Payments",
|
|
204
|
+
subtitle: "Add subscriptions and checkout flows with webhook-driven access control.",
|
|
205
|
+
updated: "December 18, 2025",
|
|
206
|
+
atAGlance: {
|
|
207
|
+
whatYouGet: ["Checkout flow", "Webhook handling", "Subscription state tracking"],
|
|
208
|
+
youConfigure: ["Payment provider keys", "Webhook secret", "Product IDs / price IDs"],
|
|
209
|
+
},
|
|
210
|
+
quickStart: [
|
|
211
|
+
{ title: "Add payment provider keys", description: "Add your provider API keys and webhook secret to .env.local (POLAR_ACCESS_TOKEN, POLAR_WEBHOOK_SECRET, etc.)." },
|
|
212
|
+
{ title: "Create subscription products", description: "Create your subscription tiers/prices in your provider dashboard (Polar.sh, Stripe, etc.)." },
|
|
213
|
+
{ title: "Configure webhook endpoint", description: "Point webhooks to your app's /api/webhooks/[provider] route and verify signature validation is enabled." },
|
|
214
|
+
{ title: "Test checkout flow", description: "Visit /pricing, select a plan, complete checkout, and verify subscription state updates in the dashboard." },
|
|
215
|
+
],
|
|
216
|
+
variants: [
|
|
217
|
+
{ title: "Polar", href: "/docs/payments/polar", summary: "Default in the current scaffold. Subscriptions + customer portal with Polar.", status: "available" },
|
|
218
|
+
{ title: "Stripe", href: "/docs/payments/stripe", summary: "Industry standard payments. Subscriptions, invoices, coupons, and more.", status: "coming_soon" },
|
|
219
|
+
{ title: "Lemon Squeezy", href: "/docs/payments/lemon-squeezy", summary: "Fast SaaS billing and digital products with a simple setup.", status: "coming_soon" },
|
|
220
|
+
],
|
|
221
|
+
snippets: [
|
|
222
|
+
{
|
|
223
|
+
title: ".env.local (Polar)",
|
|
224
|
+
description: "Keys needed to enable checkout + webhook handling.",
|
|
225
|
+
variant: "env",
|
|
226
|
+
code: `# Polar\nPOLAR_ACCESS_TOKEN=\"your-polar-access-token\"\nPOLAR_WEBHOOK_SECRET=\"your-webhook-secret\"\nPOLAR_SUCCESS_URL=\"http://localhost:3000/success\"\nNEXT_PUBLIC_STARTER_TIER=\"your-starter-tier-id\"\nNEXT_PUBLIC_STARTER_SLUG=\"your-org-slug\"`,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
title: "Run a local webhook test",
|
|
230
|
+
description: "Once deployed, you’ll point your provider’s webhook to your /api route.",
|
|
231
|
+
variant: "terminal",
|
|
232
|
+
code: `$ # provider dashboard → set webhook URL\n$ # verify signature validation is enabled`,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
emails: {
|
|
237
|
+
title: "Emails",
|
|
238
|
+
subtitle: "Send transactional email and configure DNS so your messages land in inboxes (not spam).",
|
|
239
|
+
updated: "December 18, 2025",
|
|
240
|
+
atAGlance: {
|
|
241
|
+
whatYouGet: ["Transactional sends", "Deliverability checklist", "Optional inbound webhooks"],
|
|
242
|
+
youConfigure: ["Provider API key", "Sending domain", "DKIM/DMARC/SPF DNS records"],
|
|
243
|
+
},
|
|
244
|
+
quickStart: [
|
|
245
|
+
{ title: "Add email provider API key", description: "Add your provider API key to .env.local (RESEND_API_KEY, MAILGUN_API_KEY, etc.)." },
|
|
246
|
+
{ title: "Verify sending domain", description: "Set up a subdomain (e.g., mg.yourdomain.com) and verify it in your provider dashboard." },
|
|
247
|
+
{ title: "Configure DNS records", description: "Add DKIM, DMARC, and SPF records to your subdomain to avoid spam folders." },
|
|
248
|
+
{ title: "Test email sending", description: "Trigger a transactional email (e.g., welcome email on signup) and verify it arrives in inbox." },
|
|
249
|
+
],
|
|
250
|
+
variants: [
|
|
251
|
+
{ title: "Resend", href: "/docs/emails/resend", summary: "Simple transactional email provider with a great developer experience.", status: "coming_soon" },
|
|
252
|
+
{ title: "Mailgun", href: "/docs/emails/mailgun", summary: "Robust sending + optional inbound email processing capabilities.", status: "coming_soon" },
|
|
253
|
+
{ title: "Inbound webhooks", href: "/docs/emails/inbound-webhooks", summary: "Receive and forward inbound email events to your app endpoint.", status: "coming_soon" },
|
|
254
|
+
{ title: "DNS (DKIM/DMARC/SPF)", href: "/docs/emails/dns", summary: "Deliverability essentials—set these on a subdomain for best results.", status: "available" },
|
|
255
|
+
],
|
|
256
|
+
snippets: [
|
|
257
|
+
{
|
|
258
|
+
title: ".env.local (email provider)",
|
|
259
|
+
description: "Example env vars (provider-specific).",
|
|
260
|
+
variant: "env",
|
|
261
|
+
code: `# Email\nEMAIL_PROVIDER_API_KEY=\"your-provider-api-key\"\nEMAIL_FROM=\"Your App <no-reply@mg.yourdomain.com>\"`,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
title: "Deliverability checklist",
|
|
265
|
+
description: "Use a subdomain and configure DKIM/DMARC/SPF before you send.",
|
|
266
|
+
variant: "terminal",
|
|
267
|
+
code: `$ # DNS\n$ # DKIM + SPF + DMARC\n$ # verify domain in provider dashboard`,
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
database: {
|
|
272
|
+
title: "Database",
|
|
273
|
+
subtitle: "Pick your database and start with schemas/tables and safe migration workflows.",
|
|
274
|
+
updated: "December 18, 2025",
|
|
275
|
+
atAGlance: {
|
|
276
|
+
whatYouGet: ["Schema starter", "Migrations workflow", "Connection-string based config"],
|
|
277
|
+
youConfigure: ["DATABASE_URL", "Provider-specific credentials", "Migration commands"],
|
|
278
|
+
},
|
|
279
|
+
quickStart: [
|
|
280
|
+
{ title: "Set DATABASE_URL", description: "Add your database connection string to .env.local (e.g., from Neon or Supabase dashboard)." },
|
|
281
|
+
{ title: "Run migrations", description: "Generate and apply schema changes with drizzle-kit (npx drizzle-kit generate && npx drizzle-kit push)." },
|
|
282
|
+
{ title: "Restart the dev server", description: "Restart npm run dev so the database connection is loaded." },
|
|
283
|
+
{ title: "Verify database connection", description: "Sign up a test user and confirm auth/session data is stored correctly in the database." },
|
|
284
|
+
],
|
|
285
|
+
variants: [
|
|
286
|
+
{ title: "Neon Postgres", href: "/docs/database/neon-postgres", summary: "Default in the current scaffold. Serverless Postgres + Drizzle ORM.", status: "available" },
|
|
287
|
+
{ title: "Supabase Postgres", href: "/docs/database/supabase-postgres", summary: "Managed Postgres with Supabase dashboard and tooling.", status: "available" },
|
|
288
|
+
{ title: "MongoDB", href: "/docs/database/mongodb", summary: "Document database option (planned) with Mongoose schemas/plugins.", status: "coming_soon" },
|
|
289
|
+
{ title: "Schema & migrations", href: "/docs/database/schema-migrations", summary: "How to keep schema changes tracked and deployable.", status: "available" },
|
|
290
|
+
],
|
|
291
|
+
snippets: [
|
|
292
|
+
{
|
|
293
|
+
title: ".env.local (DATABASE_URL)",
|
|
294
|
+
description: "Use Neon or Supabase Postgres connection strings.",
|
|
295
|
+
variant: "env",
|
|
296
|
+
code: `DATABASE_URL=\"postgresql://postgres:YOUR_PASSWORD@db.yourproject.supabase.co:5432/postgres\"`,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
title: "Run migrations (Drizzle)",
|
|
300
|
+
description: "Generate and apply schema changes.",
|
|
301
|
+
variant: "terminal",
|
|
302
|
+
code: `$ npx drizzle-kit generate\n$ npx drizzle-kit push`,
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
seo: {
|
|
307
|
+
title: "SEO",
|
|
308
|
+
subtitle: "Ship SEO basics so your landing pages and docs are discoverable and share well.",
|
|
309
|
+
updated: "December 18, 2025",
|
|
310
|
+
atAGlance: {
|
|
311
|
+
whatYouGet: ["Meta tags defaults", "OpenGraph tags", "Sitemap + structured data options"],
|
|
312
|
+
youConfigure: ["Site name/URL", "Social image", "Blog structure (optional)"],
|
|
313
|
+
},
|
|
314
|
+
quickStart: [
|
|
315
|
+
{ title: "Set site metadata", description: "Update name/description and OpenGraph defaults." },
|
|
316
|
+
{ title: "Add a blog (optional)", description: "Publish engineering/launch posts to rank for keywords." },
|
|
317
|
+
{ title: "Generate sitemap", description: "Ensure pages are included and indexed quickly." },
|
|
318
|
+
{ title: "Add structured data", description: "Use schema.org markup for richer snippets." },
|
|
319
|
+
],
|
|
320
|
+
variants: [
|
|
321
|
+
{ title: "Blog starter", href: "/docs/seo/blog", summary: "A simple blog structure you can extend.", status: "available" },
|
|
322
|
+
{ title: "Meta tags", href: "/docs/seo/meta-tags", summary: "Title/description defaults and page overrides.", status: "available" },
|
|
323
|
+
{ title: "OpenGraph", href: "/docs/seo/opengraph", summary: "Social sharing previews for links.", status: "available" },
|
|
324
|
+
{ title: "Sitemap", href: "/docs/seo/sitemap", summary: "Automated sitemap generation.", status: "available" },
|
|
325
|
+
{ title: "Structured data", href: "/docs/seo/structured-data", summary: "Schema.org markup for rich snippets.", status: "available" },
|
|
326
|
+
],
|
|
327
|
+
snippets: [
|
|
328
|
+
{
|
|
329
|
+
title: "Metadata (Next.js)",
|
|
330
|
+
description: "Example page metadata with OpenGraph defaults.",
|
|
331
|
+
variant: "code",
|
|
332
|
+
code: `export const metadata = {\n title: \"My SaaS\",\n description: \"Launch fast with shipd\",\n openGraph: {\n title: \"My SaaS\",\n description: \"Launch fast with shipd\",\n },\n};`,
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
style: {
|
|
337
|
+
title: "Style",
|
|
338
|
+
subtitle: "Start with a polished UI kit and theming so your SaaS looks great from day one.",
|
|
339
|
+
updated: "December 18, 2025",
|
|
340
|
+
atAGlance: {
|
|
341
|
+
whatYouGet: ["Component library", "Responsive layout patterns", "Theme/dark mode support"],
|
|
342
|
+
youConfigure: ["Brand colors", "Theme choice", "Typography tweaks"],
|
|
343
|
+
},
|
|
344
|
+
quickStart: [
|
|
345
|
+
{ title: "Pick a base theme", description: "Start with Tailwind, then apply a theme system if needed." },
|
|
346
|
+
{ title: "Customize tokens", description: "Adjust colors, radii, and spacing to match your brand." },
|
|
347
|
+
{ title: "Enable dark mode", description: "Make sure theme preference persists across sessions." },
|
|
348
|
+
{ title: "Reuse sections", description: "Use prebuilt sections for pricing, testimonials, FAQ, etc." },
|
|
349
|
+
],
|
|
350
|
+
variants: [
|
|
351
|
+
{ title: "Tailwind CSS", href: "/docs/style/tailwind", summary: "Utility-first styling with sensible defaults.", status: "available" },
|
|
352
|
+
{ title: "daisyUI themes", href: "/docs/style/daisyui", summary: "Theme packs to re-skin your UI quickly (planned).", status: "coming_soon" },
|
|
353
|
+
{ title: "Automatic dark mode", href: "/docs/style/dark-mode", summary: "Toggle + persisted preference out of the box.", status: "available" },
|
|
354
|
+
{ title: "UI components", href: "/docs/style/ui-components", summary: "A starter component library for common flows and sections.", status: "available" },
|
|
355
|
+
],
|
|
356
|
+
snippets: [
|
|
357
|
+
{
|
|
358
|
+
title: "Use a component",
|
|
359
|
+
description: "Example of a shadcn/ui button.",
|
|
360
|
+
variant: "code",
|
|
361
|
+
code: `import { Button } from \"@/components/ui/button\";\n\nexport function Example() {\n return <Button>Get Started</Button>;\n}`,
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
export default async function DocsPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
368
|
+
const { slug } = await params;
|
|
369
|
+
const category = categoryPages[slug];
|
|
370
|
+
const page = pages[slug];
|
|
371
|
+
|
|
372
|
+
if (category) {
|
|
373
|
+
const toc = [
|
|
374
|
+
{ id: "at-a-glance", title: "At a glance" },
|
|
375
|
+
{ id: "quickstart", title: "Quickstart" },
|
|
376
|
+
{ id: "providers", title: "Providers" },
|
|
377
|
+
...(category.snippets?.length ? [{ id: "code", title: "Code" }] : []),
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<div className="grid grid-cols-1 xl:grid-cols-[minmax(0,1fr)_240px] gap-10">
|
|
382
|
+
<DocsCategoryPage model={category} />
|
|
383
|
+
<aside className="hidden xl:block">
|
|
384
|
+
<DocsToc items={toc} />
|
|
385
|
+
</aside>
|
|
386
|
+
</div>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (!page) {
|
|
391
|
+
return (
|
|
392
|
+
<div className="py-10">
|
|
393
|
+
<div className="text-3xl font-semibold tracking-tight">Not found</div>
|
|
394
|
+
<p className="mt-3 text-white/70">This docs page doesn’t exist yet.</p>
|
|
395
|
+
</div>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<div className="grid grid-cols-1 xl:grid-cols-[minmax(0,1fr)_240px] gap-10">
|
|
401
|
+
<article className="py-10">
|
|
402
|
+
<div className="text-[44px] leading-[1.1] font-semibold tracking-tight">{page.title}</div>
|
|
403
|
+
<div className="mt-4 text-sm text-white/60">Last updated {page.updated}</div>
|
|
404
|
+
|
|
405
|
+
<div className="mt-10 border-t border-white/10 pt-10 space-y-10">
|
|
406
|
+
{page.sections.map((s) => (
|
|
407
|
+
<section key={s.id} id={s.id} className="space-y-3">
|
|
408
|
+
<h2 className="text-2xl font-semibold tracking-tight">{s.heading}</h2>
|
|
409
|
+
<p className="text-white/70 leading-relaxed">{s.body}</p>
|
|
410
|
+
</section>
|
|
411
|
+
))}
|
|
412
|
+
</div>
|
|
413
|
+
</article>
|
|
414
|
+
|
|
415
|
+
<aside className="hidden xl:block">
|
|
416
|
+
<DocsToc items={page.toc} />
|
|
417
|
+
</aside>
|
|
418
|
+
</div>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import DocsToc from "@/components/docs/docs-toc";
|
|
2
|
+
|
|
3
|
+
const toc = [
|
|
4
|
+
{ id: "overview", title: "Overview" },
|
|
5
|
+
{ id: "authentication", title: "Authentication" },
|
|
6
|
+
{ id: "webhooks", title: "Webhooks" },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export default function DocsApiPage() {
|
|
10
|
+
return (
|
|
11
|
+
<div className="grid grid-cols-1 xl:grid-cols-[minmax(0,1fr)_240px] gap-10">
|
|
12
|
+
<article className="py-10">
|
|
13
|
+
<div className="text-[44px] leading-[1.1] font-semibold tracking-tight">API Reference</div>
|
|
14
|
+
<div className="mt-4 text-sm text-white/60">Last updated December 18, 2025</div>
|
|
15
|
+
|
|
16
|
+
<div className="mt-10 border-t border-white/10 pt-10 space-y-10">
|
|
17
|
+
<section id="overview" className="space-y-3">
|
|
18
|
+
<h2 className="text-2xl font-semibold tracking-tight">Overview</h2>
|
|
19
|
+
<p className="text-white/70 leading-relaxed">
|
|
20
|
+
This section documents the HTTP endpoints provided by the shipd app (auth, billing, webhooks).
|
|
21
|
+
</p>
|
|
22
|
+
</section>
|
|
23
|
+
|
|
24
|
+
<section id="authentication" className="space-y-3">
|
|
25
|
+
<h2 className="text-2xl font-semibold tracking-tight">Authentication</h2>
|
|
26
|
+
<p className="text-white/70 leading-relaxed">
|
|
27
|
+
Better Auth routes are available under <span className="text-white">/api/auth</span>.
|
|
28
|
+
</p>
|
|
29
|
+
</section>
|
|
30
|
+
|
|
31
|
+
<section id="webhooks" className="space-y-3">
|
|
32
|
+
<h2 className="text-2xl font-semibold tracking-tight">Webhooks</h2>
|
|
33
|
+
<p className="text-white/70 leading-relaxed">
|
|
34
|
+
Configure your Polar webhook URL to point to <span className="text-white">/api/subscription</span>.
|
|
35
|
+
</p>
|
|
36
|
+
</section>
|
|
37
|
+
</div>
|
|
38
|
+
</article>
|
|
39
|
+
|
|
40
|
+
<aside className="hidden xl:block">
|
|
41
|
+
<DocsToc items={toc} />
|
|
42
|
+
</aside>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { ArrowRight } from "lucide-react";
|
|
3
|
+
import { Badge } from "@/components/ui/badge";
|
|
4
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
5
|
+
import DocsCodeCard, { type DocsCodeCardProps } from "@/components/docs/docs-code-card";
|
|
6
|
+
|
|
7
|
+
export type CategoryVariant = {
|
|
8
|
+
title: string;
|
|
9
|
+
href: string;
|
|
10
|
+
summary: string;
|
|
11
|
+
status: "available" | "coming_soon";
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type CategoryQuickStartStep = {
|
|
15
|
+
title: string;
|
|
16
|
+
description: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type CategoryPageModel = {
|
|
20
|
+
title: string;
|
|
21
|
+
subtitle: string;
|
|
22
|
+
updated: string;
|
|
23
|
+
atAGlance: {
|
|
24
|
+
whatYouGet: string[];
|
|
25
|
+
youConfigure: string[];
|
|
26
|
+
};
|
|
27
|
+
quickStart: CategoryQuickStartStep[];
|
|
28
|
+
variants: CategoryVariant[];
|
|
29
|
+
snippets?: DocsCodeCardProps[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function StatusBadge({ status }: { status: CategoryVariant["status"] }) {
|
|
33
|
+
if (status === "available") return <Badge className="bg-emerald-500/15 text-emerald-200 border-emerald-500/30">Available</Badge>;
|
|
34
|
+
return <Badge variant="secondary" className="bg-white/10 text-white/70 border-white/10">Coming soon</Badge>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function BulletList({ items }: { items: string[] }) {
|
|
38
|
+
return (
|
|
39
|
+
<ul className="mt-3 space-y-2 text-sm text-white/70">
|
|
40
|
+
{items.map((t) => (
|
|
41
|
+
<li key={t} className="flex gap-2">
|
|
42
|
+
<span className="mt-[6px] h-1.5 w-1.5 rounded-full bg-white/40 shrink-0" />
|
|
43
|
+
<span>{t}</span>
|
|
44
|
+
</li>
|
|
45
|
+
))}
|
|
46
|
+
</ul>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default function DocsCategoryPage({ model }: { model: CategoryPageModel }) {
|
|
51
|
+
return (
|
|
52
|
+
<article className="py-10">
|
|
53
|
+
<div className="text-[44px] leading-[1.1] font-semibold tracking-tight">{model.title}</div>
|
|
54
|
+
<p className="mt-4 text-white/70 text-lg leading-relaxed max-w-3xl">{model.subtitle}</p>
|
|
55
|
+
<div className="mt-4 text-sm text-white/60">Last updated {model.updated}</div>
|
|
56
|
+
|
|
57
|
+
<div className="mt-10 border-t border-white/10 pt-10 space-y-12">
|
|
58
|
+
<section id="at-a-glance" className="space-y-5">
|
|
59
|
+
<h2 className="text-2xl font-semibold tracking-tight">At a glance</h2>
|
|
60
|
+
|
|
61
|
+
<div className="grid gap-6 lg:grid-cols-2">
|
|
62
|
+
<Card className="bg-white/5 border-white/10 shadow-none">
|
|
63
|
+
<CardContent className="px-6">
|
|
64
|
+
<div className="pt-4 text-sm font-semibold text-white">What you get</div>
|
|
65
|
+
<BulletList items={model.atAGlance.whatYouGet} />
|
|
66
|
+
<div className="pb-4" />
|
|
67
|
+
</CardContent>
|
|
68
|
+
</Card>
|
|
69
|
+
|
|
70
|
+
<Card className="bg-white/5 border-white/10 shadow-none">
|
|
71
|
+
<CardContent className="px-6">
|
|
72
|
+
<div className="pt-4 text-sm font-semibold text-white">You'll configure</div>
|
|
73
|
+
<BulletList items={model.atAGlance.youConfigure} />
|
|
74
|
+
<div className="pb-4" />
|
|
75
|
+
</CardContent>
|
|
76
|
+
</Card>
|
|
77
|
+
</div>
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
<section id="quickstart" className="space-y-5">
|
|
81
|
+
<h2 className="text-2xl font-semibold tracking-tight">Quickstart</h2>
|
|
82
|
+
<div className="space-y-3">
|
|
83
|
+
{model.quickStart.map((step, idx) => (
|
|
84
|
+
<div key={step.title} className="relative">
|
|
85
|
+
{/* Connector line */}
|
|
86
|
+
{idx < model.quickStart.length - 1 ? (
|
|
87
|
+
<div className="absolute left-8 top-14 bottom-0 w-px bg-white/10" />
|
|
88
|
+
) : null}
|
|
89
|
+
|
|
90
|
+
<Card className="bg-white/5 border-white/10 shadow-none">
|
|
91
|
+
<CardContent className="px-6">
|
|
92
|
+
<div className="pt-4 flex items-start gap-3">
|
|
93
|
+
<div className="mt-0.5 flex items-center justify-center">
|
|
94
|
+
<div className="h-9 w-9 rounded-full border border-white/10 bg-black/30 flex items-center justify-center text-sm font-semibold text-white/80">
|
|
95
|
+
{idx + 1}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="min-w-0">
|
|
99
|
+
<div className="text-base font-semibold text-white">{step.title}</div>
|
|
100
|
+
<p className="mt-1 text-sm text-white/60 leading-relaxed">{step.description}</p>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
<div className="pb-4" />
|
|
104
|
+
</CardContent>
|
|
105
|
+
</Card>
|
|
106
|
+
</div>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
<section id="providers" className="space-y-5">
|
|
112
|
+
<h2 className="text-2xl font-semibold tracking-tight">Providers</h2>
|
|
113
|
+
<p className="text-white/60 text-sm max-w-3xl">
|
|
114
|
+
Pick an option below. Each provider has a short guide so you can implement without digging through docs.
|
|
115
|
+
</p>
|
|
116
|
+
|
|
117
|
+
<div className="space-y-3">
|
|
118
|
+
{model.variants.map((v) => (
|
|
119
|
+
<details
|
|
120
|
+
key={v.href}
|
|
121
|
+
className="group rounded-xl border border-white/10 bg-white/5 open:bg-white/[0.07] transition-colors"
|
|
122
|
+
>
|
|
123
|
+
<summary className="cursor-pointer list-none px-5 py-3.5 flex items-center gap-3">
|
|
124
|
+
<div className="text-sm font-semibold text-white">{v.title}</div>
|
|
125
|
+
<StatusBadge status={v.status} />
|
|
126
|
+
<div className="ml-auto text-xs text-white/50 group-open:hidden">Expand</div>
|
|
127
|
+
<div className="ml-auto text-xs text-white/50 hidden group-open:block">Collapse</div>
|
|
128
|
+
</summary>
|
|
129
|
+
<div className="px-5 pb-4">
|
|
130
|
+
<p className="text-sm text-white/65 leading-relaxed">{v.summary}</p>
|
|
131
|
+
<div className="mt-4">
|
|
132
|
+
<Link
|
|
133
|
+
href={v.href}
|
|
134
|
+
className="inline-flex items-center gap-2 rounded-md bg-white text-black px-3 py-2 text-sm font-medium hover:bg-white/90 transition-colors"
|
|
135
|
+
>
|
|
136
|
+
Read guide <ArrowRight className="h-4 w-4" />
|
|
137
|
+
</Link>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</details>
|
|
141
|
+
))}
|
|
142
|
+
</div>
|
|
143
|
+
</section>
|
|
144
|
+
|
|
145
|
+
{model.snippets?.length ? (
|
|
146
|
+
<section id="code" className="space-y-5">
|
|
147
|
+
<h2 className="text-2xl font-semibold tracking-tight">Code</h2>
|
|
148
|
+
<p className="text-white/60 text-sm max-w-3xl">
|
|
149
|
+
Copy/paste-friendly code blocks. Designed to get you working quickly without digging through long docs.
|
|
150
|
+
</p>
|
|
151
|
+
<div className="space-y-4">
|
|
152
|
+
{model.snippets.map((s) => (
|
|
153
|
+
<DocsCodeCard key={s.title} {...s} />
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
</section>
|
|
157
|
+
) : null}
|
|
158
|
+
</div>
|
|
159
|
+
</article>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|