shipd 0.1.3 → 0.2.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 (116) hide show
  1. package/base-package/app/globals.css +126 -0
  2. package/base-package/app/layout.tsx +53 -0
  3. package/base-package/app/page.tsx +15 -0
  4. package/base-package/base.config.json +57 -0
  5. package/base-package/components/ui/avatar.tsx +53 -0
  6. package/base-package/components/ui/badge.tsx +46 -0
  7. package/base-package/components/ui/button.tsx +59 -0
  8. package/base-package/components/ui/card.tsx +92 -0
  9. package/base-package/components/ui/chart.tsx +353 -0
  10. package/base-package/components/ui/checkbox.tsx +32 -0
  11. package/base-package/components/ui/dialog.tsx +135 -0
  12. package/base-package/components/ui/dropdown-menu.tsx +257 -0
  13. package/base-package/components/ui/form.tsx +167 -0
  14. package/base-package/components/ui/input.tsx +21 -0
  15. package/base-package/components/ui/label.tsx +24 -0
  16. package/base-package/components/ui/progress.tsx +31 -0
  17. package/base-package/components/ui/resizable.tsx +56 -0
  18. package/base-package/components/ui/select.tsx +185 -0
  19. package/base-package/components/ui/separator.tsx +28 -0
  20. package/base-package/components/ui/sheet.tsx +139 -0
  21. package/base-package/components/ui/skeleton.tsx +13 -0
  22. package/base-package/components/ui/sonner.tsx +25 -0
  23. package/base-package/components/ui/switch.tsx +31 -0
  24. package/base-package/components/ui/tabs.tsx +66 -0
  25. package/base-package/components/ui/textarea.tsx +18 -0
  26. package/base-package/components/ui/toggle-group.tsx +73 -0
  27. package/base-package/components/ui/toggle.tsx +47 -0
  28. package/base-package/components/ui/tooltip.tsx +61 -0
  29. package/base-package/components.json +21 -0
  30. package/base-package/eslint.config.mjs +16 -0
  31. package/base-package/lib/utils.ts +6 -0
  32. package/base-package/middleware.ts +12 -0
  33. package/base-package/next.config.ts +27 -0
  34. package/base-package/package.json +49 -0
  35. package/base-package/postcss.config.mjs +5 -0
  36. package/base-package/public/favicon.svg +4 -0
  37. package/base-package/tailwind.config.ts +89 -0
  38. package/base-package/tsconfig.json +27 -0
  39. package/dist/index.js +1858 -956
  40. package/docs-template/README.md +74 -0
  41. package/features/ai-chat/README.md +316 -0
  42. package/features/ai-chat/app/api/chat/route.ts +16 -0
  43. package/features/ai-chat/app/dashboard/_components/chatbot.tsx +39 -0
  44. package/features/ai-chat/app/dashboard/chat/page.tsx +73 -0
  45. package/features/ai-chat/feature.config.json +22 -0
  46. package/features/analytics/README.md +364 -0
  47. package/features/analytics/feature.config.json +20 -0
  48. package/features/analytics/lib/posthog.ts +36 -0
  49. package/features/auth/README.md +409 -0
  50. package/features/auth/app/api/auth/[...all]/route.ts +4 -0
  51. package/features/auth/app/dashboard/layout.tsx +15 -0
  52. package/features/auth/app/dashboard/page.tsx +140 -0
  53. package/features/auth/app/sign-in/page.tsx +228 -0
  54. package/features/auth/app/sign-up/page.tsx +243 -0
  55. package/features/auth/auth-schema.ts +47 -0
  56. package/features/auth/components/auth/setup-instructions.tsx +123 -0
  57. package/features/auth/feature.config.json +33 -0
  58. package/features/auth/lib/auth-client.ts +8 -0
  59. package/features/auth/lib/auth.ts +295 -0
  60. package/features/auth/lib/email-stub.ts +55 -0
  61. package/features/auth/lib/email.ts +47 -0
  62. package/features/auth/middleware.patch.ts +43 -0
  63. package/features/database/README.md +312 -0
  64. package/features/database/db/drizzle.ts +48 -0
  65. package/features/database/db/schema.ts +21 -0
  66. package/features/database/drizzle.config.ts +13 -0
  67. package/features/database/feature.config.json +30 -0
  68. package/features/email/README.md +341 -0
  69. package/features/email/emails/components/layout.tsx +181 -0
  70. package/features/email/emails/password-reset.tsx +67 -0
  71. package/features/email/emails/payment-failed.tsx +167 -0
  72. package/features/email/emails/subscription-confirmation.tsx +129 -0
  73. package/features/email/emails/welcome.tsx +100 -0
  74. package/features/email/feature.config.json +22 -0
  75. package/features/email/lib/email.ts +118 -0
  76. package/features/file-upload/README.md +329 -0
  77. package/features/file-upload/app/api/upload-image/route.ts +64 -0
  78. package/features/file-upload/app/dashboard/upload/page.tsx +324 -0
  79. package/features/file-upload/feature.config.json +23 -0
  80. package/features/file-upload/lib/upload-image.ts +28 -0
  81. package/features/marketing-landing/README.md +333 -0
  82. package/features/marketing-landing/app/page.tsx +25 -0
  83. package/features/marketing-landing/components/homepage/cli-workflow-section.tsx +231 -0
  84. package/features/marketing-landing/components/homepage/features-section.tsx +152 -0
  85. package/features/marketing-landing/components/homepage/footer.tsx +53 -0
  86. package/features/marketing-landing/components/homepage/hero-section.tsx +112 -0
  87. package/features/marketing-landing/components/homepage/integrations.tsx +124 -0
  88. package/features/marketing-landing/components/homepage/navigation.tsx +116 -0
  89. package/features/marketing-landing/components/homepage/news-section.tsx +82 -0
  90. package/features/marketing-landing/components/homepage/pricing-section.tsx +98 -0
  91. package/features/marketing-landing/components/homepage/testimonials-section.tsx +34 -0
  92. package/features/marketing-landing/components/logos/BetterAuth.tsx +21 -0
  93. package/features/marketing-landing/components/logos/NeonPostgres.tsx +41 -0
  94. package/features/marketing-landing/components/logos/Nextjs.tsx +72 -0
  95. package/features/marketing-landing/components/logos/Polar.tsx +7 -0
  96. package/features/marketing-landing/components/logos/TailwindCSS.tsx +27 -0
  97. package/features/marketing-landing/components/logos/index.ts +6 -0
  98. package/features/marketing-landing/components/logos/shadcnui.tsx +8 -0
  99. package/features/marketing-landing/feature.config.json +23 -0
  100. package/features/payments/README.md +375 -0
  101. package/features/payments/app/api/subscription/route.ts +25 -0
  102. package/features/payments/app/dashboard/payment/_components/manage-subscription.tsx +22 -0
  103. package/features/payments/app/dashboard/payment/page.tsx +126 -0
  104. package/features/payments/app/success/page.tsx +123 -0
  105. package/features/payments/feature.config.json +31 -0
  106. package/features/payments/lib/polar-products.ts +49 -0
  107. package/features/payments/lib/subscription.ts +148 -0
  108. package/features/payments/payments-schema.ts +30 -0
  109. package/features/seo/README.md +302 -0
  110. package/features/seo/app/blog/[slug]/page.tsx +314 -0
  111. package/features/seo/app/blog/page.tsx +107 -0
  112. package/features/seo/app/robots.txt +13 -0
  113. package/features/seo/app/sitemap.ts +70 -0
  114. package/features/seo/feature.config.json +19 -0
  115. package/features/seo/lib/seo-utils.ts +163 -0
  116. package/package.json +3 -1
