shipd 0.1.2 → 0.1.4
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 +1862 -948
- package/features/ai-chat/README.md +258 -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 +308 -0
- package/features/analytics/feature.config.json +20 -0
- package/features/analytics/lib/posthog.ts +36 -0
- package/features/auth/README.md +336 -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 +256 -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 +282 -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 +271 -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 +266 -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 +306 -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 +244 -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,167 @@
|
|
|
1
|
+
import { Text, Link, Section } from '@react-email/components';
|
|
2
|
+
import { EmailLayout, styles } from './components/layout';
|
|
3
|
+
|
|
4
|
+
interface PaymentFailedEmailProps {
|
|
5
|
+
planName: string;
|
|
6
|
+
retryDate: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function PaymentFailedEmail({
|
|
10
|
+
planName,
|
|
11
|
+
retryDate,
|
|
12
|
+
}: PaymentFailedEmailProps) {
|
|
13
|
+
return (
|
|
14
|
+
<EmailLayout preview="Payment failed - Action required">
|
|
15
|
+
<Text style={styles.h1}>Payment Failed</Text>
|
|
16
|
+
|
|
17
|
+
<Section style={alertBox}>
|
|
18
|
+
<Text style={alertText}>
|
|
19
|
+
<strong>Action Required:</strong> We couldn't process your payment for the {planName} subscription.
|
|
20
|
+
</Text>
|
|
21
|
+
</Section>
|
|
22
|
+
|
|
23
|
+
<Text style={styles.p}>
|
|
24
|
+
We attempted to charge your payment method on file, but the payment was declined. This can happen for several reasons:
|
|
25
|
+
</Text>
|
|
26
|
+
|
|
27
|
+
<ul style={list}>
|
|
28
|
+
<li style={listItem}>
|
|
29
|
+
<Text style={listText}>Insufficient funds</Text>
|
|
30
|
+
</li>
|
|
31
|
+
<li style={listItem}>
|
|
32
|
+
<Text style={listText}>Expired card</Text>
|
|
33
|
+
</li>
|
|
34
|
+
<li style={listItem}>
|
|
35
|
+
<Text style={listText}>Card blocked or frozen</Text>
|
|
36
|
+
</li>
|
|
37
|
+
<li style={listItem}>
|
|
38
|
+
<Text style={listText}>Incorrect billing information</Text>
|
|
39
|
+
</li>
|
|
40
|
+
</ul>
|
|
41
|
+
|
|
42
|
+
<Text style={styles.h2}>What Happens Next?</Text>
|
|
43
|
+
|
|
44
|
+
<Section style={infoBox}>
|
|
45
|
+
<table style={table}>
|
|
46
|
+
<tbody>
|
|
47
|
+
<tr>
|
|
48
|
+
<td style={labelCell}>Next retry:</td>
|
|
49
|
+
<td style={valueCell}>{retryDate}</td>
|
|
50
|
+
</tr>
|
|
51
|
+
<tr>
|
|
52
|
+
<td style={labelCell}>Status:</td>
|
|
53
|
+
<td style={valueCell}>Access maintained until retry</td>
|
|
54
|
+
</tr>
|
|
55
|
+
</tbody>
|
|
56
|
+
</table>
|
|
57
|
+
</Section>
|
|
58
|
+
|
|
59
|
+
<Text style={styles.p}>
|
|
60
|
+
Your access to SaaS Scaffold will remain active until our next retry attempt. To avoid any interruption, please update your payment method as soon as possible.
|
|
61
|
+
</Text>
|
|
62
|
+
|
|
63
|
+
<Link href="https://saas-scaffold.com/dashboard/billing" style={styles.button}>
|
|
64
|
+
Update Payment Method
|
|
65
|
+
</Link>
|
|
66
|
+
|
|
67
|
+
<Text style={styles.p}>
|
|
68
|
+
If the payment issue isn't resolved by the retry date, your subscription will be automatically canceled and access will be revoked.
|
|
69
|
+
</Text>
|
|
70
|
+
|
|
71
|
+
<Section style={supportBox}>
|
|
72
|
+
<Text style={supportText}>
|
|
73
|
+
Need help? Contact our support team at{' '}
|
|
74
|
+
<Link href="mailto:support@saas-scaffold.com" style={supportLink}>
|
|
75
|
+
support@saas-scaffold.com
|
|
76
|
+
</Link>
|
|
77
|
+
</Text>
|
|
78
|
+
</Section>
|
|
79
|
+
|
|
80
|
+
<Text style={styles.p}>
|
|
81
|
+
Thanks,<br />
|
|
82
|
+
The SaaS Scaffold Team
|
|
83
|
+
</Text>
|
|
84
|
+
</EmailLayout>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Additional styles
|
|
89
|
+
const alertBox = {
|
|
90
|
+
backgroundColor: '#2a1a1a',
|
|
91
|
+
border: '1px solid #ff5722',
|
|
92
|
+
borderRadius: '6px',
|
|
93
|
+
padding: '16px',
|
|
94
|
+
margin: '0 0 24px',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const alertText = {
|
|
98
|
+
fontSize: '14px',
|
|
99
|
+
lineHeight: '20px',
|
|
100
|
+
color: '#ff9a80',
|
|
101
|
+
margin: 0,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const list = {
|
|
105
|
+
margin: '16px 0',
|
|
106
|
+
padding: '0 0 0 20px',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const listItem = {
|
|
110
|
+
margin: '8px 0',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const listText = {
|
|
114
|
+
fontSize: '14px',
|
|
115
|
+
lineHeight: '20px',
|
|
116
|
+
color: '#cccccc',
|
|
117
|
+
margin: 0,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const infoBox = {
|
|
121
|
+
backgroundColor: '#1a1a1a',
|
|
122
|
+
border: '1px solid #2a2a2a',
|
|
123
|
+
borderRadius: '6px',
|
|
124
|
+
padding: '16px',
|
|
125
|
+
margin: '16px 0',
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const table = {
|
|
129
|
+
width: '100%',
|
|
130
|
+
borderCollapse: 'collapse' as const,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const labelCell = {
|
|
134
|
+
fontSize: '14px',
|
|
135
|
+
color: '#999999',
|
|
136
|
+
padding: '8px 0',
|
|
137
|
+
width: '140px',
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const valueCell = {
|
|
141
|
+
fontSize: '14px',
|
|
142
|
+
color: '#ffffff',
|
|
143
|
+
fontWeight: '600',
|
|
144
|
+
padding: '8px 0',
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const supportBox = {
|
|
148
|
+
backgroundColor: '#1a1a1a',
|
|
149
|
+
border: '1px solid #2a2a2a',
|
|
150
|
+
borderRadius: '6px',
|
|
151
|
+
padding: '16px',
|
|
152
|
+
margin: '24px 0',
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const supportText = {
|
|
156
|
+
fontSize: '14px',
|
|
157
|
+
lineHeight: '20px',
|
|
158
|
+
color: '#cccccc',
|
|
159
|
+
margin: 0,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const supportLink = {
|
|
163
|
+
color: '#ff5722',
|
|
164
|
+
textDecoration: 'none',
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export default PaymentFailedEmail;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Text, Link, Section } from '@react-email/components';
|
|
2
|
+
import { EmailLayout, styles } from './components/layout';
|
|
3
|
+
|
|
4
|
+
interface SubscriptionConfirmationEmailProps {
|
|
5
|
+
planName: string;
|
|
6
|
+
amount: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function SubscriptionConfirmationEmail({
|
|
10
|
+
planName,
|
|
11
|
+
amount,
|
|
12
|
+
}: SubscriptionConfirmationEmailProps) {
|
|
13
|
+
return (
|
|
14
|
+
<EmailLayout preview={`Your ${planName} subscription is confirmed`}>
|
|
15
|
+
<Text style={styles.h1}>Subscription Confirmed! 🎉</Text>
|
|
16
|
+
|
|
17
|
+
<Text style={styles.p}>
|
|
18
|
+
Thank you for subscribing to SaaS Scaffold! Your subscription has been successfully activated.
|
|
19
|
+
</Text>
|
|
20
|
+
|
|
21
|
+
<Section style={summaryBox}>
|
|
22
|
+
<Text style={summaryTitle}>Subscription Summary</Text>
|
|
23
|
+
<table style={table}>
|
|
24
|
+
<tbody>
|
|
25
|
+
<tr>
|
|
26
|
+
<td style={labelCell}>Plan:</td>
|
|
27
|
+
<td style={valueCell}>{planName}</td>
|
|
28
|
+
</tr>
|
|
29
|
+
<tr>
|
|
30
|
+
<td style={labelCell}>Amount:</td>
|
|
31
|
+
<td style={valueCell}>{amount}</td>
|
|
32
|
+
</tr>
|
|
33
|
+
<tr>
|
|
34
|
+
<td style={labelCell}>Billing:</td>
|
|
35
|
+
<td style={valueCell}>Monthly (auto-renews)</td>
|
|
36
|
+
</tr>
|
|
37
|
+
</tbody>
|
|
38
|
+
</table>
|
|
39
|
+
</Section>
|
|
40
|
+
|
|
41
|
+
<Text style={styles.h2}>What's Next?</Text>
|
|
42
|
+
|
|
43
|
+
<Text style={styles.p}>
|
|
44
|
+
You now have access to all premium features:
|
|
45
|
+
</Text>
|
|
46
|
+
|
|
47
|
+
<ul style={list}>
|
|
48
|
+
<li style={listItem}>
|
|
49
|
+
<Text style={listText}>Generate unlimited projects with our CLI</Text>
|
|
50
|
+
</li>
|
|
51
|
+
<li style={listItem}>
|
|
52
|
+
<Text style={listText}>Access to all premium templates</Text>
|
|
53
|
+
</li>
|
|
54
|
+
<li style={listItem}>
|
|
55
|
+
<Text style={listText}>Priority support and updates</Text>
|
|
56
|
+
</li>
|
|
57
|
+
<li style={listItem}>
|
|
58
|
+
<Text style={listText}>Exclusive community access</Text>
|
|
59
|
+
</li>
|
|
60
|
+
</ul>
|
|
61
|
+
|
|
62
|
+
<Link href="https://saas-scaffold.com/dashboard" style={styles.button}>
|
|
63
|
+
Go to Dashboard
|
|
64
|
+
</Link>
|
|
65
|
+
|
|
66
|
+
<Text style={styles.p}>
|
|
67
|
+
You can manage your subscription, update payment methods, or view invoices anytime from your dashboard.
|
|
68
|
+
</Text>
|
|
69
|
+
|
|
70
|
+
<Text style={styles.p}>
|
|
71
|
+
Thanks for your support!<br />
|
|
72
|
+
The SaaS Scaffold Team
|
|
73
|
+
</Text>
|
|
74
|
+
</EmailLayout>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Additional styles
|
|
79
|
+
const summaryBox = {
|
|
80
|
+
backgroundColor: '#1a1a1a',
|
|
81
|
+
border: '1px solid #2a2a2a',
|
|
82
|
+
borderRadius: '6px',
|
|
83
|
+
padding: '24px',
|
|
84
|
+
margin: '24px 0',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const summaryTitle = {
|
|
88
|
+
fontSize: '18px',
|
|
89
|
+
fontWeight: '600',
|
|
90
|
+
color: '#ffffff',
|
|
91
|
+
margin: '0 0 16px',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const table = {
|
|
95
|
+
width: '100%',
|
|
96
|
+
borderCollapse: 'collapse' as const,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const labelCell = {
|
|
100
|
+
fontSize: '14px',
|
|
101
|
+
color: '#999999',
|
|
102
|
+
padding: '8px 0',
|
|
103
|
+
width: '100px',
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const valueCell = {
|
|
107
|
+
fontSize: '14px',
|
|
108
|
+
color: '#ffffff',
|
|
109
|
+
fontWeight: '600',
|
|
110
|
+
padding: '8px 0',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const list = {
|
|
114
|
+
margin: '16px 0',
|
|
115
|
+
padding: '0 0 0 20px',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const listItem = {
|
|
119
|
+
margin: '12px 0',
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const listText = {
|
|
123
|
+
fontSize: '14px',
|
|
124
|
+
lineHeight: '20px',
|
|
125
|
+
color: '#cccccc',
|
|
126
|
+
margin: 0,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export default SubscriptionConfirmationEmail;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Text, Link, Section } from '@react-email/components';
|
|
2
|
+
import { EmailLayout, styles } from './components/layout';
|
|
3
|
+
|
|
4
|
+
interface WelcomeEmailProps {
|
|
5
|
+
userName?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function WelcomeEmail({ userName = 'there' }: WelcomeEmailProps) {
|
|
9
|
+
return (
|
|
10
|
+
<EmailLayout preview="Welcome to SaaS Scaffold! Let's get you started.">
|
|
11
|
+
<Text style={styles.h1}>Welcome to SaaS Scaffold! 👋</Text>
|
|
12
|
+
|
|
13
|
+
<Text style={styles.p}>
|
|
14
|
+
Hi {userName},
|
|
15
|
+
</Text>
|
|
16
|
+
|
|
17
|
+
<Text style={styles.p}>
|
|
18
|
+
Thanks for joining SaaS Scaffold! We're excited to help you build and ship your SaaS product faster than ever.
|
|
19
|
+
</Text>
|
|
20
|
+
|
|
21
|
+
<Section style={cardContainer}>
|
|
22
|
+
<Text style={cardTitle}>What's included in your subscription:</Text>
|
|
23
|
+
<ul style={list}>
|
|
24
|
+
<li style={listItem}>
|
|
25
|
+
<Text style={listText}><strong>Premium Templates</strong> - Production-ready Next.js templates</Text>
|
|
26
|
+
</li>
|
|
27
|
+
<li style={listItem}>
|
|
28
|
+
<Text style={listText}><strong>CLI Tool</strong> - Generate projects in seconds</Text>
|
|
29
|
+
</li>
|
|
30
|
+
<li style={listItem}>
|
|
31
|
+
<Text style={listText}><strong>Authentication</strong> - Better Auth with Polar.sh integration</Text>
|
|
32
|
+
</li>
|
|
33
|
+
<li style={listItem}>
|
|
34
|
+
<Text style={listText}><strong>Billing</strong> - Subscriptions and payments pre-configured</Text>
|
|
35
|
+
</li>
|
|
36
|
+
<li style={listItem}>
|
|
37
|
+
<Text style={listText}><strong>Priority Support</strong> - Get help when you need it</Text>
|
|
38
|
+
</li>
|
|
39
|
+
</ul>
|
|
40
|
+
</Section>
|
|
41
|
+
|
|
42
|
+
<Text style={styles.h2}>Get Started</Text>
|
|
43
|
+
|
|
44
|
+
<Text style={styles.p}>
|
|
45
|
+
Ready to create your first project? Run this command in your terminal:
|
|
46
|
+
</Text>
|
|
47
|
+
|
|
48
|
+
<Section style={styles.codeBlock}>
|
|
49
|
+
npx saas-scaffold init my-project
|
|
50
|
+
</Section>
|
|
51
|
+
|
|
52
|
+
<Link href="https://saas-scaffold.com/docs/getting-started" style={styles.button}>
|
|
53
|
+
View Documentation
|
|
54
|
+
</Link>
|
|
55
|
+
|
|
56
|
+
<Text style={styles.p}>
|
|
57
|
+
If you have any questions or need help getting started, don't hesitate to reach out to our support team.
|
|
58
|
+
</Text>
|
|
59
|
+
|
|
60
|
+
<Text style={styles.p}>
|
|
61
|
+
Happy building!<br />
|
|
62
|
+
The SaaS Scaffold Team
|
|
63
|
+
</Text>
|
|
64
|
+
</EmailLayout>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Additional styles
|
|
69
|
+
const cardContainer = {
|
|
70
|
+
backgroundColor: '#1a1a1a',
|
|
71
|
+
border: '1px solid #2a2a2a',
|
|
72
|
+
borderRadius: '6px',
|
|
73
|
+
padding: '24px',
|
|
74
|
+
margin: '24px 0',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const cardTitle = {
|
|
78
|
+
fontSize: '18px',
|
|
79
|
+
fontWeight: '600',
|
|
80
|
+
color: '#ffffff',
|
|
81
|
+
margin: '0 0 16px',
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const list = {
|
|
85
|
+
margin: '0',
|
|
86
|
+
padding: '0 0 0 20px',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const listItem = {
|
|
90
|
+
margin: '12px 0',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const listText = {
|
|
94
|
+
fontSize: '14px',
|
|
95
|
+
lineHeight: '20px',
|
|
96
|
+
color: '#cccccc',
|
|
97
|
+
margin: 0,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export default WelcomeEmail;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "email",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Resend email integration with React Email templates",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"resend": "^6.6.0",
|
|
7
|
+
"@react-email/components": "^1.0.2",
|
|
8
|
+
"@react-email/render": "^2.0.0"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {},
|
|
11
|
+
"envVars": [
|
|
12
|
+
"RESEND_API_KEY",
|
|
13
|
+
"EMAIL_FROM"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
"lib/email.ts",
|
|
17
|
+
"emails/**/*"
|
|
18
|
+
],
|
|
19
|
+
"requires": [],
|
|
20
|
+
"conflicts": []
|
|
21
|
+
}
|
|
22
|
+
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Resend } from 'resend';
|
|
2
|
+
|
|
3
|
+
// Check if email is configured
|
|
4
|
+
const isEmailConfigured = !!process.env.RESEND_API_KEY;
|
|
5
|
+
|
|
6
|
+
if (!isEmailConfigured && process.env.NODE_ENV === 'development') {
|
|
7
|
+
console.warn('⚠️ Resend not configured - email sending disabled. Set RESEND_API_KEY to enable emails.');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const resend = isEmailConfigured ? new Resend(process.env.RESEND_API_KEY) : null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Send an email using Resend
|
|
14
|
+
* @param to - Recipient email address
|
|
15
|
+
* @param subject - Email subject line
|
|
16
|
+
* @param react - React email component
|
|
17
|
+
* @param from - Sender email (defaults to noreply@yourdomain.com)
|
|
18
|
+
*/
|
|
19
|
+
export async function sendEmail({
|
|
20
|
+
to,
|
|
21
|
+
subject,
|
|
22
|
+
react,
|
|
23
|
+
from = process.env.EMAIL_FROM || 'SaaS Scaffold <noreply@saas-scaffold.com>',
|
|
24
|
+
}: {
|
|
25
|
+
to: string;
|
|
26
|
+
subject: string;
|
|
27
|
+
react: React.ReactElement;
|
|
28
|
+
from?: string;
|
|
29
|
+
}) {
|
|
30
|
+
// Skip if email is not configured
|
|
31
|
+
if (!resend) {
|
|
32
|
+
console.log(`📧 Email not sent (Resend not configured): ${subject} to ${to}`);
|
|
33
|
+
return { success: true, skipped: true };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const { data, error } = await resend.emails.send({
|
|
38
|
+
from,
|
|
39
|
+
to,
|
|
40
|
+
subject,
|
|
41
|
+
react,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (error) {
|
|
45
|
+
console.error('Failed to send email:', error);
|
|
46
|
+
throw new Error(`Email send failed: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log('Email sent successfully:', data);
|
|
50
|
+
return { success: true, data };
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Email send error:', error);
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Send welcome email to new users
|
|
62
|
+
*/
|
|
63
|
+
export async function sendWelcomeEmail(email: string, name?: string) {
|
|
64
|
+
const { WelcomeEmail } = await import('@/emails/welcome');
|
|
65
|
+
|
|
66
|
+
return sendEmail({
|
|
67
|
+
to: email,
|
|
68
|
+
subject: 'Welcome to SaaS Scaffold!',
|
|
69
|
+
react: WelcomeEmail({ userName: name || 'there' }),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Send password reset email
|
|
75
|
+
*/
|
|
76
|
+
export async function sendPasswordResetEmail(email: string, resetUrl: string) {
|
|
77
|
+
const { PasswordResetEmail } = await import('@/emails/password-reset');
|
|
78
|
+
|
|
79
|
+
return sendEmail({
|
|
80
|
+
to: email,
|
|
81
|
+
subject: 'Reset your password',
|
|
82
|
+
react: PasswordResetEmail({ resetUrl }),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Send subscription confirmation email
|
|
88
|
+
*/
|
|
89
|
+
export async function sendSubscriptionConfirmationEmail(
|
|
90
|
+
email: string,
|
|
91
|
+
planName: string,
|
|
92
|
+
amount: string
|
|
93
|
+
) {
|
|
94
|
+
const { SubscriptionConfirmationEmail } = await import('@/emails/subscription-confirmation');
|
|
95
|
+
|
|
96
|
+
return sendEmail({
|
|
97
|
+
to: email,
|
|
98
|
+
subject: `Subscription confirmed: ${planName}`,
|
|
99
|
+
react: SubscriptionConfirmationEmail({ planName, amount }),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Send payment failed email
|
|
105
|
+
*/
|
|
106
|
+
export async function sendPaymentFailedEmail(
|
|
107
|
+
email: string,
|
|
108
|
+
planName: string,
|
|
109
|
+
retryDate: string
|
|
110
|
+
) {
|
|
111
|
+
const { PaymentFailedEmail } = await import('@/emails/payment-failed');
|
|
112
|
+
|
|
113
|
+
return sendEmail({
|
|
114
|
+
to: email,
|
|
115
|
+
subject: 'Payment failed - Action required',
|
|
116
|
+
react: PaymentFailedEmail({ planName, retryDate }),
|
|
117
|
+
});
|
|
118
|
+
}
|