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.
- package/base-package/app/globals.css +126 -0
- package/base-package/app/layout.tsx +53 -0
- package/base-package/app/page.tsx +15 -0
- package/base-package/base.config.json +57 -0
- package/base-package/components/ui/avatar.tsx +53 -0
- package/base-package/components/ui/badge.tsx +46 -0
- package/base-package/components/ui/button.tsx +59 -0
- package/base-package/components/ui/card.tsx +92 -0
- package/base-package/components/ui/chart.tsx +353 -0
- package/base-package/components/ui/checkbox.tsx +32 -0
- package/base-package/components/ui/dialog.tsx +135 -0
- package/base-package/components/ui/dropdown-menu.tsx +257 -0
- package/base-package/components/ui/form.tsx +167 -0
- package/base-package/components/ui/input.tsx +21 -0
- package/base-package/components/ui/label.tsx +24 -0
- package/base-package/components/ui/progress.tsx +31 -0
- package/base-package/components/ui/resizable.tsx +56 -0
- package/base-package/components/ui/select.tsx +185 -0
- package/base-package/components/ui/separator.tsx +28 -0
- package/base-package/components/ui/sheet.tsx +139 -0
- package/base-package/components/ui/skeleton.tsx +13 -0
- package/base-package/components/ui/sonner.tsx +25 -0
- package/base-package/components/ui/switch.tsx +31 -0
- package/base-package/components/ui/tabs.tsx +66 -0
- package/base-package/components/ui/textarea.tsx +18 -0
- package/base-package/components/ui/toggle-group.tsx +73 -0
- package/base-package/components/ui/toggle.tsx +47 -0
- package/base-package/components/ui/tooltip.tsx +61 -0
- package/base-package/components.json +21 -0
- package/base-package/eslint.config.mjs +16 -0
- package/base-package/lib/utils.ts +6 -0
- package/base-package/middleware.ts +12 -0
- package/base-package/next.config.ts +27 -0
- package/base-package/package.json +49 -0
- package/base-package/postcss.config.mjs +5 -0
- package/base-package/public/favicon.svg +4 -0
- package/base-package/tailwind.config.ts +89 -0
- package/base-package/tsconfig.json +27 -0
- package/dist/index.js +1858 -956
- package/docs-template/README.md +74 -0
- package/features/ai-chat/README.md +316 -0
- package/features/ai-chat/app/api/chat/route.ts +16 -0
- package/features/ai-chat/app/dashboard/_components/chatbot.tsx +39 -0
- package/features/ai-chat/app/dashboard/chat/page.tsx +73 -0
- package/features/ai-chat/feature.config.json +22 -0
- package/features/analytics/README.md +364 -0
- package/features/analytics/feature.config.json +20 -0
- package/features/analytics/lib/posthog.ts +36 -0
- package/features/auth/README.md +409 -0
- package/features/auth/app/api/auth/[...all]/route.ts +4 -0
- package/features/auth/app/dashboard/layout.tsx +15 -0
- package/features/auth/app/dashboard/page.tsx +140 -0
- package/features/auth/app/sign-in/page.tsx +228 -0
- package/features/auth/app/sign-up/page.tsx +243 -0
- package/features/auth/auth-schema.ts +47 -0
- package/features/auth/components/auth/setup-instructions.tsx +123 -0
- package/features/auth/feature.config.json +33 -0
- package/features/auth/lib/auth-client.ts +8 -0
- package/features/auth/lib/auth.ts +295 -0
- package/features/auth/lib/email-stub.ts +55 -0
- package/features/auth/lib/email.ts +47 -0
- package/features/auth/middleware.patch.ts +43 -0
- package/features/database/README.md +312 -0
- package/features/database/db/drizzle.ts +48 -0
- package/features/database/db/schema.ts +21 -0
- package/features/database/drizzle.config.ts +13 -0
- package/features/database/feature.config.json +30 -0
- package/features/email/README.md +341 -0
- package/features/email/emails/components/layout.tsx +181 -0
- package/features/email/emails/password-reset.tsx +67 -0
- package/features/email/emails/payment-failed.tsx +167 -0
- package/features/email/emails/subscription-confirmation.tsx +129 -0
- package/features/email/emails/welcome.tsx +100 -0
- package/features/email/feature.config.json +22 -0
- package/features/email/lib/email.ts +118 -0
- package/features/file-upload/README.md +329 -0
- package/features/file-upload/app/api/upload-image/route.ts +64 -0
- package/features/file-upload/app/dashboard/upload/page.tsx +324 -0
- package/features/file-upload/feature.config.json +23 -0
- package/features/file-upload/lib/upload-image.ts +28 -0
- package/features/marketing-landing/README.md +333 -0
- package/features/marketing-landing/app/page.tsx +25 -0
- package/features/marketing-landing/components/homepage/cli-workflow-section.tsx +231 -0
- package/features/marketing-landing/components/homepage/features-section.tsx +152 -0
- package/features/marketing-landing/components/homepage/footer.tsx +53 -0
- package/features/marketing-landing/components/homepage/hero-section.tsx +112 -0
- package/features/marketing-landing/components/homepage/integrations.tsx +124 -0
- package/features/marketing-landing/components/homepage/navigation.tsx +116 -0
- package/features/marketing-landing/components/homepage/news-section.tsx +82 -0
- package/features/marketing-landing/components/homepage/pricing-section.tsx +98 -0
- package/features/marketing-landing/components/homepage/testimonials-section.tsx +34 -0
- package/features/marketing-landing/components/logos/BetterAuth.tsx +21 -0
- package/features/marketing-landing/components/logos/NeonPostgres.tsx +41 -0
- package/features/marketing-landing/components/logos/Nextjs.tsx +72 -0
- package/features/marketing-landing/components/logos/Polar.tsx +7 -0
- package/features/marketing-landing/components/logos/TailwindCSS.tsx +27 -0
- package/features/marketing-landing/components/logos/index.ts +6 -0
- package/features/marketing-landing/components/logos/shadcnui.tsx +8 -0
- package/features/marketing-landing/feature.config.json +23 -0
- package/features/payments/README.md +375 -0
- package/features/payments/app/api/subscription/route.ts +25 -0
- package/features/payments/app/dashboard/payment/_components/manage-subscription.tsx +22 -0
- package/features/payments/app/dashboard/payment/page.tsx +126 -0
- package/features/payments/app/success/page.tsx +123 -0
- package/features/payments/feature.config.json +31 -0
- package/features/payments/lib/polar-products.ts +49 -0
- package/features/payments/lib/subscription.ts +148 -0
- package/features/payments/payments-schema.ts +30 -0
- package/features/seo/README.md +302 -0
- package/features/seo/app/blog/[slug]/page.tsx +314 -0
- package/features/seo/app/blog/page.tsx +107 -0
- package/features/seo/app/robots.txt +13 -0
- package/features/seo/app/sitemap.ts +70 -0
- package/features/seo/feature.config.json +19 -0
- package/features/seo/lib/seo-utils.ts +163 -0
- package/package.json +3 -1
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { db } from "@/db/drizzle";
|
|
2
|
+
import { account, session, user, verification } from "@/db/schema";
|
|
3
|
+
import { eq } from "drizzle-orm";
|
|
4
|
+
// Subscription is optional (only if payments module is installed)
|
|
5
|
+
// We'll check for it at runtime in the webhook handler
|
|
6
|
+
import {
|
|
7
|
+
checkout,
|
|
8
|
+
polar,
|
|
9
|
+
portal,
|
|
10
|
+
usage,
|
|
11
|
+
webhooks,
|
|
12
|
+
} from "@polar-sh/better-auth";
|
|
13
|
+
import { Polar } from "@polar-sh/sdk";
|
|
14
|
+
import { betterAuth } from "better-auth";
|
|
15
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
16
|
+
import { nextCookies } from "better-auth/next-js";
|
|
17
|
+
import {
|
|
18
|
+
sendPasswordResetEmail,
|
|
19
|
+
sendSubscriptionConfirmationEmail,
|
|
20
|
+
sendPaymentFailedEmail,
|
|
21
|
+
} from "@/lib/email";
|
|
22
|
+
|
|
23
|
+
// Utility function to safely parse dates
|
|
24
|
+
function safeParseDate(value: string | Date | null | undefined): Date | null {
|
|
25
|
+
if (!value) return null;
|
|
26
|
+
if (value instanceof Date) return value;
|
|
27
|
+
return new Date(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check if Polar is configured
|
|
31
|
+
const isPolarConfigured = !!(
|
|
32
|
+
process.env.POLAR_ACCESS_TOKEN &&
|
|
33
|
+
process.env.POLAR_WEBHOOK_SECRET &&
|
|
34
|
+
process.env.NEXT_PUBLIC_STARTER_TIER &&
|
|
35
|
+
process.env.NEXT_PUBLIC_STARTER_SLUG
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!isPolarConfigured && process.env.NODE_ENV === "development") {
|
|
39
|
+
console.warn(
|
|
40
|
+
"⚠️ Polar.sh not configured - subscription features disabled. Configure POLAR_* env vars to enable payments.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const polarClient = isPolarConfigured
|
|
45
|
+
? new Polar({
|
|
46
|
+
accessToken: process.env.POLAR_ACCESS_TOKEN,
|
|
47
|
+
})
|
|
48
|
+
: null;
|
|
49
|
+
|
|
50
|
+
export const auth = betterAuth({
|
|
51
|
+
trustedOrigins: [`${process.env.NEXT_PUBLIC_APP_URL}`],
|
|
52
|
+
allowedDevOrigins: [`${process.env.NEXT_PUBLIC_APP_URL}`],
|
|
53
|
+
cookieCache: {
|
|
54
|
+
enabled: true,
|
|
55
|
+
maxAge: 5 * 60, // Cache duration in seconds
|
|
56
|
+
},
|
|
57
|
+
database: drizzleAdapter(db, {
|
|
58
|
+
provider: "pg",
|
|
59
|
+
schema: {
|
|
60
|
+
user,
|
|
61
|
+
session,
|
|
62
|
+
account,
|
|
63
|
+
verification,
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
emailAndPassword: {
|
|
67
|
+
enabled: true,
|
|
68
|
+
sendResetPassword: async ({ user, url }) => {
|
|
69
|
+
// Send password reset email
|
|
70
|
+
await sendPasswordResetEmail(user.email, url);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
user: {
|
|
74
|
+
additionalFields: {
|
|
75
|
+
name: {
|
|
76
|
+
type: "string",
|
|
77
|
+
required: false,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
socialProviders: {
|
|
82
|
+
google: {
|
|
83
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
84
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
// TODO: Implement welcome emails via a custom sign-up API route
|
|
88
|
+
// Better Auth hooks API may have changed - using webhook approach instead
|
|
89
|
+
plugins: [
|
|
90
|
+
...(isPolarConfigured && polarClient
|
|
91
|
+
? [
|
|
92
|
+
polar({
|
|
93
|
+
client: polarClient,
|
|
94
|
+
createCustomerOnSignUp: true,
|
|
95
|
+
use: [
|
|
96
|
+
checkout({
|
|
97
|
+
products: [
|
|
98
|
+
{
|
|
99
|
+
productId: process.env.NEXT_PUBLIC_STARTER_TIER!,
|
|
100
|
+
slug: process.env.NEXT_PUBLIC_STARTER_SLUG!,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/${process.env.POLAR_SUCCESS_URL}`,
|
|
104
|
+
authenticatedUsersOnly: true,
|
|
105
|
+
}),
|
|
106
|
+
portal(),
|
|
107
|
+
usage(),
|
|
108
|
+
webhooks({
|
|
109
|
+
secret: process.env.POLAR_WEBHOOK_SECRET!,
|
|
110
|
+
onPayload: async ({ data, type }) => {
|
|
111
|
+
if (
|
|
112
|
+
type === "subscription.created" ||
|
|
113
|
+
type === "subscription.active" ||
|
|
114
|
+
type === "subscription.canceled" ||
|
|
115
|
+
type === "subscription.revoked" ||
|
|
116
|
+
type === "subscription.uncanceled" ||
|
|
117
|
+
type === "subscription.updated"
|
|
118
|
+
) {
|
|
119
|
+
console.log("🎯 Processing subscription webhook:", type);
|
|
120
|
+
console.log("📦 Payload data:", JSON.stringify(data, null, 2));
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// STEP 1: Extract user ID from customer data
|
|
124
|
+
const userId = data.customer?.externalId;
|
|
125
|
+
// STEP 2: Build subscription data
|
|
126
|
+
const subscriptionData = {
|
|
127
|
+
id: data.id,
|
|
128
|
+
createdAt: new Date(data.createdAt),
|
|
129
|
+
modifiedAt: safeParseDate(data.modifiedAt),
|
|
130
|
+
amount: data.amount,
|
|
131
|
+
currency: data.currency,
|
|
132
|
+
recurringInterval: data.recurringInterval,
|
|
133
|
+
status: data.status,
|
|
134
|
+
currentPeriodStart:
|
|
135
|
+
safeParseDate(data.currentPeriodStart) || new Date(),
|
|
136
|
+
currentPeriodEnd:
|
|
137
|
+
safeParseDate(data.currentPeriodEnd) || new Date(),
|
|
138
|
+
cancelAtPeriodEnd: data.cancelAtPeriodEnd || false,
|
|
139
|
+
canceledAt: safeParseDate(data.canceledAt),
|
|
140
|
+
startedAt: safeParseDate(data.startedAt) || new Date(),
|
|
141
|
+
endsAt: safeParseDate(data.endsAt),
|
|
142
|
+
endedAt: safeParseDate(data.endedAt),
|
|
143
|
+
customerId: data.customerId,
|
|
144
|
+
productId: data.productId,
|
|
145
|
+
discountId: data.discountId || null,
|
|
146
|
+
checkoutId: data.checkoutId || "",
|
|
147
|
+
customerCancellationReason:
|
|
148
|
+
data.customerCancellationReason || null,
|
|
149
|
+
customerCancellationComment:
|
|
150
|
+
data.customerCancellationComment || null,
|
|
151
|
+
metadata: data.metadata
|
|
152
|
+
? JSON.stringify(data.metadata)
|
|
153
|
+
: null,
|
|
154
|
+
customFieldData: data.customFieldData
|
|
155
|
+
? JSON.stringify(data.customFieldData)
|
|
156
|
+
: null,
|
|
157
|
+
userId: userId as string | null,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
console.log("💾 Final subscription data:", {
|
|
161
|
+
id: subscriptionData.id,
|
|
162
|
+
status: subscriptionData.status,
|
|
163
|
+
userId: subscriptionData.userId,
|
|
164
|
+
amount: subscriptionData.amount,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// STEP 3: Use Drizzle's onConflictDoUpdate for proper upsert
|
|
168
|
+
// Only if subscription table exists (payments module installed)
|
|
169
|
+
// Dynamically import subscription table
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
|
+
let subscriptionTable: any;
|
|
172
|
+
try {
|
|
173
|
+
const schemaModule = await import("@/db/schema");
|
|
174
|
+
if ('subscription' in schemaModule && schemaModule.subscription) {
|
|
175
|
+
subscriptionTable = schemaModule.subscription;
|
|
176
|
+
} else {
|
|
177
|
+
console.warn("⚠️ Subscription table not found. Install payments module to enable subscription webhooks.");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
console.warn("⚠️ Subscription table not found. Install payments module to enable subscription webhooks.");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
await db
|
|
186
|
+
.insert(subscriptionTable)
|
|
187
|
+
.values(subscriptionData)
|
|
188
|
+
.onConflictDoUpdate({
|
|
189
|
+
target: subscriptionTable.id,
|
|
190
|
+
set: {
|
|
191
|
+
modifiedAt: subscriptionData.modifiedAt || new Date(),
|
|
192
|
+
amount: subscriptionData.amount,
|
|
193
|
+
currency: subscriptionData.currency,
|
|
194
|
+
recurringInterval: subscriptionData.recurringInterval,
|
|
195
|
+
status: subscriptionData.status,
|
|
196
|
+
currentPeriodStart: subscriptionData.currentPeriodStart,
|
|
197
|
+
currentPeriodEnd: subscriptionData.currentPeriodEnd,
|
|
198
|
+
cancelAtPeriodEnd: subscriptionData.cancelAtPeriodEnd,
|
|
199
|
+
canceledAt: subscriptionData.canceledAt,
|
|
200
|
+
startedAt: subscriptionData.startedAt,
|
|
201
|
+
endsAt: subscriptionData.endsAt,
|
|
202
|
+
endedAt: subscriptionData.endedAt,
|
|
203
|
+
customerId: subscriptionData.customerId,
|
|
204
|
+
productId: subscriptionData.productId,
|
|
205
|
+
discountId: subscriptionData.discountId,
|
|
206
|
+
checkoutId: subscriptionData.checkoutId,
|
|
207
|
+
customerCancellationReason:
|
|
208
|
+
subscriptionData.customerCancellationReason,
|
|
209
|
+
customerCancellationComment:
|
|
210
|
+
subscriptionData.customerCancellationComment,
|
|
211
|
+
metadata: subscriptionData.metadata,
|
|
212
|
+
customFieldData: subscriptionData.customFieldData,
|
|
213
|
+
userId: subscriptionData.userId,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
console.log("✅ Upserted subscription:", data.id);
|
|
218
|
+
|
|
219
|
+
// Send subscription emails based on event type
|
|
220
|
+
try {
|
|
221
|
+
// Get user email from database
|
|
222
|
+
if (userId) {
|
|
223
|
+
const userRecord = await db
|
|
224
|
+
.select()
|
|
225
|
+
.from(user)
|
|
226
|
+
.where(eq(user.id, userId))
|
|
227
|
+
.limit(1);
|
|
228
|
+
|
|
229
|
+
if (userRecord[0]?.email) {
|
|
230
|
+
const userEmail = userRecord[0].email;
|
|
231
|
+
|
|
232
|
+
// Send confirmation email for new or reactivated subscriptions
|
|
233
|
+
if (
|
|
234
|
+
type === "subscription.created" ||
|
|
235
|
+
type === "subscription.active"
|
|
236
|
+
) {
|
|
237
|
+
const amount = `$${(data.amount / 100).toFixed(2)}/${data.recurringInterval}`;
|
|
238
|
+
await sendSubscriptionConfirmationEmail(
|
|
239
|
+
userEmail,
|
|
240
|
+
"Premium", // TODO: Get actual plan name from product
|
|
241
|
+
amount
|
|
242
|
+
);
|
|
243
|
+
console.log("📧 Sent subscription confirmation email to:", userEmail);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch (emailError) {
|
|
248
|
+
console.error("📧 Failed to send subscription email:", emailError);
|
|
249
|
+
// Don't throw - email failures shouldn't block webhook processing
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error(
|
|
253
|
+
"💥 Error processing subscription webhook:",
|
|
254
|
+
error,
|
|
255
|
+
);
|
|
256
|
+
// Don't throw - let webhook succeed to avoid retries
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Handle payment failed events
|
|
261
|
+
if (type === "subscription.payment_failed") {
|
|
262
|
+
try {
|
|
263
|
+
const userId = data.customer?.externalId;
|
|
264
|
+
if (userId) {
|
|
265
|
+
const userRecord = await db
|
|
266
|
+
.select()
|
|
267
|
+
.from(user)
|
|
268
|
+
.where(eq(user.id, userId))
|
|
269
|
+
.limit(1);
|
|
270
|
+
|
|
271
|
+
if (userRecord[0]?.email) {
|
|
272
|
+
const retryDate = new Date();
|
|
273
|
+
retryDate.setDate(retryDate.getDate() + 3); // 3 days from now
|
|
274
|
+
|
|
275
|
+
await sendPaymentFailedEmail(
|
|
276
|
+
userRecord[0].email,
|
|
277
|
+
"Premium", // TODO: Get actual plan name
|
|
278
|
+
retryDate.toLocaleDateString()
|
|
279
|
+
);
|
|
280
|
+
console.log("📧 Sent payment failed email to:", userRecord[0].email);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error("📧 Failed to send payment failed email:", error);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
}),
|
|
289
|
+
],
|
|
290
|
+
}),
|
|
291
|
+
]
|
|
292
|
+
: []),
|
|
293
|
+
nextCookies(),
|
|
294
|
+
],
|
|
295
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email stub for auth module
|
|
3
|
+
*
|
|
4
|
+
* This provides minimal email functionality when the full email module is not installed.
|
|
5
|
+
* Email sending will be logged to console in development.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface EmailOptions {
|
|
9
|
+
to: string;
|
|
10
|
+
subject: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
user?: { email: string; name?: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Send password reset email (stub)
|
|
17
|
+
*/
|
|
18
|
+
export async function sendPasswordResetEmail(
|
|
19
|
+
email: string,
|
|
20
|
+
url: string
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
if (process.env.NODE_ENV === 'development') {
|
|
23
|
+
console.log(`📧 [Email Stub] Password reset email would be sent to ${email}`);
|
|
24
|
+
console.log(` Reset URL: ${url}`);
|
|
25
|
+
console.log(` Install the email module for actual email sending.`);
|
|
26
|
+
}
|
|
27
|
+
// In production, this would fail silently or you should install the email module
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Send subscription confirmation email (stub)
|
|
32
|
+
*/
|
|
33
|
+
export async function sendSubscriptionConfirmationEmail(
|
|
34
|
+
email: string,
|
|
35
|
+
subscriptionDetails: any
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
if (process.env.NODE_ENV === 'development') {
|
|
38
|
+
console.log(`📧 [Email Stub] Subscription confirmation email would be sent to ${email}`);
|
|
39
|
+
console.log(` Install the email module for actual email sending.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Send payment failed email (stub)
|
|
45
|
+
*/
|
|
46
|
+
export async function sendPaymentFailedEmail(
|
|
47
|
+
email: string,
|
|
48
|
+
errorDetails: any
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
if (process.env.NODE_ENV === 'development') {
|
|
51
|
+
console.log(`📧 [Email Stub] Payment failed email would be sent to ${email}`);
|
|
52
|
+
console.log(` Install the email module for actual email sending.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email functions for auth module
|
|
3
|
+
*
|
|
4
|
+
* This is a minimal email implementation that logs to console.
|
|
5
|
+
* Install the full email module (Resend) for production email sending.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Send password reset email
|
|
10
|
+
*/
|
|
11
|
+
export async function sendPasswordResetEmail(
|
|
12
|
+
email: string,
|
|
13
|
+
url: string
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
if (process.env.NODE_ENV === 'development') {
|
|
16
|
+
console.log(`📧 [Email] Password reset email would be sent to ${email}`);
|
|
17
|
+
console.log(` Reset URL: ${url}`);
|
|
18
|
+
console.log(` Install the email module (Resend) for actual email sending.`);
|
|
19
|
+
}
|
|
20
|
+
// In production without email module, this fails silently
|
|
21
|
+
// Install the email module for production use
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Send subscription confirmation email
|
|
26
|
+
*/
|
|
27
|
+
export async function sendSubscriptionConfirmationEmail(
|
|
28
|
+
email: string
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
if (process.env.NODE_ENV === 'development') {
|
|
31
|
+
console.log(`📧 [Email] Subscription confirmation email would be sent to ${email}`);
|
|
32
|
+
console.log(` Install the email module (Resend) for actual email sending.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Send payment failed email
|
|
38
|
+
*/
|
|
39
|
+
export async function sendPaymentFailedEmail(
|
|
40
|
+
email: string
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
if (process.env.NODE_ENV === 'development') {
|
|
43
|
+
console.log(`📧 [Email] Payment failed email would be sent to ${email}`);
|
|
44
|
+
console.log(` Install the email module (Resend) for actual email sending.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Middleware Patch
|
|
3
|
+
*
|
|
4
|
+
* This file contains the middleware logic for authentication.
|
|
5
|
+
* When the auth module is appended, this will be merged into the base middleware.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
9
|
+
import { getSessionCookie } from "better-auth/cookies";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Apply auth middleware logic
|
|
13
|
+
* This function should be called from the base middleware
|
|
14
|
+
*/
|
|
15
|
+
export function applyAuthMiddleware(
|
|
16
|
+
request: NextRequest,
|
|
17
|
+
baseResponse: NextResponse
|
|
18
|
+
): NextResponse | null {
|
|
19
|
+
const sessionCookie = getSessionCookie(request);
|
|
20
|
+
const { pathname } = request.nextUrl;
|
|
21
|
+
|
|
22
|
+
// Don't redirect from sign-in/sign-up pages to avoid redirect loops
|
|
23
|
+
if (["/sign-in", "/sign-up"].includes(pathname)) {
|
|
24
|
+
return null; // Let base middleware handle it
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Redirect unauthenticated users from protected routes
|
|
28
|
+
const protectedRoutes = ["/dashboard"]; // Can be customized
|
|
29
|
+
const isProtectedRoute = protectedRoutes.some(route => pathname.startsWith(route));
|
|
30
|
+
|
|
31
|
+
if (isProtectedRoute && !sessionCookie) {
|
|
32
|
+
const returnTo = encodeURIComponent(pathname);
|
|
33
|
+
return NextResponse.redirect(new URL(`/sign-in?returnTo=${returnTo}`, request.url));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null; // Continue with base middleware
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Middleware matcher configuration for auth
|
|
41
|
+
*/
|
|
42
|
+
export const matcher = ["/dashboard/:path*", "/sign-in", "/sign-up"];
|
|
43
|
+
|