@@ -0,0 +1,228 @@
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 { Label } from "@/components/ui/label";
13
+ import { authClient } from "@/lib/auth-client";
14
+ import { cn } from "@/lib/utils";
15
+ import Link from "next/link";
16
+ import { useRouter, useSearchParams } from "next/navigation";
17
+ import { Suspense, useState } from "react";
18
+ import { toast } from "sonner";
19
+
20
+ function SignInContent() {
21
+ const [loading, setLoading] = useState(false);
22
+ const [email, setEmail] = useState("");
23
+ const [password, setPassword] = useState("");
24
+ const searchParams = useSearchParams();
25
+ const returnTo = searchParams.get("returnTo");
26
+ const router = useRouter();
27
+
28
+ const handleEmailSignIn = async (e: React.FormEvent) => {
29
+ e.preventDefault();
30
+ setLoading(true);
31
+
32
+ try {
33
+ const { error } = await authClient.signIn.email({
34
+ email,
35
+ password,
36
+ callbackURL: returnTo || "/dashboard",
37
+ });
38
+
39
+ if (error) {
40
+ console.error("Sign-in error:", error);
41
+
42
+ // Provide helpful error messages
43
+ if (error.message?.includes("database") || error.message?.includes("connect")) {
44
+ toast.error("Database connection failed. Please configure DATABASE_URL in your .env.local file.", {
45
+ duration: 7000,
46
+ });
47
+ } else if (error.message?.includes("Invalid") || error.message?.includes("credentials")) {
48
+ toast.error("Invalid email or password. Please try again.");
49
+ } else {
50
+ toast.error(error.message || "Sign-in failed. Check your environment configuration.", {
51
+ duration: 5000,
52
+ });
53
+ }
54
+ } else {
55
+ toast.success("Signed in successfully!");
56
+ router.push(returnTo || "/dashboard");
57
+ }
58
+ } catch (err) {
59
+ console.error("Authentication error:", err);
60
+ toast.error("An unexpected error occurred. Check console for details.", {
61
+ duration: 5000,
62
+ });
63
+ } finally {
64
+ setLoading(false);
65
+ }
66
+ };
67
+
68
+ return (
69
+ <div className="flex flex-col justify-center items-center w-full h-screen bg-black">
70
+ <Card className="max-w-md w-full bg-[#0a0a0a] border-[#2a2a2a]">
71
+ <CardHeader>
72
+ <CardTitle className="text-lg md:text-xl text-white">
73
+ Welcome Back
74
+ </CardTitle>
75
+ <CardDescription className="text-xs md:text-sm text-gray-400">
76
+ Sign in to your account to continue
77
+ </CardDescription>
78
+ </CardHeader>
79
+ <CardContent>
80
+ <form onSubmit={handleEmailSignIn} className="grid gap-4">
81
+ <div className="grid gap-2">
82
+ <Label htmlFor="email" className="text-gray-300">Email</Label>
83
+ <Input
84
+ id="email"
85
+ type="email"
86
+ placeholder="you@example.com"
87
+ value={email}
88
+ onChange={(e) => setEmail(e.target.value)}
89
+ required
90
+ disabled={loading}
91
+ className="bg-[#1a1a1a] border-[#2a2a2a] text-white placeholder:text-gray-500"
92
+ />
93
+ </div>
94
+ <div className="grid gap-2">
95
+ <Label htmlFor="password" className="text-gray-300">Password</Label>
96
+ <Input
97
+ id="password"
98
+ type="password"
99
+ placeholder="Enter your password"
100
+ value={password}
101
+ onChange={(e) => setPassword(e.target.value)}
102
+ required
103
+ disabled={loading}
104
+ className="bg-[#1a1a1a] border-[#2a2a2a] text-white placeholder:text-gray-500"
105
+ />
106
+ </div>
107
+ <Button type="submit" className="w-full" disabled={loading}>
108
+ {loading ? "Signing in..." : "Sign In"}
109
+ </Button>
110
+ </form>
111
+
112
+ <div className="relative my-4">
113
+ <div className="absolute inset-0 flex items-center">
114
+ <span className="w-full border-t border-[#2a2a2a]" />
115
+ </div>
116
+ <div className="relative flex justify-center text-xs uppercase">
117
+ <span className="bg-[#0a0a0a] px-2 text-gray-500">Or continue with</span>
118
+ </div>
119
+ </div>
120
+
121
+ <Button
122
+ variant="outline"
123
+ className={cn("w-full gap-2 bg-[#1a1a1a] border-[#2a2a2a] text-white hover:bg-[#2a2a2a]")}
124
+ disabled={loading}
125
+ onClick={async () => {
126
+ try {
127
+ setLoading(true);
128
+ await authClient.signIn.social(
129
+ {
130
+ provider: "google",
131
+ callbackURL: returnTo || "/dashboard",
132
+ },
133
+ {
134
+ onResponse: () => {
135
+ setLoading(false);
136
+ },
137
+ onError: (ctx) => {
138
+ setLoading(false);
139
+ console.error("Sign-in failed:", ctx.error);
140
+
141
+ if (ctx.error?.message?.includes("database") || ctx.error?.message?.includes("connect")) {
142
+ toast.error("Database connection failed. Please configure DATABASE_URL in your .env.local file.", {
143
+ duration: 7000,
144
+ });
145
+ } else {
146
+ toast.error("Google sign-in failed. Check your Google OAuth configuration.", {
147
+ duration: 5000,
148
+ });
149
+ }
150
+ },
151
+ },
152
+ );
153
+ } catch (error) {
154
+ setLoading(false);
155
+ console.error("Authentication error:", error);
156
+ toast.error("Authentication error. Check console for details.", {
157
+ duration: 5000,
158
+ });
159
+ }
160
+ }}
161
+ >
162
+ <svg
163
+ xmlns="http://www.w3.org/2000/svg"
164
+ width="0.98em"
165
+ height="1em"
166
+ viewBox="0 0 256 262"
167
+ >
168
+ <path
169
+ fill="#4285F4"
170
+ d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
171
+ ></path>
172
+ <path
173
+ fill="#34A853"
174
+ d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
175
+ ></path>
176
+ <path
177
+ fill="#FBBC05"
178
+ d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z"
179
+ ></path>
180
+ <path
181
+ fill="#EB4335"
182
+ d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
183
+ ></path>
184
+ </svg>
185
+ Continue with Google
186
+ </Button>
187
+
188
+ <p className="mt-4 text-center text-sm text-gray-500">
189
+ Don&apos;t have an account?{" "}
190
+ <Link href="/sign-up" className="text-[#ff5722] hover:underline">
191
+ Sign up
192
+ </Link>
193
+ </p>
194
+ </CardContent>
195
+ </Card>
196
+ <p className="mt-6 text-xs text-center text-gray-500 dark:text-gray-400 max-w-md">
197
+ By signing in, you agree to our{" "}
198
+ <Link
199
+ href="/terms-of-service"
200
+ className="underline hover:text-gray-700 dark:hover:text-gray-300"
201
+ >
202
+ Terms of Service
203
+ </Link>{" "}
204
+ and{" "}
205
+ <Link
206
+ href="/privacy-policy"
207
+ className="underline hover:text-gray-700 dark:hover:text-gray-300"
208
+ >
209
+ Privacy Policy
210
+ </Link>
211
+ </p>
212
+ </div>
213
+ );
214
+ }
215
+
216
+ export default function SignIn() {
217
+ return (
218
+ <Suspense
219
+ fallback={
220
+ <div className="flex flex-col justify-center items-center w-full h-screen">
221
+ <div className="max-w-md w-full bg-gray-200 dark:bg-gray-800 animate-pulse rounded-lg h-96"></div>
222
+ </div>
223
+ }
224
+ >
225
+ <SignInContent />
226
+ </Suspense>
227
+ );
228
+ }
@@ -0,0 +1,243 @@
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 { Label } from "@/components/ui/label";
13
+ import { authClient } from "@/lib/auth-client";
14
+ import { cn } from "@/lib/utils";
15
+ import Link from "next/link";
16
+ import { useRouter, useSearchParams } from "next/navigation";
17
+ import { Suspense, useState } from "react";
18
+ import { toast } from "sonner";
19
+
20
+ function SignUpContent() {
21
+ const [loading, setLoading] = useState(false);
22
+ const [name, setName] = useState("");
23
+ const [email, setEmail] = useState("");
24
+ const [password, setPassword] = useState("");
25
+ const searchParams = useSearchParams();
26
+ const returnTo = searchParams.get("returnTo");
27
+ const router = useRouter();
28
+
29
+ const handleEmailSignUp = async (e: React.FormEvent) => {
30
+ e.preventDefault();
31
+ setLoading(true);
32
+
33
+ try {
34
+ const { error } = await authClient.signUp.email({
35
+ email,
36
+ password,
37
+ name,
38
+ callbackURL: returnTo || "/dashboard",
39
+ });
40
+
41
+ if (error) {
42
+ console.error("Sign-up error:", error);
43
+
44
+ if (error.message?.includes("database") || error.message?.includes("connect")) {
45
+ toast.error("Database connection failed. Please configure DATABASE_URL in your .env.local file.", {
46
+ duration: 7000,
47
+ });
48
+ } else if (error.message?.includes("already exists") || error.message?.includes("duplicate")) {
49
+ toast.error("An account with this email already exists. Please sign in instead.");
50
+ } else {
51
+ toast.error(error.message || "Sign-up failed. Check your environment configuration.", {
52
+ duration: 5000,
53
+ });
54
+ }
55
+ } else {
56
+ toast.success("Account created successfully!");
57
+ router.push(returnTo || "/dashboard");
58
+ }
59
+ } catch (err) {
60
+ console.error("Authentication error:", err);
61
+ toast.error("An unexpected error occurred. Check console for details.", {
62
+ duration: 5000,
63
+ });
64
+ } finally {
65
+ setLoading(false);
66
+ }
67
+ };
68
+
69
+ return (
70
+ <div className="flex flex-col justify-center items-center w-full h-screen bg-black">
71
+ <Card className="max-w-md w-full bg-[#0a0a0a] border-[#2a2a2a]">
72
+ <CardHeader>
73
+ <CardTitle className="text-lg md:text-xl text-white">
74
+ Create Your Account
75
+ </CardTitle>
76
+ <CardDescription className="text-xs md:text-sm text-gray-400">
77
+ Get started in seconds
78
+ </CardDescription>
79
+ </CardHeader>
80
+ <CardContent>
81
+ <form onSubmit={handleEmailSignUp} className="grid gap-4">
82
+ <div className="grid gap-2">
83
+ <Label htmlFor="name" className="text-gray-300">Name</Label>
84
+ <Input
85
+ id="name"
86
+ type="text"
87
+ placeholder="John Doe"
88
+ value={name}
89
+ onChange={(e) => setName(e.target.value)}
90
+ required
91
+ disabled={loading}
92
+ className="bg-[#1a1a1a] border-[#2a2a2a] text-white placeholder:text-gray-500"
93
+ />
94
+ </div>
95
+ <div className="grid gap-2">
96
+ <Label htmlFor="email" className="text-gray-300">Email</Label>
97
+ <Input
98
+ id="email"
99
+ type="email"
100
+ placeholder="you@example.com"
101
+ value={email}
102
+ onChange={(e) => setEmail(e.target.value)}
103
+ required
104
+ disabled={loading}
105
+ className="bg-[#1a1a1a] border-[#2a2a2a] text-white placeholder:text-gray-500"
106
+ />
107
+ </div>
108
+ <div className="grid gap-2">
109
+ <Label htmlFor="password" className="text-gray-300">Password</Label>
110
+ <Input
111
+ id="password"
112
+ type="password"
113
+ placeholder="Create a strong password"
114
+ value={password}
115
+ onChange={(e) => setPassword(e.target.value)}
116
+ required
117
+ disabled={loading}
118
+ minLength={8}
119
+ className="bg-[#1a1a1a] border-[#2a2a2a] text-white placeholder:text-gray-500"
120
+ />
121
+ </div>
122
+ <Button type="submit" className="w-full" disabled={loading}>
123
+ {loading ? "Creating account..." : "Create Account"}
124
+ </Button>
125
+ </form>
126
+
127
+ <div className="relative my-4">
128
+ <div className="absolute inset-0 flex items-center">
129
+ <span className="w-full border-t border-[#2a2a2a]" />
130
+ </div>
131
+ <div className="relative flex justify-center text-xs uppercase">
132
+ <span className="bg-[#0a0a0a] px-2 text-gray-500">Or continue with</span>
133
+ </div>
134
+ </div>
135
+
136
+ <Button
137
+ variant="outline"
138
+ className={cn("w-full gap-2 bg-[#1a1a1a] border-[#2a2a2a] text-white hover:bg-[#2a2a2a]")}
139
+ disabled={loading}
140
+ onClick={async () => {
141
+ try {
142
+ setLoading(true);
143
+ await authClient.signIn.social(
144
+ {
145
+ provider: "google",
146
+ callbackURL: returnTo || "/dashboard",
147
+ },
148
+ {
149
+ onResponse: () => {
150
+ setLoading(false);
151
+ },
152
+ onError: (ctx) => {
153
+ setLoading(false);
154
+ console.error("Sign-up failed:", ctx.error);
155
+
156
+ if (ctx.error?.message?.includes("database") || ctx.error?.message?.includes("connect")) {
157
+ toast.error("Database connection failed. Please configure DATABASE_URL in your .env.local file.", {
158
+ duration: 7000,
159
+ });
160
+ } else {
161
+ toast.error("Google sign-up failed. Check your Google OAuth configuration.", {
162
+ duration: 5000,
163
+ });
164
+ }
165
+ },
166
+ },
167
+ );
168
+ } catch (error) {
169
+ setLoading(false);
170
+ console.error("Authentication error:", error);
171
+ toast.error("Authentication error. Check console for details.", {
172
+ duration: 5000,
173
+ });
174
+ }
175
+ }}
176
+ >
177
+ <svg
178
+ xmlns="http://www.w3.org/2000/svg"
179
+ width="0.98em"
180
+ height="1em"
181
+ viewBox="0 0 256 262"
182
+ >
183
+ <path
184
+ fill="#4285F4"
185
+ d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
186
+ ></path>
187
+ <path
188
+ fill="#34A853"
189
+ d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
190
+ ></path>
191
+ <path
192
+ fill="#FBBC05"
193
+ d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z"
194
+ ></path>
195
+ <path
196
+ fill="#EB4335"
197
+ d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
198
+ ></path>
199
+ </svg>
200
+ Continue with Google
201
+ </Button>
202
+
203
+ <p className="mt-4 text-center text-sm text-gray-500">
204
+ Already have an account?{" "}
205
+ <Link href="/sign-in" className="text-[#ff5722] hover:underline">
206
+ Sign in
207
+ </Link>
208
+ </p>
209
+ </CardContent>
210
+ </Card>
211
+ <p className="mt-6 text-xs text-center text-gray-500 max-w-md">
212
+ By signing up, you agree to our{" "}
213
+ <Link
214
+ href="/terms-of-service"
215
+ className="underline hover:text-gray-400"
216
+ >
217
+ Terms of Service
218
+ </Link>{" "}
219
+ and{" "}
220
+ <Link
221
+ href="/privacy-policy"
222
+ className="underline hover:text-gray-400"
223
+ >
224
+ Privacy Policy
225
+ </Link>
226
+ </p>
227
+ </div>
228
+ );
229
+ }
230
+
231
+ export default function SignUp() {
232
+ return (
233
+ <Suspense
234
+ fallback={
235
+ <div className="flex flex-col justify-center items-center w-full h-screen bg-black">
236
+ <div className="max-w-md w-full bg-[#0a0a0a] animate-pulse rounded-lg h-96"></div>
237
+ </div>
238
+ }
239
+ >
240
+ <SignUpContent />
241
+ </Suspense>
242
+ );
243
+ }
@@ -0,0 +1,47 @@
1
+ import { pgTable, text, timestamp, boolean, integer } from "drizzle-orm/pg-core";
2
+
3
+ export const user = pgTable("user", {
4
+ id: text('id').primaryKey(),
5
+ name: text('name').notNull(),
6
+ email: text('email').notNull().unique(),
7
+ emailVerified: boolean('email_verified').$defaultFn(() => false).notNull(),
8
+ image: text('image'),
9
+ createdAt: timestamp('created_at').$defaultFn(() => /* @__PURE__ */ new Date()).notNull(),
10
+ updatedAt: timestamp('updated_at').$defaultFn(() => /* @__PURE__ */ new Date()).notNull()
11
+ });
12
+
13
+ export const session = pgTable("session", {
14
+ id: text('id').primaryKey(),
15
+ expiresAt: timestamp('expires_at').notNull(),
16
+ token: text('token').notNull().unique(),
17
+ createdAt: timestamp('created_at').notNull(),
18
+ updatedAt: timestamp('updated_at').notNull(),
19
+ ipAddress: text('ip_address'),
20
+ userAgent: text('user_agent'),
21
+ userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' })
22
+ });
23
+
24
+ export const account = pgTable("account", {
25
+ id: text('id').primaryKey(),
26
+ accountId: text('account_id').notNull(),
27
+ providerId: text('provider_id').notNull(),
28
+ userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' }),
29
+ accessToken: text('access_token'),
30
+ refreshToken: text('refresh_token'),
31
+ idToken: text('id_token'),
32
+ accessTokenExpiresAt: timestamp('access_token_expires_at'),
33
+ refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
34
+ scope: text('scope'),
35
+ password: text('password'),
36
+ createdAt: timestamp('created_at').notNull(),
37
+ updatedAt: timestamp('updated_at').notNull()
38
+ });
39
+
40
+ export const verification = pgTable("verification", {
41
+ id: text('id').primaryKey(),
42
+ identifier: text('identifier').notNull(),
43
+ value: text('value').notNull(),
44
+ expiresAt: timestamp('expires_at').notNull(),
45
+ createdAt: timestamp('created_at').$defaultFn(() => /* @__PURE__ */ new Date()),
46
+ updatedAt: timestamp('updated_at').$defaultFn(() => /* @__PURE__ */ new Date())
47
+ });
@@ -0,0 +1,123 @@
1
+ "use client";
2
+
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from "@/components/ui/card";
10
+ import { AlertCircle, CheckCircle2 } from "lucide-react";
11
+ import { useState, useEffect } from "react";
12
+
13
+ return (
14
+ <Card className="max-w-2xl w-full bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800">
15
+ <CardHeader>
16
+ <CardTitle className="flex items-center gap-2 text-yellow-900 dark:text-yellow-100">
17
+ <AlertCircle className="w-5 h-5" />
18
+ Setup Required
19
+ </CardTitle>
20
+ <CardDescription className="text-yellow-800 dark:text-yellow-200">
21
+ Configure your environment variables to enable authentication
22
+ </CardDescription>
23
+ </CardHeader>
24
+ <CardContent>
25
+ <div className="space-y-4">
26
+ <div className="space-y-2">
27
+ <h4 className="font-semibold text-yellow-900 dark:text-yellow-100">
28
+ Required Configuration:
29
+ </h4>
30
+ <ul className="space-y-2 text-sm text-yellow-800 dark:text-yellow-200">
31
+ <li className="flex items-start gap-2">
32
+ <CheckCircle2 className="w-4 h-4 mt-0.5 flex-shrink-0" />
33
+ <div>
34
+ <strong>DATABASE_URL</strong> - PostgreSQL connection string
35
+ <br />
36
+ <span className="text-xs opacity-75">
37
+ Get one from{" "}
38
+ <a
39
+ href="https://neon.tech"
40
+ target="_blank"
41
+ rel="noopener noreferrer"
42
+ className="underline"
43
+ >
44
+ Neon
45
+ </a>{" "}
46
+ or{" "}
47
+ <a
48
+ href="https://supabase.com"
49
+ target="_blank"
50
+ rel="noopener noreferrer"
51
+ className="underline"
52
+ >
53
+ Supabase
54
+ </a>
55
+ </span>
56
+ </div>
57
+ </li>
58
+ <li className="flex items-start gap-2">
59
+ <CheckCircle2 className="w-4 h-4 mt-0.5 flex-shrink-0" />
60
+ <div>
61
+ <strong>BETTER_AUTH_SECRET</strong> - Secret key for token signing
62
+ <br />
63
+ <span className="text-xs opacity-75">
64
+ Generate with: <code className="bg-yellow-100 dark:bg-yellow-900 px-1 rounded">openssl rand -base64 32</code>
65
+ </span>
66
+ </div>
67
+ </li>
68
+ <li className="flex items-start gap-2">
69
+ <CheckCircle2 className="w-4 h-4 mt-0.5 flex-shrink-0" />
70
+ <div>
71
+ <strong>NEXT_PUBLIC_APP_URL</strong> - Your application URL
72
+ <br />
73
+ <span className="text-xs opacity-75">
74
+ Example: <code className="bg-yellow-100 dark:bg-yellow-900 px-1 rounded">http://localhost:3000</code>
75
+ </span>
76
+ </div>
77
+ </li>
78
+ </ul>
79
+ </div>
80
+
81
+ <div className="space-y-2">
82
+ <h4 className="font-semibold text-yellow-900 dark:text-yellow-100">
83
+ Optional (Recommended):
84
+ </h4>
85
+ <ul className="space-y-2 text-sm text-yellow-800 dark:text-yellow-200">
86
+ <li className="flex items-start gap-2">
87
+ <CheckCircle2 className="w-4 h-4 mt-0.5 flex-shrink-0" />
88
+ <div>
89
+ <strong>GOOGLE_CLIENT_ID</strong> & <strong>GOOGLE_CLIENT_SECRET</strong>
90
+ <br />
91
+ <span className="text-xs opacity-75">
92
+ Get from{" "}
93
+ <a
94
+ href="https://console.cloud.google.com"
95
+ target="_blank"
96
+ rel="noopener noreferrer"
97
+ className="underline"
98
+ >
99
+ Google Cloud Console
100
+ </a>
101
+ </span>
102
+ </div>
103
+ </li>
104
+ </ul>
105
+ </div>
106
+
107
+ <div className="pt-4 border-t border-yellow-200 dark:border-yellow-800">
108
+ <p className="text-sm text-yellow-800 dark:text-yellow-200">
109
+ <strong>Steps:</strong>
110
+ </p>
111
+ <ol className="list-decimal list-inside space-y-1 text-sm text-yellow-800 dark:text-yellow-200 mt-2">
112
+ <li>Create a <code className="bg-yellow-100 dark:bg-yellow-900 px-1 rounded">.env.local</code> file in your project root</li>
113
+ <li>Add the required environment variables</li>
114
+ <li>Run <code className="bg-yellow-100 dark:bg-yellow-900 px-1 rounded">npm run db:push</code> to create database tables</li>
115
+ <li>Restart your dev server</li>
116
+ </ol>
117
+ </div>
118
+ </div>
119
+ </CardContent>
120
+ </Card>
121
+ );
122
+ }
123
+
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "auth",
3
+ "version": "1.0.0",
4
+ "description": "Better Auth authentication with email/password and Google OAuth",
5
+ "dependencies": {
6
+ "better-auth": "^1.0.0",
7
+ "@polar-sh/better-auth": "^1.0.0"
8
+ },
9
+ "devDependencies": {},
10
+ "envVars": [
11
+ "BETTER_AUTH_SECRET",
12
+ "GOOGLE_CLIENT_ID",
13
+ "GOOGLE_CLIENT_SECRET",
14
+ "NEXT_PUBLIC_APP_URL"
15
+ ],
16
+ "files": [
17
+ "app/sign-in/**/*",
18
+ "app/sign-up/**/*",
19
+ "app/dashboard/**/*",
20
+ "app/api/auth/**/*",
21
+ "lib/auth.ts",
22
+ "lib/auth-client.ts",
23
+ "auth-schema.ts"
24
+ ],
25
+ "middlewarePatches": [
26
+ "middleware.patch.ts"
27
+ ],
28
+ "requires": [
29
+ "database"
30
+ ],
31
+ "conflicts": []
32
+ }
33
+
@@ -0,0 +1,8 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+ import { organizationClient } from "better-auth/client/plugins";
3
+ import { polarClient } from "@polar-sh/better-auth";
4
+
5
+ export const authClient = createAuthClient({
6
+ baseURL: process.env.NEXT_PUBLIC_APP_URL,
7
+ plugins: [organizationClient(), polarClient()],
8
+ });