shipd 0.1.3 → 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 +1858 -956
- 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,48 @@
|
|
|
1
|
+
import postgres from "postgres";
|
|
2
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
3
|
+
|
|
4
|
+
// Lazy database connection - only connect when actually used
|
|
5
|
+
// This allows the app to start even without DATABASE_URL configured
|
|
6
|
+
let dbInstance: ReturnType<typeof drizzle> | null = null;
|
|
7
|
+
let connectionError: Error | null = null;
|
|
8
|
+
|
|
9
|
+
function getDb() {
|
|
10
|
+
if (dbInstance) {
|
|
11
|
+
return dbInstance;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (connectionError) {
|
|
15
|
+
throw connectionError;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const connectionString = process.env.DATABASE_URL;
|
|
19
|
+
if (!connectionString) {
|
|
20
|
+
connectionError = new Error(
|
|
21
|
+
"Missing DATABASE_URL. Add it to your .env.local (or hosting provider env vars). " +
|
|
22
|
+
"You can use a Neon Postgres URL, Supabase, or any Postgres connection string."
|
|
23
|
+
);
|
|
24
|
+
throw connectionError;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Disable prepared statements for Supabase compatibility
|
|
29
|
+
const client = postgres(connectionString, { prepare: false });
|
|
30
|
+
dbInstance = drizzle(client);
|
|
31
|
+
return dbInstance;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
connectionError = error instanceof Error ? error : new Error(String(error));
|
|
34
|
+
throw connectionError;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Export a proxy that lazily initializes the database
|
|
39
|
+
export const db = new Proxy({} as ReturnType<typeof drizzle>, {
|
|
40
|
+
get(_target, prop) {
|
|
41
|
+
const db = getDb();
|
|
42
|
+
const value = db[prop as keyof ReturnType<typeof drizzle>];
|
|
43
|
+
if (typeof value === 'function') {
|
|
44
|
+
return value.bind(db);
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
boolean,
|
|
3
|
+
integer,
|
|
4
|
+
pgTable,
|
|
5
|
+
text,
|
|
6
|
+
timestamp,
|
|
7
|
+
} from "drizzle-orm/pg-core";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base Database Schema
|
|
11
|
+
*
|
|
12
|
+
* This is the base schema. Other modules (auth, payments) will add their tables.
|
|
13
|
+
* When appending modules, their schema files will be merged into this file.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Auth tables will be added by the auth module
|
|
17
|
+
// Subscription table will be added by the payments module
|
|
18
|
+
|
|
19
|
+
// Base schema is empty - modules add their own tables
|
|
20
|
+
// This file serves as the entry point for schema merging
|
|
21
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
import { defineConfig } from "drizzle-kit";
|
|
3
|
+
|
|
4
|
+
config({ path: '.env' });
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
schema: "./db/schema.ts",
|
|
8
|
+
out: "./db/migrations",
|
|
9
|
+
dialect: "postgresql",
|
|
10
|
+
dbCredentials: {
|
|
11
|
+
url: process.env.DATABASE_URL!,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "database",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "PostgreSQL database setup with Drizzle ORM",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"postgres": "^3.4.4",
|
|
7
|
+
"drizzle-orm": "^0.41.0"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"drizzle-kit": "^0.31.0"
|
|
11
|
+
},
|
|
12
|
+
"envVars": [
|
|
13
|
+
"DATABASE_URL"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
"drizzle.config.ts",
|
|
17
|
+
"db/drizzle.ts",
|
|
18
|
+
"db/schema.ts",
|
|
19
|
+
"db/migrations"
|
|
20
|
+
],
|
|
21
|
+
"packageJsonScripts": {
|
|
22
|
+
"db:push": "drizzle-kit push",
|
|
23
|
+
"db:generate": "drizzle-kit generate",
|
|
24
|
+
"db:migrate": "drizzle-kit migrate",
|
|
25
|
+
"db:studio": "drizzle-kit studio"
|
|
26
|
+
},
|
|
27
|
+
"requires": [],
|
|
28
|
+
"conflicts": []
|
|
29
|
+
}
|
|
30
|
+
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Email Module - Integration Guide
|
|
2
|
+
|
|
3
|
+
**Module Version:** 1.0.0
|
|
4
|
+
**Last Updated:** 2025-12-22
|
|
5
|
+
**Standalone:** ✅ Yes (can work independently)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This module adds email sending functionality using Resend and React Email. It includes pre-built email templates for common use cases like welcome emails, password resets, subscription confirmations, and payment failures.
|
|
12
|
+
|
|
13
|
+
**Key Features:**
|
|
14
|
+
- Resend integration for email delivery
|
|
15
|
+
- React Email templates (beautiful, responsive emails)
|
|
16
|
+
- Pre-built email templates:
|
|
17
|
+
- Welcome email
|
|
18
|
+
- Password reset email
|
|
19
|
+
- Subscription confirmation email
|
|
20
|
+
- Payment failed email
|
|
21
|
+
- Graceful fallback (logs to console if not configured)
|
|
22
|
+
- Type-safe email sending
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Files Added
|
|
27
|
+
|
|
28
|
+
### Utilities/Libraries
|
|
29
|
+
- `lib/email.ts` - Email sending utilities and helper functions
|
|
30
|
+
|
|
31
|
+
### Email Templates
|
|
32
|
+
- `emails/welcome.tsx` - Welcome email template
|
|
33
|
+
- `emails/password-reset.tsx` - Password reset email template
|
|
34
|
+
- `emails/subscription-confirmation.tsx` - Subscription confirmation template
|
|
35
|
+
- `emails/payment-failed.tsx` - Payment failed notification template
|
|
36
|
+
- `emails/components/layout.tsx` - Shared email layout component
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Dependencies
|
|
41
|
+
|
|
42
|
+
### Package Dependencies
|
|
43
|
+
The following packages will be added to `package.json`:
|
|
44
|
+
- `resend@^6.6.0` - Resend email service SDK
|
|
45
|
+
- `@react-email/components@^1.0.2` - React Email component library
|
|
46
|
+
- `@react-email/render@^2.0.0` - React Email rendering utilities
|
|
47
|
+
|
|
48
|
+
### Required Modules
|
|
49
|
+
- None - Email module is standalone
|
|
50
|
+
|
|
51
|
+
### Optional Integration
|
|
52
|
+
- **Auth Module** - Uses email for password resets (works without it, but emails won't send)
|
|
53
|
+
- **Payments Module** - Uses email for subscription confirmations (works without it, but emails won't send)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Environment Variables
|
|
58
|
+
|
|
59
|
+
Add these to your `.env.local`:
|
|
60
|
+
|
|
61
|
+
```env
|
|
62
|
+
# Email (Resend)
|
|
63
|
+
RESEND_API_KEY="re_xxxxxxxxxxxxx" # Your Resend API key
|
|
64
|
+
EMAIL_FROM="Your App <noreply@yourdomain.com>" # Sender email address
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Required Variables:**
|
|
68
|
+
- `RESEND_API_KEY` - Your Resend API key (get from [resend.com](https://resend.com))
|
|
69
|
+
|
|
70
|
+
**Optional Variables:**
|
|
71
|
+
- `EMAIL_FROM` - Sender email address (defaults to "SaaS Scaffold <noreply@saas-scaffold.com>")
|
|
72
|
+
|
|
73
|
+
**Where to get them:**
|
|
74
|
+
- **Resend**: [resend.com](https://resend.com) - Free tier available (100 emails/day)
|
|
75
|
+
- Create an account and get your API key from the dashboard
|
|
76
|
+
- Verify your domain for production use
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Manual Integration Steps
|
|
81
|
+
|
|
82
|
+
If you're appending this module to an existing project, follow these steps:
|
|
83
|
+
|
|
84
|
+
### Step 1: Install Dependencies
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install
|
|
88
|
+
# or
|
|
89
|
+
pnpm install
|
|
90
|
+
# or
|
|
91
|
+
yarn install
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The append command automatically adds missing dependencies to `package.json`, but you need to install them.
|
|
95
|
+
|
|
96
|
+
### Step 2: Add Environment Variables
|
|
97
|
+
|
|
98
|
+
1. Copy `.env.example` to `.env.local` (if not exists)
|
|
99
|
+
2. Add `RESEND_API_KEY` with your Resend API key
|
|
100
|
+
3. Optionally set `EMAIL_FROM` with your sender email
|
|
101
|
+
|
|
102
|
+
### Step 3: Verify Installation
|
|
103
|
+
|
|
104
|
+
1. Check that `lib/email.ts` exists
|
|
105
|
+
2. Check that `emails/` directory exists with all templates
|
|
106
|
+
3. Start your dev server: `npm run dev`
|
|
107
|
+
|
|
108
|
+
### Step 4: Test Email Sending
|
|
109
|
+
|
|
110
|
+
The email module works gracefully without configuration:
|
|
111
|
+
- If `RESEND_API_KEY` is not set, emails are logged to console
|
|
112
|
+
- If `RESEND_API_KEY` is set, emails are sent via Resend
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Usage Examples
|
|
117
|
+
|
|
118
|
+
### Sending a Welcome Email
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { sendWelcomeEmail } from "@/lib/email";
|
|
122
|
+
|
|
123
|
+
// In your sign-up handler
|
|
124
|
+
await sendWelcomeEmail(user.email, user.name);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Sending a Password Reset Email
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { sendPasswordResetEmail } from "@/lib/email";
|
|
131
|
+
|
|
132
|
+
// In your password reset handler
|
|
133
|
+
const resetUrl = `${process.env.NEXT_PUBLIC_APP_URL}/reset-password?token=${token}`;
|
|
134
|
+
await sendPasswordResetEmail(user.email, resetUrl);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Sending a Custom Email
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { sendEmail } from "@/lib/email";
|
|
141
|
+
import { MyCustomEmail } from "@/emails/my-custom-email";
|
|
142
|
+
|
|
143
|
+
await sendEmail({
|
|
144
|
+
to: "user@example.com",
|
|
145
|
+
subject: "Custom Email Subject",
|
|
146
|
+
react: MyCustomEmail({ customProp: "value" }),
|
|
147
|
+
from: "Custom Sender <custom@yourdomain.com>", // Optional
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Creating a New Email Template
|
|
152
|
+
|
|
153
|
+
1. Create a new file in `emails/`:
|
|
154
|
+
```typescript
|
|
155
|
+
// emails/my-email.tsx
|
|
156
|
+
import { Text } from '@react-email/components';
|
|
157
|
+
import { EmailLayout } from './components/layout';
|
|
158
|
+
|
|
159
|
+
interface MyEmailProps {
|
|
160
|
+
userName: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function MyEmail({ userName }: MyEmailProps) {
|
|
164
|
+
return (
|
|
165
|
+
<EmailLayout preview="My email preview">
|
|
166
|
+
<Text>Hello {userName}!</Text>
|
|
167
|
+
</EmailLayout>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
2. Add a helper function in `lib/email.ts`:
|
|
173
|
+
```typescript
|
|
174
|
+
export async function sendMyEmail(email: string, userName: string) {
|
|
175
|
+
const { MyEmail } = await import('@/emails/my-email');
|
|
176
|
+
return sendEmail({
|
|
177
|
+
to: email,
|
|
178
|
+
subject: 'My Email Subject',
|
|
179
|
+
react: MyEmail({ userName }),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Customization
|
|
187
|
+
|
|
188
|
+
### Styling
|
|
189
|
+
- Email templates use React Email components
|
|
190
|
+
- Customize styles in individual email templates
|
|
191
|
+
- Shared layout in `emails/components/layout.tsx`
|
|
192
|
+
|
|
193
|
+
### Configuration
|
|
194
|
+
- Email configuration is in `lib/email.ts`
|
|
195
|
+
- Customize default sender in `EMAIL_FROM` env var
|
|
196
|
+
- Modify email templates in `emails/` directory
|
|
197
|
+
|
|
198
|
+
### Templates
|
|
199
|
+
- All templates are in `emails/` directory
|
|
200
|
+
- Use `EmailLayout` component for consistent styling
|
|
201
|
+
- Templates are React components - fully customizable
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Integration Points
|
|
206
|
+
|
|
207
|
+
### Files That May Need Manual Updates
|
|
208
|
+
|
|
209
|
+
**lib/auth.ts** (if auth module installed):
|
|
210
|
+
- Already imports email functions
|
|
211
|
+
- Will automatically use real email sending when email module is installed
|
|
212
|
+
- Falls back to console logging if email module not installed
|
|
213
|
+
|
|
214
|
+
**lib/subscription.ts** (if payments module installed):
|
|
215
|
+
- Subscription confirmation emails will work automatically
|
|
216
|
+
- Payment failed emails will work automatically
|
|
217
|
+
|
|
218
|
+
**No manual integration needed!** The email module integrates automatically with auth and payments modules.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Troubleshooting
|
|
223
|
+
|
|
224
|
+
### Common Issues
|
|
225
|
+
|
|
226
|
+
**Issue:** "Email not sent (Resend not configured)" in console
|
|
227
|
+
**Solution:**
|
|
228
|
+
- Add `RESEND_API_KEY` to `.env.local`
|
|
229
|
+
- Get API key from [resend.com](https://resend.com)
|
|
230
|
+
- Restart dev server after adding env var
|
|
231
|
+
|
|
232
|
+
**Issue:** Emails not being received
|
|
233
|
+
**Solution:**
|
|
234
|
+
- Check Resend dashboard for delivery status
|
|
235
|
+
- Verify sender email is verified in Resend
|
|
236
|
+
- Check spam folder
|
|
237
|
+
- Verify `EMAIL_FROM` matches verified domain
|
|
238
|
+
|
|
239
|
+
**Issue:** "Invalid API key" error
|
|
240
|
+
**Solution:**
|
|
241
|
+
- Verify `RESEND_API_KEY` is correct
|
|
242
|
+
- Check for extra spaces or quotes in `.env.local`
|
|
243
|
+
- Regenerate API key in Resend dashboard if needed
|
|
244
|
+
|
|
245
|
+
**Issue:** Domain not verified
|
|
246
|
+
**Solution:**
|
|
247
|
+
- In development, you can use Resend's test domain
|
|
248
|
+
- For production, verify your domain in Resend dashboard
|
|
249
|
+
- Add DNS records as instructed by Resend
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Next Steps
|
|
254
|
+
|
|
255
|
+
After installing this module:
|
|
256
|
+
|
|
257
|
+
1. Get Resend API key from [resend.com](https://resend.com)
|
|
258
|
+
2. Add `RESEND_API_KEY` to `.env.local`
|
|
259
|
+
3. Test email sending (welcome email, password reset, etc.)
|
|
260
|
+
4. Verify your domain for production
|
|
261
|
+
5. Customize email templates to match your brand
|
|
262
|
+
6. Set up custom `EMAIL_FROM` address
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Related Modules
|
|
267
|
+
|
|
268
|
+
This module works well with:
|
|
269
|
+
- **Auth Module** - For password reset emails
|
|
270
|
+
- **Payments Module** - For subscription confirmation and payment failed emails
|
|
271
|
+
|
|
272
|
+
**Note:** Email module is optional. Auth and Payments modules work without it (they log to console instead of sending emails).
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Module Status
|
|
277
|
+
|
|
278
|
+
✅ **Standalone Package** - Can be installed independently
|
|
279
|
+
✅ **Graceful Fallback** - Works without configuration (logs to console)
|
|
280
|
+
✅ **Ready to Use** - Works immediately after append
|
|
281
|
+
✅ **Well Documented** - Comprehensive integration guide
|
|
282
|
+
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Html,
|
|
3
|
+
Head,
|
|
4
|
+
Preview,
|
|
5
|
+
Body,
|
|
6
|
+
Container,
|
|
7
|
+
Section,
|
|
8
|
+
Text,
|
|
9
|
+
Link,
|
|
10
|
+
Hr,
|
|
11
|
+
} from '@react-email/components';
|
|
12
|
+
|
|
13
|
+
interface EmailLayoutProps {
|
|
14
|
+
preview: string;
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function EmailLayout({ preview, children }: EmailLayoutProps) {
|
|
19
|
+
return (
|
|
20
|
+
<Html>
|
|
21
|
+
<Head />
|
|
22
|
+
<Preview>{preview}</Preview>
|
|
23
|
+
<Body style={main}>
|
|
24
|
+
<Container style={container}>
|
|
25
|
+
{/* Header */}
|
|
26
|
+
<Section style={header}>
|
|
27
|
+
<Text style={logo}>SaaS Scaffold</Text>
|
|
28
|
+
</Section>
|
|
29
|
+
|
|
30
|
+
{/* Main Content */}
|
|
31
|
+
<Section style={content}>
|
|
32
|
+
{children}
|
|
33
|
+
</Section>
|
|
34
|
+
|
|
35
|
+
{/* Footer */}
|
|
36
|
+
<Hr style={hr} />
|
|
37
|
+
<Section style={footer}>
|
|
38
|
+
<Text style={footerText}>
|
|
39
|
+
© {new Date().getFullYear()} SaaS Scaffold. All rights reserved.
|
|
40
|
+
</Text>
|
|
41
|
+
<Text style={footerLinks}>
|
|
42
|
+
<Link href="https://saas-scaffold.com" style={link}>
|
|
43
|
+
Website
|
|
44
|
+
</Link>
|
|
45
|
+
{' • '}
|
|
46
|
+
<Link href="https://saas-scaffold.com/docs" style={link}>
|
|
47
|
+
Docs
|
|
48
|
+
</Link>
|
|
49
|
+
{' • '}
|
|
50
|
+
<Link href="https://saas-scaffold.com/support" style={link}>
|
|
51
|
+
Support
|
|
52
|
+
</Link>
|
|
53
|
+
</Text>
|
|
54
|
+
<Text style={footerText}>
|
|
55
|
+
You received this email because you have an account with SaaS Scaffold.
|
|
56
|
+
</Text>
|
|
57
|
+
</Section>
|
|
58
|
+
</Container>
|
|
59
|
+
</Body>
|
|
60
|
+
</Html>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Styles
|
|
65
|
+
const main = {
|
|
66
|
+
backgroundColor: '#000000',
|
|
67
|
+
fontFamily:
|
|
68
|
+
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const container = {
|
|
72
|
+
backgroundColor: '#0a0a0a',
|
|
73
|
+
margin: '0 auto',
|
|
74
|
+
padding: '20px 0 48px',
|
|
75
|
+
marginBottom: '64px',
|
|
76
|
+
maxWidth: '600px',
|
|
77
|
+
border: '1px solid #2a2a2a',
|
|
78
|
+
borderRadius: '8px',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const header = {
|
|
82
|
+
padding: '32px 48px',
|
|
83
|
+
backgroundColor: '#000000',
|
|
84
|
+
borderBottom: '1px solid #2a2a2a',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const logo = {
|
|
88
|
+
fontSize: '24px',
|
|
89
|
+
fontWeight: 'bold',
|
|
90
|
+
color: '#ffffff',
|
|
91
|
+
margin: 0,
|
|
92
|
+
background: 'linear-gradient(135deg, #ff5722 0%, #d84315 100%)',
|
|
93
|
+
WebkitBackgroundClip: 'text',
|
|
94
|
+
WebkitTextFillColor: 'transparent',
|
|
95
|
+
backgroundClip: 'text',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const content = {
|
|
99
|
+
padding: '48px 48px 24px',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const hr = {
|
|
103
|
+
borderColor: '#2a2a2a',
|
|
104
|
+
margin: '32px 48px',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const footer = {
|
|
108
|
+
padding: '0 48px 32px',
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const footerText = {
|
|
112
|
+
fontSize: '12px',
|
|
113
|
+
lineHeight: '16px',
|
|
114
|
+
color: '#666666',
|
|
115
|
+
margin: '8px 0',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const footerLinks = {
|
|
119
|
+
fontSize: '12px',
|
|
120
|
+
lineHeight: '16px',
|
|
121
|
+
color: '#666666',
|
|
122
|
+
margin: '16px 0 8px',
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const link = {
|
|
126
|
+
color: '#ff5722',
|
|
127
|
+
textDecoration: 'none',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Reusable component styles
|
|
131
|
+
export const styles = {
|
|
132
|
+
h1: {
|
|
133
|
+
fontSize: '32px',
|
|
134
|
+
fontWeight: 'bold',
|
|
135
|
+
color: '#ffffff',
|
|
136
|
+
margin: '0 0 16px',
|
|
137
|
+
},
|
|
138
|
+
h2: {
|
|
139
|
+
fontSize: '24px',
|
|
140
|
+
fontWeight: 'bold',
|
|
141
|
+
color: '#ffffff',
|
|
142
|
+
margin: '32px 0 16px',
|
|
143
|
+
},
|
|
144
|
+
p: {
|
|
145
|
+
fontSize: '16px',
|
|
146
|
+
lineHeight: '24px',
|
|
147
|
+
color: '#cccccc',
|
|
148
|
+
margin: '16px 0',
|
|
149
|
+
},
|
|
150
|
+
button: {
|
|
151
|
+
backgroundColor: '#ff5722',
|
|
152
|
+
borderRadius: '6px',
|
|
153
|
+
color: '#ffffff',
|
|
154
|
+
fontSize: '16px',
|
|
155
|
+
fontWeight: '600',
|
|
156
|
+
textDecoration: 'none',
|
|
157
|
+
textAlign: 'center' as const,
|
|
158
|
+
display: 'inline-block',
|
|
159
|
+
padding: '12px 32px',
|
|
160
|
+
margin: '24px 0',
|
|
161
|
+
},
|
|
162
|
+
code: {
|
|
163
|
+
backgroundColor: '#1a1a1a',
|
|
164
|
+
border: '1px solid #2a2a2a',
|
|
165
|
+
borderRadius: '4px',
|
|
166
|
+
color: '#ff5722',
|
|
167
|
+
fontFamily: 'monospace',
|
|
168
|
+
fontSize: '14px',
|
|
169
|
+
padding: '2px 6px',
|
|
170
|
+
},
|
|
171
|
+
codeBlock: {
|
|
172
|
+
backgroundColor: '#1a1a1a',
|
|
173
|
+
border: '1px solid #2a2a2a',
|
|
174
|
+
borderRadius: '6px',
|
|
175
|
+
color: '#ffffff',
|
|
176
|
+
fontFamily: 'monospace',
|
|
177
|
+
fontSize: '14px',
|
|
178
|
+
padding: '16px',
|
|
179
|
+
margin: '16px 0',
|
|
180
|
+
},
|
|
181
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Text, Link, Section } from '@react-email/components';
|
|
2
|
+
import { EmailLayout, styles } from './components/layout';
|
|
3
|
+
|
|
4
|
+
interface PasswordResetEmailProps {
|
|
5
|
+
resetUrl: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function PasswordResetEmail({ resetUrl }: PasswordResetEmailProps) {
|
|
9
|
+
return (
|
|
10
|
+
<EmailLayout preview="Reset your SaaS Scaffold password">
|
|
11
|
+
<Text style={styles.h1}>Reset your password</Text>
|
|
12
|
+
|
|
13
|
+
<Text style={styles.p}>
|
|
14
|
+
We received a request to reset your password for your SaaS Scaffold account.
|
|
15
|
+
</Text>
|
|
16
|
+
|
|
17
|
+
<Text style={styles.p}>
|
|
18
|
+
Click the button below to choose a new password. This link will expire in 1 hour for security reasons.
|
|
19
|
+
</Text>
|
|
20
|
+
|
|
21
|
+
<Link href={resetUrl} style={styles.button}>
|
|
22
|
+
Reset Password
|
|
23
|
+
</Link>
|
|
24
|
+
|
|
25
|
+
<Section style={warningBox}>
|
|
26
|
+
<Text style={warningText}>
|
|
27
|
+
<strong>Security Tip:</strong> If you didn't request this password reset, you can safely ignore this email. Your password will remain unchanged.
|
|
28
|
+
</Text>
|
|
29
|
+
</Section>
|
|
30
|
+
|
|
31
|
+
<Text style={styles.p}>
|
|
32
|
+
For your security, this password reset link can only be used once and will expire in 60 minutes.
|
|
33
|
+
</Text>
|
|
34
|
+
|
|
35
|
+
<Text style={{ ...styles.p, marginTop: '32px' }}>
|
|
36
|
+
If you're having trouble clicking the button, copy and paste this URL into your browser:
|
|
37
|
+
</Text>
|
|
38
|
+
|
|
39
|
+
<Section style={styles.codeBlock}>
|
|
40
|
+
{resetUrl}
|
|
41
|
+
</Section>
|
|
42
|
+
|
|
43
|
+
<Text style={styles.p}>
|
|
44
|
+
Thanks,<br />
|
|
45
|
+
The SaaS Scaffold Team
|
|
46
|
+
</Text>
|
|
47
|
+
</EmailLayout>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Additional styles
|
|
52
|
+
const warningBox = {
|
|
53
|
+
backgroundColor: '#2a1a1a',
|
|
54
|
+
border: '1px solid #ff5722',
|
|
55
|
+
borderRadius: '6px',
|
|
56
|
+
padding: '16px',
|
|
57
|
+
margin: '24px 0',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const warningText = {
|
|
61
|
+
fontSize: '14px',
|
|
62
|
+
lineHeight: '20px',
|
|
63
|
+
color: '#ff9a80',
|
|
64
|
+
margin: 0,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default PasswordResetEmail;
|