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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1366 -0
  5. package/docs-template/README.md +255 -0
  6. package/docs-template/[slug]/[subslug]/page.tsx +1242 -0
  7. package/docs-template/[slug]/page.tsx +422 -0
  8. package/docs-template/api/page.tsx +47 -0
  9. package/docs-template/components/docs/docs-category-page.tsx +162 -0
  10. package/docs-template/components/docs/docs-code-card.tsx +135 -0
  11. package/docs-template/components/docs/docs-header.tsx +69 -0
  12. package/docs-template/components/docs/docs-nav.ts +95 -0
  13. package/docs-template/components/docs/docs-sidebar.tsx +112 -0
  14. package/docs-template/components/docs/docs-toc.tsx +38 -0
  15. package/docs-template/components/ui/badge.tsx +47 -0
  16. package/docs-template/components/ui/button.tsx +60 -0
  17. package/docs-template/components/ui/card.tsx +93 -0
  18. package/docs-template/components/ui/sheet.tsx +140 -0
  19. package/docs-template/documentation/page.tsx +80 -0
  20. package/docs-template/layout.tsx +27 -0
  21. package/docs-template/lib/utils.ts +7 -0
  22. package/docs-template/page.tsx +360 -0
  23. package/package.json +66 -0
  24. package/template/.env.example +45 -0
  25. package/template/README.md +239 -0
  26. package/template/app/api/auth/[...all]/route.ts +4 -0
  27. package/template/app/api/chat/route.ts +16 -0
  28. package/template/app/api/subscription/route.ts +25 -0
  29. package/template/app/api/upload-image/route.ts +64 -0
  30. package/template/app/blog/[slug]/page.tsx +314 -0
  31. package/template/app/blog/page.tsx +107 -0
  32. package/template/app/dashboard/_components/chart-interactive.tsx +289 -0
  33. package/template/app/dashboard/_components/chatbot.tsx +39 -0
  34. package/template/app/dashboard/_components/mode-toggle.tsx +46 -0
  35. package/template/app/dashboard/_components/navbar.tsx +84 -0
  36. package/template/app/dashboard/_components/section-cards.tsx +102 -0
  37. package/template/app/dashboard/_components/sidebar.tsx +90 -0
  38. package/template/app/dashboard/_components/subscribe-button.tsx +49 -0
  39. package/template/app/dashboard/billing/page.tsx +277 -0
  40. package/template/app/dashboard/chat/page.tsx +73 -0
  41. package/template/app/dashboard/cli/page.tsx +260 -0
  42. package/template/app/dashboard/layout.tsx +24 -0
  43. package/template/app/dashboard/page.tsx +216 -0
  44. package/template/app/dashboard/payment/_components/manage-subscription.tsx +22 -0
  45. package/template/app/dashboard/payment/page.tsx +126 -0
  46. package/template/app/dashboard/settings/page.tsx +613 -0
  47. package/template/app/dashboard/upload/page.tsx +324 -0
  48. package/template/app/error.tsx +78 -0
  49. package/template/app/favicon.ico +0 -0
  50. package/template/app/globals.css +126 -0
  51. package/template/app/layout.tsx +135 -0
  52. package/template/app/not-found.tsx +45 -0
  53. package/template/app/page.tsx +28 -0
  54. package/template/app/pricing/_component/pricing-table.tsx +276 -0
  55. package/template/app/pricing/page.tsx +23 -0
  56. package/template/app/privacy-policy/page.tsx +280 -0
  57. package/template/app/robots.txt +12 -0
  58. package/template/app/sign-in/page.tsx +228 -0
  59. package/template/app/sign-up/page.tsx +243 -0
  60. package/template/app/sitemap.ts +62 -0
  61. package/template/app/success/page.tsx +123 -0
  62. package/template/app/terms-of-service/page.tsx +212 -0
  63. package/template/auth-schema.ts +47 -0
  64. package/template/components/homepage/cli-workflow-section.tsx +138 -0
  65. package/template/components/homepage/features-section.tsx +150 -0
  66. package/template/components/homepage/footer.tsx +53 -0
  67. package/template/components/homepage/hero-section.tsx +112 -0
  68. package/template/components/homepage/integrations.tsx +124 -0
  69. package/template/components/homepage/navigation.tsx +116 -0
  70. package/template/components/homepage/news-section.tsx +82 -0
  71. package/template/components/homepage/testimonials-section.tsx +34 -0
  72. package/template/components/logos/BetterAuth.tsx +21 -0
  73. package/template/components/logos/NeonPostgres.tsx +41 -0
  74. package/template/components/logos/Nextjs.tsx +72 -0
  75. package/template/components/logos/Polar.tsx +7 -0
  76. package/template/components/logos/TailwindCSS.tsx +27 -0
  77. package/template/components/logos/index.ts +6 -0
  78. package/template/components/logos/shadcnui.tsx +8 -0
  79. package/template/components/provider.tsx +8 -0
  80. package/template/components/ui/avatar.tsx +53 -0
  81. package/template/components/ui/badge.tsx +46 -0
  82. package/template/components/ui/button.tsx +59 -0
  83. package/template/components/ui/card.tsx +92 -0
  84. package/template/components/ui/chart.tsx +353 -0
  85. package/template/components/ui/checkbox.tsx +32 -0
  86. package/template/components/ui/dialog.tsx +135 -0
  87. package/template/components/ui/dropdown-menu.tsx +257 -0
  88. package/template/components/ui/form.tsx +167 -0
  89. package/template/components/ui/input.tsx +21 -0
  90. package/template/components/ui/label.tsx +24 -0
  91. package/template/components/ui/progress.tsx +31 -0
  92. package/template/components/ui/resizable.tsx +56 -0
  93. package/template/components/ui/select.tsx +185 -0
  94. package/template/components/ui/separator.tsx +28 -0
  95. package/template/components/ui/sheet.tsx +139 -0
  96. package/template/components/ui/skeleton.tsx +13 -0
  97. package/template/components/ui/sonner.tsx +25 -0
  98. package/template/components/ui/switch.tsx +31 -0
  99. package/template/components/ui/tabs.tsx +66 -0
  100. package/template/components/ui/textarea.tsx +18 -0
  101. package/template/components/ui/toggle-group.tsx +73 -0
  102. package/template/components/ui/toggle.tsx +47 -0
  103. package/template/components/ui/tooltip.tsx +61 -0
  104. package/template/components/user-profile.tsx +139 -0
  105. package/template/components.json +21 -0
  106. package/template/db/drizzle.ts +14 -0
  107. package/template/db/migrations/0000_worried_rawhide_kid.sql +77 -0
  108. package/template/db/migrations/meta/0000_snapshot.json +494 -0
  109. package/template/db/migrations/meta/_journal.json +13 -0
  110. package/template/db/schema.ts +85 -0
  111. package/template/drizzle.config.ts +13 -0
  112. package/template/emails/components/layout.tsx +181 -0
  113. package/template/emails/password-reset.tsx +67 -0
  114. package/template/emails/payment-failed.tsx +167 -0
  115. package/template/emails/subscription-confirmation.tsx +129 -0
  116. package/template/emails/welcome.tsx +100 -0
  117. package/template/eslint.config.mjs +16 -0
  118. package/template/hooks/use-mobile.ts +21 -0
  119. package/template/lib/auth-client.ts +8 -0
  120. package/template/lib/auth.ts +276 -0
  121. package/template/lib/email.ts +118 -0
  122. package/template/lib/polar-products.ts +49 -0
  123. package/template/lib/subscription.ts +148 -0
  124. package/template/lib/upload-image.ts +28 -0
  125. package/template/lib/utils.ts +6 -0
  126. package/template/middleware.ts +30 -0
  127. package/template/next-env.d.ts +5 -0
  128. package/template/next.config.ts +27 -0
  129. package/template/package.json +99 -0
  130. package/template/postcss.config.mjs +5 -0
  131. package/template/public/add.png +0 -0
  132. package/template/public/favicon.svg +4 -0
  133. package/template/public/file.svg +1 -0
  134. package/template/public/globe.svg +1 -0
  135. package/template/public/iphone.png +0 -0
  136. package/template/public/logo.png +0 -0
  137. package/template/public/next.svg +1 -0
  138. package/template/public/polar-sh.svg +1 -0
  139. package/template/public/shadcn-ui.svg +1 -0
  140. package/template/public/site.webmanifest +21 -0
  141. package/template/public/vercel.svg +1 -0
  142. package/template/public/window.svg +1 -0
  143. package/template/tailwind.config.ts +89 -0
  144. package/template/template.config.json +138 -0
  145. package/template/tsconfig.json +27 -0
@@ -0,0 +1,324 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from "@/components/ui/card";
11
+ import { Input } from "@/components/ui/input";
12
+ import { Progress } from "@/components/ui/progress";
13
+ import { Check, FileImage, Upload, X } from "lucide-react";
14
+ import Image from "next/image";
15
+ import { useCallback, useState } from "react";
16
+ import { toast } from "sonner";
17
+
18
+ interface UploadedFile {
19
+ id: string;
20
+ name: string;
21
+ url: string;
22
+ size: number;
23
+ type: string;
24
+ uploadedAt: Date;
25
+ }
26
+
27
+ export default function UploadPage() {
28
+ const [uploading, setUploading] = useState(false);
29
+ const [uploadProgress, setUploadProgress] = useState(0);
30
+ const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
31
+ const [dragActive, setDragActive] = useState(false);
32
+
33
+ const handleFileUpload = async (files: FileList | File[]) => {
34
+ const fileArray = Array.from(files);
35
+
36
+ for (const file of fileArray) {
37
+ if (!file.type.startsWith("image/")) {
38
+ toast.error(`${file.name} is not an image file`);
39
+ continue;
40
+ }
41
+
42
+ if (file.size > 5 * 1024 * 1024) {
43
+ toast.error(`${file.name} is too large (max 5MB)`);
44
+ continue;
45
+ }
46
+
47
+ setUploading(true);
48
+ setUploadProgress(0);
49
+
50
+ try {
51
+ const formData = new FormData();
52
+ formData.append("file", file);
53
+
54
+ // Simulate progress
55
+ const progressInterval = setInterval(() => {
56
+ setUploadProgress((prev) => {
57
+ if (prev >= 90) {
58
+ clearInterval(progressInterval);
59
+ return prev;
60
+ }
61
+ return prev + Math.random() * 20;
62
+ });
63
+ }, 200);
64
+
65
+ const response = await fetch("/api/upload-image", {
66
+ method: "POST",
67
+ body: formData,
68
+ });
69
+
70
+ clearInterval(progressInterval);
71
+ setUploadProgress(100);
72
+
73
+ if (!response.ok) {
74
+ throw new Error("Upload failed");
75
+ }
76
+
77
+ const { url } = await response.json();
78
+
79
+ const uploadedFile: UploadedFile = {
80
+ id: crypto.randomUUID(),
81
+ name: file.name,
82
+ url,
83
+ size: file.size,
84
+ type: file.type,
85
+ uploadedAt: new Date(),
86
+ };
87
+
88
+ setUploadedFiles((prev) => [uploadedFile, ...prev]);
89
+ toast.success(`${file.name} uploaded successfully`);
90
+ } catch (error) {
91
+ console.error("Upload error:", error);
92
+ toast.error(`Failed to upload ${file.name}`);
93
+ } finally {
94
+ setUploading(false);
95
+ setUploadProgress(0);
96
+ }
97
+ }
98
+ };
99
+
100
+ const handleDrag = useCallback((e: React.DragEvent) => {
101
+ e.preventDefault();
102
+ e.stopPropagation();
103
+ if (e.type === "dragenter" || e.type === "dragover") {
104
+ setDragActive(true);
105
+ } else if (e.type === "dragleave") {
106
+ setDragActive(false);
107
+ }
108
+ }, []);
109
+
110
+ const handleDrop = useCallback((e: React.DragEvent) => {
111
+ e.preventDefault();
112
+ e.stopPropagation();
113
+ setDragActive(false);
114
+
115
+ if (e.dataTransfer.files && e.dataTransfer.files[0]) {
116
+ handleFileUpload(e.dataTransfer.files);
117
+ }
118
+ }, []);
119
+
120
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
121
+ if (e.target.files && e.target.files[0]) {
122
+ handleFileUpload(e.target.files);
123
+ }
124
+ };
125
+
126
+ const removeFile = (id: string) => {
127
+ setUploadedFiles((prev) => prev.filter((file) => file.id !== id));
128
+ };
129
+
130
+ const formatFileSize = (bytes: number) => {
131
+ if (bytes === 0) return "0 Bytes";
132
+ const k = 1024;
133
+ const sizes = ["Bytes", "KB", "MB", "GB"];
134
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
135
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
136
+ };
137
+
138
+ return (
139
+ <div className="p-6 space-y-6">
140
+ <div>
141
+ <h1 className="text-3xl font-semibold tracking-tight">File Upload</h1>
142
+ <p className="text-muted-foreground mt-2">
143
+ Upload images to Cloudflare R2 storage with drag and drop support
144
+ </p>
145
+ </div>
146
+
147
+ <div className="grid gap-6 md:grid-cols-2">
148
+ {/* Upload Area */}
149
+ <Card>
150
+ <CardHeader>
151
+ <CardTitle className="flex items-center gap-2">
152
+ <Upload className="h-5 w-5" />
153
+ Upload Images
154
+ </CardTitle>
155
+ <CardDescription>
156
+ Upload images to Cloudflare R2. Maximum file size is 5MB.
157
+ </CardDescription>
158
+ </CardHeader>
159
+ <CardContent className="space-y-4">
160
+ <div
161
+ className={`relative border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
162
+ dragActive
163
+ ? "border-primary bg-primary/5"
164
+ : "border-muted-foreground/25 hover:border-muted-foreground/50"
165
+ }`}
166
+ onDragEnter={handleDrag}
167
+ onDragLeave={handleDrag}
168
+ onDragOver={handleDrag}
169
+ onDrop={handleDrop}
170
+ >
171
+ <Input
172
+ type="file"
173
+ accept="image/*"
174
+ multiple
175
+ onChange={handleInputChange}
176
+ className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
177
+ disabled={uploading}
178
+ />
179
+ <div className="space-y-2">
180
+ <FileImage className="h-10 w-10 mx-auto text-muted-foreground" />
181
+ <div>
182
+ <p className="text-sm font-medium">
183
+ {dragActive
184
+ ? "Drop files here"
185
+ : "Click to upload or drag and drop"}
186
+ </p>
187
+ <p className="text-xs text-muted-foreground">
188
+ PNG, JPG, GIF up to 5MB
189
+ </p>
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ {uploading && (
195
+ <div className="space-y-2">
196
+ <div className="flex items-center justify-between text-sm">
197
+ <span>Uploading...</span>
198
+ <span>{Math.round(uploadProgress)}%</span>
199
+ </div>
200
+ <Progress value={uploadProgress} className="h-2" />
201
+ </div>
202
+ )}
203
+ </CardContent>
204
+ </Card>
205
+
206
+ {/* Upload Info */}
207
+ <Card>
208
+ <CardHeader>
209
+ <CardTitle>About R2 Storage</CardTitle>
210
+ <CardDescription>
211
+ Cloudflare R2 provides S3-compatible object storage
212
+ </CardDescription>
213
+ </CardHeader>
214
+ <CardContent className="space-y-4">
215
+ <div className="space-y-3">
216
+ <div className="flex items-start gap-3">
217
+ <Check className="h-4 w-4 text-green-500 mt-0.5" />
218
+ <div className="text-sm">
219
+ <p className="font-medium">Global CDN</p>
220
+ <p className="text-muted-foreground">
221
+ Fast delivery worldwide
222
+ </p>
223
+ </div>
224
+ </div>
225
+ <div className="flex items-start gap-3">
226
+ <Check className="h-4 w-4 text-green-500 mt-0.5" />
227
+ <div className="text-sm">
228
+ <p className="font-medium">Zero Egress Fees</p>
229
+ <p className="text-muted-foreground">No bandwidth charges</p>
230
+ </div>
231
+ </div>
232
+ <div className="flex items-start gap-3">
233
+ <Check className="h-4 w-4 text-green-500 mt-0.5" />
234
+ <div className="text-sm">
235
+ <p className="font-medium">S3 Compatible</p>
236
+ <p className="text-muted-foreground">
237
+ Works with existing tools
238
+ </p>
239
+ </div>
240
+ </div>
241
+ <div className="flex items-start gap-3">
242
+ <Check className="h-4 w-4 text-green-500 mt-0.5" />
243
+ <div className="text-sm">
244
+ <p className="font-medium">Auto Scaling</p>
245
+ <p className="text-muted-foreground">Handles any file size</p>
246
+ </div>
247
+ </div>
248
+ </div>
249
+ </CardContent>
250
+ </Card>
251
+ </div>
252
+
253
+ {/* Uploaded Files */}
254
+ {uploadedFiles.length > 0 && (
255
+ <Card>
256
+ <CardHeader>
257
+ <CardTitle>Uploaded Files ({uploadedFiles.length})</CardTitle>
258
+ <CardDescription>
259
+ Recently uploaded images to R2 storage
260
+ </CardDescription>
261
+ </CardHeader>
262
+ <CardContent>
263
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
264
+ {uploadedFiles.map((file) => (
265
+ <div
266
+ key={file.id}
267
+ className="group relative border rounded-lg overflow-hidden hover:shadow-md transition-shadow"
268
+ >
269
+ <div className="aspect-video relative bg-muted">
270
+ <Image
271
+ src={file.url}
272
+ alt={file.name}
273
+ fill
274
+ className="object-cover"
275
+ sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
276
+ />
277
+ </div>
278
+ <div className="p-3">
279
+ <p
280
+ className="font-medium text-sm truncate"
281
+ title={file.name}
282
+ >
283
+ {file.name}
284
+ </p>
285
+ <div className="flex items-center justify-between text-xs text-muted-foreground mt-1">
286
+ <span>{formatFileSize(file.size)}</span>
287
+ <span>{file.uploadedAt.toLocaleDateString()}</span>
288
+ </div>
289
+ <div className="mt-2 flex items-center gap-2">
290
+ <Button
291
+ size="sm"
292
+ variant="outline"
293
+ onClick={() => navigator.clipboard.writeText(file.url)}
294
+ className="flex-1 text-xs"
295
+ >
296
+ Copy URL
297
+ </Button>
298
+ <Button
299
+ size="sm"
300
+ variant="outline"
301
+ onClick={() => window.open(file.url, "_blank")}
302
+ className="flex-1 text-xs"
303
+ >
304
+ Open
305
+ </Button>
306
+ </div>
307
+ </div>
308
+ <Button
309
+ size="sm"
310
+ variant="ghost"
311
+ onClick={() => removeFile(file.id)}
312
+ className="absolute top-2 right-2 h-8 w-8 p-0 opacity-0 group-hover:opacity-100 transition-opacity bg-background/80 hover:bg-background"
313
+ >
314
+ <X className="h-4 w-4" />
315
+ </Button>
316
+ </div>
317
+ ))}
318
+ </div>
319
+ </CardContent>
320
+ </Card>
321
+ )}
322
+ </div>
323
+ );
324
+ }
@@ -0,0 +1,78 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { AlertCircle, Home, RefreshCcw } from "lucide-react";
6
+ import Link from "next/link";
7
+
8
+ export default function Error({
9
+ error,
10
+ reset,
11
+ }: {
12
+ error: Error & { digest?: string };
13
+ reset: () => void;
14
+ }) {
15
+ useEffect(() => {
16
+ // Log the error to an error reporting service
17
+ console.error("Application error:", error);
18
+ }, [error]);
19
+
20
+ return (
21
+ <div className="flex min-h-screen flex-col items-center justify-center px-6">
22
+ <div className="text-center space-y-6 max-w-md">
23
+ <div className="flex justify-center">
24
+ <div className="rounded-full bg-destructive/10 p-4">
25
+ <AlertCircle className="h-12 w-12 text-destructive" />
26
+ </div>
27
+ </div>
28
+
29
+ <div className="space-y-2">
30
+ <h1 className="text-4xl font-bold">Something Went Wrong</h1>
31
+ <p className="text-muted-foreground text-lg">
32
+ We&apos;re sorry, but something unexpected happened. Our team has been notified.
33
+ </p>
34
+ </div>
35
+
36
+ {process.env.NODE_ENV === "development" && (
37
+ <div className="bg-muted/50 rounded-lg p-4 text-left">
38
+ <p className="text-sm font-mono text-destructive break-all">
39
+ {error.message}
40
+ </p>
41
+ {error.digest && (
42
+ <p className="text-xs text-muted-foreground mt-2">
43
+ Error ID: {error.digest}
44
+ </p>
45
+ )}
46
+ </div>
47
+ )}
48
+
49
+ <div className="flex flex-col sm:flex-row gap-3 justify-center items-center pt-4">
50
+ <Button onClick={reset} variant="default">
51
+ <RefreshCcw className="mr-2 h-4 w-4" />
52
+ Try Again
53
+ </Button>
54
+ <Button asChild variant="outline">
55
+ <Link href="/">
56
+ <Home className="mr-2 h-4 w-4" />
57
+ Go Home
58
+ </Link>
59
+ </Button>
60
+ </div>
61
+
62
+ <div className="pt-8 text-sm text-muted-foreground">
63
+ <p>
64
+ If this problem persists,{" "}
65
+ <Link
66
+ href="https://github.com/kedbrant/Saas-scaffold/issues"
67
+ className="text-primary hover:underline"
68
+ target="_blank"
69
+ rel="noopener noreferrer"
70
+ >
71
+ please report it
72
+ </Link>
73
+ </p>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ );
78
+ }
Binary file
@@ -0,0 +1,126 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ @theme inline {
7
+ --color-background: var(--background);
8
+ --color-foreground: var(--foreground);
9
+ --font-sans: var(--font-apple-system);
10
+ --font-mono: var(--font-sf-mono);
11
+ --color-sidebar-ring: var(--sidebar-ring);
12
+ --color-sidebar-border: var(--sidebar-border);
13
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
14
+ --color-sidebar-accent: var(--sidebar-accent);
15
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
16
+ --color-sidebar-primary: var(--sidebar-primary);
17
+ --color-sidebar-foreground: var(--sidebar-foreground);
18
+ --color-sidebar: var(--sidebar);
19
+ --color-chart-5: var(--chart-5);
20
+ --color-chart-4: var(--chart-4);
21
+ --color-chart-3: var(--chart-3);
22
+ --color-chart-2: var(--chart-2);
23
+ --color-chart-1: var(--chart-1);
24
+ --color-ring: var(--ring);
25
+ --color-input: var(--input);
26
+ --color-border: var(--border);
27
+ --color-destructive: var(--destructive);
28
+ --color-accent-foreground: var(--accent-foreground);
29
+ --color-accent: var(--accent);
30
+ --color-muted-foreground: var(--muted-foreground);
31
+ --color-muted: var(--muted);
32
+ --color-secondary-foreground: var(--secondary-foreground);
33
+ --color-secondary: var(--secondary);
34
+ --color-primary-foreground: var(--primary-foreground);
35
+ --color-primary: var(--primary);
36
+ --color-popover-foreground: var(--popover-foreground);
37
+ --color-popover: var(--popover);
38
+ --color-card-foreground: var(--card-foreground);
39
+ --color-card: var(--card);
40
+ --radius-sm: 8px;
41
+ --radius-md: 10px;
42
+ --radius-lg: 12px;
43
+ --radius-xl: 20px;
44
+ }
45
+
46
+ :root {
47
+ /* Dark Theme with Sunset Orange */
48
+ --background: #000000; /* Pure black */
49
+ --foreground: #ffffff;
50
+ --card: #0a0a0a; /* Slightly lighter than black */
51
+ --card-foreground: #ffffff;
52
+ --popover: #0a0a0a;
53
+ --popover-foreground: #ffffff;
54
+ --primary: #ff5722; /* Deep sunset orange */
55
+ --primary-foreground: #ffffff;
56
+ --secondary: #1a1a1a; /* Dark gray */
57
+ --secondary-foreground: #ffffff;
58
+ --muted: #1a1a1a;
59
+ --muted-foreground: #a1a1a1;
60
+ --accent: #ff5722; /* Sunset orange */
61
+ --accent-foreground: #ffffff;
62
+ --destructive: #ff3b30;
63
+ --destructive-foreground: #ffffff;
64
+ --border: #2a2a2a; /* Subtle dark border */
65
+ --input: #1a1a1a;
66
+ --ring: #ff5722;
67
+ --chart-1: #ff5722; /* Sunset orange */
68
+ --chart-2: #ff7043; /* Lighter orange */
69
+ --chart-3: #ff8a65; /* Even lighter orange */
70
+ --chart-4: #ffab91; /* Peachy orange */
71
+ --chart-5: #d84315; /* Deep burnt orange */
72
+ --sidebar: #0a0a0a;
73
+ --sidebar-foreground: #ffffff;
74
+ --sidebar-primary: #ff5722;
75
+ --sidebar-primary-foreground: #ffffff;
76
+ --sidebar-accent: #1a1a1a;
77
+ --sidebar-accent-foreground: #ffffff;
78
+ --sidebar-border: #2a2a2a;
79
+ --sidebar-ring: #ff5722;
80
+ /* Fonts */
81
+ --font-apple-system:
82
+ -apple-system, BlinkMacSystemFont, "San Francisco", "Helvetica Neue",
83
+ Helvetica, sans-serif;
84
+ --font-sf-mono: "SF Mono", Menlo, monospace;
85
+ --font-sans: var(--font-apple-system);
86
+ --font-serif: "New York", Georgia, serif;
87
+ --font-mono: var(--font-sf-mono);
88
+ --radius: 10px;
89
+ /* Dark theme shadows with orange glow */
90
+ --shadow-2xs: 0px 1px 2px rgba(255, 87, 34, 0.05);
91
+ --shadow-xs: 0px 1px 3px rgba(255, 87, 34, 0.1);
92
+ --shadow-sm: 0px 2px 4px rgba(255, 87, 34, 0.1);
93
+ --shadow: 0px 2px 6px rgba(255, 87, 34, 0.1);
94
+ --shadow-md: 0px 4px 8px rgba(255, 87, 34, 0.15);
95
+ --shadow-lg: 0px 8px 16px rgba(255, 87, 34, 0.2);
96
+ --shadow-xl: 0px 12px 24px rgba(255, 87, 34, 0.25);
97
+ --shadow-2xl: 0px 16px 32px rgba(255, 87, 34, 0.3);
98
+ }
99
+
100
+ @layer base {
101
+ * {
102
+ @apply border-border outline-ring/50;
103
+ }
104
+ body {
105
+ @apply bg-background text-foreground font-sans;
106
+ -webkit-font-smoothing: antialiased;
107
+ -moz-osx-font-smoothing: grayscale;
108
+ }
109
+ button,
110
+ input,
111
+ select,
112
+ textarea {
113
+ @apply focus:outline-none focus:ring-2 focus:ring-primary/50 transition-all duration-200;
114
+ }
115
+ button {
116
+ @apply hover:cursor-pointer;
117
+ }
118
+ h1,
119
+ h2,
120
+ h3,
121
+ h4,
122
+ h5,
123
+ h6 {
124
+ @apply font-medium tracking-tight;
125
+ }
126
+ }
@@ -0,0 +1,135 @@
1
+ import { Toaster } from "@/components/ui/sonner";
2
+ import type { Metadata } from "next";
3
+ import { ThemeProvider } from "../components/provider";
4
+ import "./globals.css";
5
+ import { Analytics } from "@vercel/analytics/next";
6
+
7
+ const siteConfig = {
8
+ name: "{{PROJECT_NAME}}",
9
+ description: "{{PROJECT_DESCRIPTION}}",
10
+ url: "{{APP_URL}}",
11
+ ogImage: "{{APP_URL}}/og-image.png",
12
+ links: {
13
+ twitter: "https://twitter.com/yourhandle",
14
+ github: "https://github.com/yourusername/{{PROJECT_NAME_KEBAB}}",
15
+ },
16
+ };
17
+
18
+ export const metadata: Metadata = {
19
+ metadataBase: new URL(siteConfig.url),
20
+ title: {
21
+ default: siteConfig.name,
22
+ template: `%s | ${siteConfig.name}`,
23
+ },
24
+ description: siteConfig.description,
25
+ keywords: [
26
+ "SaaS",
27
+ "Next.js",
28
+ "React",
29
+ "TypeScript",
30
+ "Tailwind CSS",
31
+ "shadcn/ui",
32
+ "Authentication",
33
+ "Payments",
34
+ ],
35
+ authors: [
36
+ {
37
+ name: siteConfig.name,
38
+ url: siteConfig.url,
39
+ },
40
+ ],
41
+ creator: siteConfig.name,
42
+ openGraph: {
43
+ type: "website",
44
+ locale: "en_US",
45
+ url: siteConfig.url,
46
+ title: siteConfig.name,
47
+ description: siteConfig.description,
48
+ siteName: siteConfig.name,
49
+ images: [
50
+ {
51
+ url: siteConfig.ogImage,
52
+ width: 1200,
53
+ height: 630,
54
+ alt: siteConfig.name,
55
+ },
56
+ ],
57
+ },
58
+ twitter: {
59
+ card: "summary_large_image",
60
+ title: siteConfig.name,
61
+ description: siteConfig.description,
62
+ images: [siteConfig.ogImage],
63
+ creator: "@yourhandle",
64
+ },
65
+ icons: {
66
+ icon: "/favicon.svg",
67
+ shortcut: "/favicon.svg",
68
+ apple: "/favicon.svg",
69
+ },
70
+ manifest: "/site.webmanifest",
71
+ robots: {
72
+ index: true,
73
+ follow: true,
74
+ googleBot: {
75
+ index: true,
76
+ follow: true,
77
+ "max-video-preview": -1,
78
+ "max-image-preview": "large",
79
+ "max-snippet": -1,
80
+ },
81
+ },
82
+ verification: {
83
+ google: "your-google-verification-code",
84
+ // yandex: "your-yandex-verification-code",
85
+ // yahoo: "your-yahoo-verification-code",
86
+ },
87
+ };
88
+
89
+ export default function RootLayout({
90
+ children,
91
+ }: Readonly<{
92
+ children: React.ReactNode;
93
+ }>) {
94
+ const jsonLd = {
95
+ "@context": "https://schema.org",
96
+ "@type": "WebApplication",
97
+ name: siteConfig.name,
98
+ description: siteConfig.description,
99
+ url: siteConfig.url,
100
+ applicationCategory: "BusinessApplication",
101
+ offers: {
102
+ "@type": "Offer",
103
+ category: "SaaS",
104
+ },
105
+ creator: {
106
+ "@type": "Organization",
107
+ name: siteConfig.name,
108
+ url: siteConfig.url,
109
+ },
110
+ };
111
+
112
+ return (
113
+ <html lang="en" suppressHydrationWarning>
114
+ <head>
115
+ <script
116
+ type="application/ld+json"
117
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
118
+ />
119
+ </head>
120
+ <body className={`font-[-apple-system,BlinkMacSystemFont]antialiased`}>
121
+ <ThemeProvider
122
+ attribute="class"
123
+ defaultTheme="light"
124
+ enableSystem
125
+ forcedTheme="light"
126
+ disableTransitionOnChange
127
+ >
128
+ {children}
129
+ <Toaster />
130
+ <Analytics />
131
+ </ThemeProvider>
132
+ </body>
133
+ </html>
134
+ );
135
+ }