vibe-ship-it 1.0.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/README.md +133 -0
- package/bin/init.js +135 -0
- package/package.json +30 -0
- package/template/CLAUDE.md +71 -0
- package/template/_claude/commands/check.md +6 -0
- package/template/_claude/commands/save.md +9 -0
- package/template/_claude/commands/ship.md +8 -0
- package/template/_claude/commands/stuck.md +10 -0
- package/template/_claude/commands/wow.md +10 -0
- package/template/_github/agents/assistant.agent.md +60 -0
- package/template/_github/agents/checker.agent.md +93 -0
- package/template/_github/agents/investigator.agent.md +196 -0
- package/template/_github/agents/shipper.agent.md +180 -0
- package/template/_github/copilot-instructions.md +60 -0
- package/template/skills/add-login/SKILL.md +204 -0
- package/template/skills/before-you-ship/SKILL.md +132 -0
- package/template/skills/build-page/SKILL.md +137 -0
- package/template/skills/make-it-wow/SKILL.md +139 -0
- package/template/skills/noob-mode/SKILL.md +135 -0
- package/template/skills/platforms/web-nextjs/SKILL.md +160 -0
- package/template/skills/platforms/web-nextjs/references/tailwind-shortcuts.md +97 -0
- package/template/skills/quick-check/SKILL.md +89 -0
- package/template/skills/save-data/SKILL.md +155 -0
- package/template/skills/send-email/SKILL.md +164 -0
- package/template/skills/show-data/SKILL.md +209 -0
- package/template/skills/unstuck/SKILL.md +105 -0
- package/template/skills/upload-file/SKILL.md +188 -0
- package/template/skills/what-am-i-building/SKILL.md +129 -0
- package/template/skills/what-am-i-building/references/platform-routing.md +66 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-nextjs
|
|
3
|
+
description: "Next.js + Tailwind + Supabase + Vercel conventions. Loaded automatically when building a web project."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Web Platform: Next.js
|
|
7
|
+
|
|
8
|
+
The default web stack. Next.js App Router + Tailwind CSS + Supabase + Vercel.
|
|
9
|
+
|
|
10
|
+
## Project Structure
|
|
11
|
+
|
|
12
|
+
Explain to the designer as:
|
|
13
|
+
> "Your project is organized into folders. Think of each folder as a room with a specific purpose."
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/
|
|
17
|
+
app/ ← Pages (each folder = a page on your site)
|
|
18
|
+
page.tsx ← Home page (yourdomain.com/)
|
|
19
|
+
layout.tsx ← The wrapper around every page (nav, footer)
|
|
20
|
+
about/
|
|
21
|
+
page.tsx ← About page (yourdomain.com/about)
|
|
22
|
+
contact/
|
|
23
|
+
page.tsx ← Contact page (yourdomain.com/contact)
|
|
24
|
+
dashboard/
|
|
25
|
+
page.tsx ← Dashboard (yourdomain.com/dashboard)
|
|
26
|
+
login/
|
|
27
|
+
page.tsx ← Login page (yourdomain.com/login)
|
|
28
|
+
actions.ts ← Server actions (code that saves data)
|
|
29
|
+
components/ ← Reusable pieces (navigation, footer, cards)
|
|
30
|
+
utils/
|
|
31
|
+
supabase/
|
|
32
|
+
server.ts ← Server-side database connection
|
|
33
|
+
client.ts ← Browser-side database connection
|
|
34
|
+
middleware.ts ← Page protection (bouncer for private pages)
|
|
35
|
+
|
|
36
|
+
public/ ← Static files (images, favicon)
|
|
37
|
+
.env.local ← Secret settings (database keys, API keys)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Key Conventions
|
|
41
|
+
|
|
42
|
+
### Server vs Client Components
|
|
43
|
+
|
|
44
|
+
Don't explain "server components" and "client components" in those terms. Say:
|
|
45
|
+
|
|
46
|
+
- **Default (server):** "This code runs on the server before the page is sent to the visitor. Good for loading data from the database."
|
|
47
|
+
- **`'use client'`:** "This code runs in the visitor's browser. Needed when things are interactive — forms, buttons that do stuff, anything that changes on screen."
|
|
48
|
+
|
|
49
|
+
Rule of thumb:
|
|
50
|
+
- Page shows data from database → server component (default, no directive)
|
|
51
|
+
- Page has interactive elements (forms, modals, toggles) → add `'use client'` at the top
|
|
52
|
+
|
|
53
|
+
### Server Actions
|
|
54
|
+
|
|
55
|
+
Explain as: "Code that runs when someone submits a form."
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
'use server'
|
|
59
|
+
|
|
60
|
+
export async function doSomething(formData: FormData) {
|
|
61
|
+
// This runs on the server, not in the visitor's browser
|
|
62
|
+
// Safe to access the database here
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Connect to a form:
|
|
67
|
+
```tsx
|
|
68
|
+
<form action={doSomething}>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Routing
|
|
72
|
+
|
|
73
|
+
- Each folder in `app/` = a page
|
|
74
|
+
- `page.tsx` = what shows at that URL
|
|
75
|
+
- `layout.tsx` = wrapper (navigation, footer) that stays the same across pages
|
|
76
|
+
- `loading.tsx` = what shows while the page is loading (optional)
|
|
77
|
+
- `error.tsx` = what shows if something goes wrong (optional)
|
|
78
|
+
|
|
79
|
+
### Styling (Tailwind)
|
|
80
|
+
|
|
81
|
+
Don't explain Tailwind utility names. Just use them. If they ask:
|
|
82
|
+
> "Tailwind uses short class names instead of writing CSS. `text-lg` means larger text, `bg-blue-500` means blue background. You don't need to memorize these — I'll pick the right ones."
|
|
83
|
+
|
|
84
|
+
### Images
|
|
85
|
+
|
|
86
|
+
Use Next.js Image component for optimization:
|
|
87
|
+
```tsx
|
|
88
|
+
import Image from 'next/image'
|
|
89
|
+
|
|
90
|
+
<Image
|
|
91
|
+
src="/photo.jpg"
|
|
92
|
+
alt="Description"
|
|
93
|
+
width={800}
|
|
94
|
+
height={600}
|
|
95
|
+
className="rounded-lg"
|
|
96
|
+
/>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
For external images (Supabase Storage, Unsplash), add the domain to `next.config.ts`:
|
|
100
|
+
```typescript
|
|
101
|
+
const nextConfig = {
|
|
102
|
+
images: {
|
|
103
|
+
remotePatterns: [
|
|
104
|
+
{ hostname: '*.supabase.co' },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Environment Variables
|
|
111
|
+
|
|
112
|
+
`.env.local` — local development only, never committed to git:
|
|
113
|
+
```
|
|
114
|
+
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
|
115
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
|
|
116
|
+
RESEND_API_KEY=re_xxx
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- `NEXT_PUBLIC_` prefix = accessible in browser code
|
|
120
|
+
- No prefix = server-only (for secrets like API keys)
|
|
121
|
+
|
|
122
|
+
When deploying, these must be set on Vercel too.
|
|
123
|
+
|
|
124
|
+
## Common Commands
|
|
125
|
+
|
|
126
|
+
| Command | What it does |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `npm run dev` | Start the development server (preview in browser) |
|
|
129
|
+
| `npm run build` | Build for production (check for errors) |
|
|
130
|
+
| `npx vercel` | Preview deployment |
|
|
131
|
+
| `npx vercel --prod` | Production deployment |
|
|
132
|
+
|
|
133
|
+
## Packages to Install as Needed
|
|
134
|
+
|
|
135
|
+
| When | Package | Install |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| Database/Auth/Storage | Supabase | `npm install @supabase/supabase-js @supabase/ssr` |
|
|
138
|
+
| Animations | Framer Motion | `npm install framer-motion` |
|
|
139
|
+
| Email | Resend | `npm install resend` |
|
|
140
|
+
| Icons | Lucide React | `npm install lucide-react` |
|
|
141
|
+
| Date formatting | date-fns | `npm install date-fns` |
|
|
142
|
+
|
|
143
|
+
Don't pre-install everything. Install when first needed.
|
|
144
|
+
|
|
145
|
+
## Supabase Setup (One Time)
|
|
146
|
+
|
|
147
|
+
1. Go to supabase.com and create a free project
|
|
148
|
+
2. Go to Settings → API
|
|
149
|
+
3. Copy the URL and anon key
|
|
150
|
+
4. Create `.env.local` with those values
|
|
151
|
+
|
|
152
|
+
> "You need a free Supabase account — that's where your data, logins, and file uploads live. Takes 2 minutes to set up."
|
|
153
|
+
|
|
154
|
+
## Vercel Deployment
|
|
155
|
+
|
|
156
|
+
1. `npx vercel` (first time: link to Vercel account)
|
|
157
|
+
2. Set environment variables on Vercel dashboard
|
|
158
|
+
3. `npx vercel --prod` for production
|
|
159
|
+
|
|
160
|
+
> "When you want to put it online, I'll run one command. First time it'll ask you to connect your Vercel account — just follow the link."
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Tailwind CSS Quick Reference
|
|
2
|
+
|
|
3
|
+
Common patterns for designers. Organized by what you want to achieve, not by CSS property name.
|
|
4
|
+
|
|
5
|
+
## Spacing
|
|
6
|
+
|
|
7
|
+
| Want | Class | What it does |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Space between sections | `space-y-4` / `space-y-8` | Vertical gap between children |
|
|
10
|
+
| Space inside a box | `p-4` / `p-8` / `px-4 py-8` | Padding (inside cushion) |
|
|
11
|
+
| Space outside a box | `m-4` / `mx-auto` | Margin (outside spacing) |
|
|
12
|
+
| Page side margins | `px-4 md:px-8` | Horizontal padding, wider on desktop |
|
|
13
|
+
| Center content | `max-w-7xl mx-auto` | Max width + auto horizontal margins |
|
|
14
|
+
| Section breathing room | `py-16 md:py-24` | Generous vertical padding |
|
|
15
|
+
|
|
16
|
+
## Typography
|
|
17
|
+
|
|
18
|
+
| Want | Class |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Giant headline | `text-5xl md:text-7xl font-bold tracking-tight` |
|
|
21
|
+
| Section heading | `text-3xl font-semibold` |
|
|
22
|
+
| Subheading | `text-xl font-medium text-gray-600` |
|
|
23
|
+
| Body text | `text-base leading-relaxed` |
|
|
24
|
+
| Large body | `text-lg leading-relaxed` |
|
|
25
|
+
| Small/caption | `text-sm text-gray-500` |
|
|
26
|
+
| Light text | `text-gray-400` |
|
|
27
|
+
| Bold | `font-bold` or `font-semibold` |
|
|
28
|
+
|
|
29
|
+
## Layout
|
|
30
|
+
|
|
31
|
+
| Want | Class |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Side by side (2 cols) | `grid grid-cols-1 md:grid-cols-2 gap-8` |
|
|
34
|
+
| Three columns | `grid grid-cols-1 md:grid-cols-3 gap-6` |
|
|
35
|
+
| Flexbox row | `flex items-center gap-4` |
|
|
36
|
+
| Flexbox column | `flex flex-col gap-4` |
|
|
37
|
+
| Full screen height | `min-h-screen` |
|
|
38
|
+
| Full width | `w-full` |
|
|
39
|
+
| Centered everything | `flex items-center justify-center min-h-screen` |
|
|
40
|
+
| Sticky header | `sticky top-0 z-50 bg-white/80 backdrop-blur` |
|
|
41
|
+
|
|
42
|
+
## Colors
|
|
43
|
+
|
|
44
|
+
| Want | Class |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Background | `bg-white` / `bg-gray-50` / `bg-black` |
|
|
47
|
+
| Text color | `text-gray-900` / `text-white` / `text-blue-600` |
|
|
48
|
+
| Accent background | `bg-blue-500` / `bg-indigo-600` (adjust hue) |
|
|
49
|
+
| Subtle card bg | `bg-gray-50` or `bg-white` |
|
|
50
|
+
| Dark section | `bg-gray-900 text-white` |
|
|
51
|
+
|
|
52
|
+
## Cards & Containers
|
|
53
|
+
|
|
54
|
+
| Want | Class |
|
|
55
|
+
|---|---|
|
|
56
|
+
| Basic card | `bg-white rounded-xl p-6 shadow-sm border` |
|
|
57
|
+
| Elevated card | `bg-white rounded-2xl p-8 shadow-lg` |
|
|
58
|
+
| Glass effect | `bg-white/80 backdrop-blur rounded-xl border border-white/20` |
|
|
59
|
+
| Outlined card | `border rounded-xl p-6` |
|
|
60
|
+
|
|
61
|
+
## Buttons
|
|
62
|
+
|
|
63
|
+
| Want | Class |
|
|
64
|
+
|---|---|
|
|
65
|
+
| Primary | `bg-black text-white px-6 py-2 rounded-lg hover:bg-gray-800 transition-colors` |
|
|
66
|
+
| Secondary | `border px-6 py-2 rounded-lg hover:bg-gray-50 transition-colors` |
|
|
67
|
+
| With hover lift | Add `hover:scale-[1.02] hover:shadow-lg transition-all` |
|
|
68
|
+
| Disabled | Add `disabled:opacity-50 disabled:cursor-not-allowed` |
|
|
69
|
+
|
|
70
|
+
## Images
|
|
71
|
+
|
|
72
|
+
| Want | Class |
|
|
73
|
+
|---|---|
|
|
74
|
+
| Rounded | `rounded-lg` or `rounded-2xl` |
|
|
75
|
+
| Circle (profile) | `rounded-full w-12 h-12 object-cover` |
|
|
76
|
+
| Cover container | `w-full h-64 object-cover` |
|
|
77
|
+
| Aspect ratio | `aspect-square` / `aspect-video` / `aspect-[4/3]` |
|
|
78
|
+
|
|
79
|
+
## Responsive
|
|
80
|
+
|
|
81
|
+
All classes can be prefixed for different screen sizes:
|
|
82
|
+
- No prefix = mobile (default)
|
|
83
|
+
- `md:` = tablet and up (768px+)
|
|
84
|
+
- `lg:` = desktop (1024px+)
|
|
85
|
+
|
|
86
|
+
Example: `text-2xl md:text-4xl lg:text-6xl` — heading grows on bigger screens.
|
|
87
|
+
|
|
88
|
+
## Animation
|
|
89
|
+
|
|
90
|
+
| Want | Class |
|
|
91
|
+
|---|---|
|
|
92
|
+
| Smooth transitions | `transition-all duration-300` |
|
|
93
|
+
| Hover scale | `hover:scale-105 transition-transform` |
|
|
94
|
+
| Hover lift | `hover:-translate-y-1 hover:shadow-xl transition-all` |
|
|
95
|
+
| Fade in (custom) | See make-it-wow skill |
|
|
96
|
+
| Spin | `animate-spin` (for loading indicators) |
|
|
97
|
+
| Pulse | `animate-pulse` (for skeleton loading) |
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: quick-check
|
|
3
|
+
description: "Fast 3-item quality check. Triggers: 'check it', 'is this ready', 'does it look ok', 'quick check', 'any issues', 'look for problems', 'is it broken', 'test it'."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Quick Check
|
|
7
|
+
|
|
8
|
+
The fast quality check. 3 items. Takes under a minute. Good enough for "can I show my friend?"
|
|
9
|
+
|
|
10
|
+
## The 3 Checks
|
|
11
|
+
|
|
12
|
+
### 1. Does It Load?
|
|
13
|
+
|
|
14
|
+
Open every page in the browser. Check:
|
|
15
|
+
- No blank/white pages
|
|
16
|
+
- No console errors (open dev tools → Console)
|
|
17
|
+
- No visible "Error" or "500" or "404" on the page
|
|
18
|
+
- Content actually renders (not just a loading spinner forever)
|
|
19
|
+
|
|
20
|
+
✅ Pass: all pages show content
|
|
21
|
+
❌ Fail: any page shows an error, stays blank, or shows a crash screen
|
|
22
|
+
|
|
23
|
+
### 2. Does The Main Thing Work?
|
|
24
|
+
|
|
25
|
+
Identify the PRIMARY user action (form submit, button click, navigation flow) and do it:
|
|
26
|
+
- If there's a form → fill it out and submit. Does it save? Does it show confirmation?
|
|
27
|
+
- If there's a CTA button → click it. Does it go somewhere?
|
|
28
|
+
- If there's a login → try logging in. Does it work?
|
|
29
|
+
- If it's a portfolio → do images load? Can you navigate between pages?
|
|
30
|
+
|
|
31
|
+
✅ Pass: the main action completes successfully
|
|
32
|
+
❌ Fail: the main action breaks, shows an error, or does nothing
|
|
33
|
+
|
|
34
|
+
### 3. Mobile?
|
|
35
|
+
|
|
36
|
+
Resize the browser to 375px width (iPhone SE). Check:
|
|
37
|
+
- Text is readable (not overflowing or cut off)
|
|
38
|
+
- Navigation is usable (hamburger menu works, or links are tappable)
|
|
39
|
+
- No horizontal scroll
|
|
40
|
+
- Content isn't overlapping
|
|
41
|
+
|
|
42
|
+
✅ Pass: looks usable on a phone
|
|
43
|
+
❌ Fail: layout is broken, text overflows, navigation is unusable
|
|
44
|
+
|
|
45
|
+
## Report Format
|
|
46
|
+
|
|
47
|
+
### All Good
|
|
48
|
+
```
|
|
49
|
+
✅ Quick check — looks good.
|
|
50
|
+
|
|
51
|
+
1. ✅ All pages load fine
|
|
52
|
+
2. ✅ Form submits and saves correctly
|
|
53
|
+
3. ✅ Looks good on mobile
|
|
54
|
+
|
|
55
|
+
Ready to show someone!
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Issues Found
|
|
59
|
+
```
|
|
60
|
+
Quick check — found 1 thing:
|
|
61
|
+
|
|
62
|
+
1. ✅ All pages load fine
|
|
63
|
+
2. ✅ Form submits correctly
|
|
64
|
+
3. ⚠️ Mobile: the headline text is too wide and gets cut off on phones
|
|
65
|
+
|
|
66
|
+
This won't break anything, but it'll look off on smaller screens.
|
|
67
|
+
Want me to fix it?
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Serious Issue
|
|
71
|
+
```
|
|
72
|
+
Quick check — found a problem:
|
|
73
|
+
|
|
74
|
+
1. ✅ All pages load
|
|
75
|
+
2. ❌ The contact form submits but nothing happens — no confirmation,
|
|
76
|
+
no data saved. The server action might not be connected.
|
|
77
|
+
3. ✅ Mobile looks fine
|
|
78
|
+
|
|
79
|
+
This means the main feature doesn't work yet.
|
|
80
|
+
Want me to fix it?
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Rules
|
|
84
|
+
|
|
85
|
+
- NEVER skip a check
|
|
86
|
+
- NEVER sugarcoat a broken main feature — be direct
|
|
87
|
+
- DO minimize minor issues — "the footer spacing is 4px off" is not worth mentioning in a quick check
|
|
88
|
+
- Always end with "Want me to fix it?" if issues are found
|
|
89
|
+
- If everything passes, encourage: "Ready to show someone!" or "Looks good for a demo!"
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: save-data
|
|
3
|
+
description: "Saves form data to a database. Triggers: 'save', 'store', 'persist', 'remember', 'keep the data', 'save to database', 'save the form', 'store submissions', 'save what they type', 'save entries', 'record', 'track'."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Save Data
|
|
7
|
+
|
|
8
|
+
Makes forms actually save data. Handles the complete flow: create the database table, write the save function, connect it to the form, and test it.
|
|
9
|
+
|
|
10
|
+
## The Spreadsheet Analogy
|
|
11
|
+
|
|
12
|
+
Before creating anything, show the designer what their "spreadsheet" will look like:
|
|
13
|
+
|
|
14
|
+
> "Here's what your data will look like — think of it as a spreadsheet:
|
|
15
|
+
>
|
|
16
|
+
> | name | email | message | submitted at |
|
|
17
|
+
> |---|---|---|---|
|
|
18
|
+
> | Jane Smith | jane@email.com | Hello! I'd like... | Apr 3, 2026 |
|
|
19
|
+
> | Alex Kim | alex@email.com | Question about... | Apr 3, 2026 |
|
|
20
|
+
>
|
|
21
|
+
> Each time someone fills out the form, a new line gets added here."
|
|
22
|
+
|
|
23
|
+
## Implementation Steps
|
|
24
|
+
|
|
25
|
+
### Step 1: Create the Table
|
|
26
|
+
|
|
27
|
+
Generate SQL for Supabase:
|
|
28
|
+
|
|
29
|
+
```sql
|
|
30
|
+
create table [table_name] (
|
|
31
|
+
id uuid default gen_random_uuid() primary key,
|
|
32
|
+
-- columns match the form fields
|
|
33
|
+
created_at timestamptz default now()
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
-- Enable Row Level Security
|
|
37
|
+
alter table [table_name] enable row level security;
|
|
38
|
+
|
|
39
|
+
-- Policy: anyone can insert (for public forms)
|
|
40
|
+
create policy "Anyone can submit" on [table_name]
|
|
41
|
+
for insert with check (true);
|
|
42
|
+
|
|
43
|
+
-- Policy: only authenticated users can read (for admin)
|
|
44
|
+
create policy "Authenticated users can read" on [table_name]
|
|
45
|
+
for select using (auth.role() = 'authenticated');
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Tell the designer:
|
|
49
|
+
> "Go to your Supabase dashboard → SQL Editor → paste this and run it. That creates your spreadsheet."
|
|
50
|
+
|
|
51
|
+
Or if Supabase CLI is set up, run it directly.
|
|
52
|
+
|
|
53
|
+
### Step 2: Set Up Supabase Client
|
|
54
|
+
|
|
55
|
+
If not already set up, create the Supabase client utilities:
|
|
56
|
+
|
|
57
|
+
**`src/utils/supabase/server.ts`** — for server-side operations:
|
|
58
|
+
```typescript
|
|
59
|
+
import { createServerClient } from '@supabase/ssr'
|
|
60
|
+
import { cookies } from 'next/headers'
|
|
61
|
+
|
|
62
|
+
export async function createClient() {
|
|
63
|
+
const cookieStore = await cookies()
|
|
64
|
+
return createServerClient(
|
|
65
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
66
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
67
|
+
{
|
|
68
|
+
cookies: {
|
|
69
|
+
getAll() { return cookieStore.getAll() },
|
|
70
|
+
setAll(cookiesToSet) {
|
|
71
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
72
|
+
cookieStore.set(name, value, options)
|
|
73
|
+
)
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**`.env.local`** — environment variables:
|
|
82
|
+
```
|
|
83
|
+
NEXT_PUBLIC_SUPABASE_URL=your-project-url
|
|
84
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Tell the designer:
|
|
88
|
+
> "I need two settings from your Supabase dashboard — go to Settings → API and copy the URL and the anon key."
|
|
89
|
+
|
|
90
|
+
### Step 3: Create the Server Action
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
'use server'
|
|
94
|
+
|
|
95
|
+
import { createClient } from '@/utils/supabase/server'
|
|
96
|
+
|
|
97
|
+
export async function save[Name](formData: FormData) {
|
|
98
|
+
const supabase = await createClient()
|
|
99
|
+
|
|
100
|
+
const { error } = await supabase.from('[table_name]').insert({
|
|
101
|
+
// map form fields to columns
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
if (error) {
|
|
105
|
+
return { error: 'Something went wrong. Try again.' }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { success: true }
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Step 4: Connect to Form
|
|
113
|
+
|
|
114
|
+
Add the action to the existing form:
|
|
115
|
+
```tsx
|
|
116
|
+
<form action={save[Name]}>
|
|
117
|
+
{/* existing fields */}
|
|
118
|
+
<button type="submit">Send</button>
|
|
119
|
+
</form>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Add a success message:
|
|
123
|
+
```tsx
|
|
124
|
+
// Show confirmation after submit
|
|
125
|
+
"Thanks! Your message was sent."
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Step 5: Test It
|
|
129
|
+
|
|
130
|
+
> "Try filling out the form and clicking Send. Then check your Supabase dashboard — go to Table Editor → [table_name]. You should see your entry."
|
|
131
|
+
|
|
132
|
+
## Platform Adaptations
|
|
133
|
+
|
|
134
|
+
| Platform | Storage | Approach |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| Web (Next.js) | Supabase | Server actions + Supabase client (above) |
|
|
137
|
+
| Mobile (Expo) | Supabase | Supabase JS client directly |
|
|
138
|
+
| Prototype / quick test | localStorage | Just save to browser storage (no server needed) |
|
|
139
|
+
| WordPress | WP REST API | Custom endpoint + database |
|
|
140
|
+
|
|
141
|
+
## Multiple Forms
|
|
142
|
+
|
|
143
|
+
If the project already has other saved data:
|
|
144
|
+
- Reuse the existing Supabase client (don't create another one)
|
|
145
|
+
- Create a new table for each distinct type of data
|
|
146
|
+
- Create a new server action for each form
|
|
147
|
+
|
|
148
|
+
## Common Issues
|
|
149
|
+
|
|
150
|
+
| Problem | Fix |
|
|
151
|
+
|---|---|
|
|
152
|
+
| Form submits but no data appears | Check Supabase RLS policies — insert policy might be missing |
|
|
153
|
+
| "Missing environment variable" | Check `.env.local` has the Supabase URL and key |
|
|
154
|
+
| Data saves but page doesn't show confirmation | Add success state handling to the form |
|
|
155
|
+
| Duplicate entries | Add a loading/disabled state to the submit button |
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: send-email
|
|
3
|
+
description: "Sends emails from your app. Triggers: 'send email', 'email notification', 'notify me', 'confirmation email', 'send a notification', 'email when', 'alert me', 'send message', 'email the user', 'welcome email', 'thank you email'."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Send Email
|
|
7
|
+
|
|
8
|
+
Sends emails when things happen — confirmations, notifications, welcome messages.
|
|
9
|
+
|
|
10
|
+
## Default: Resend
|
|
11
|
+
|
|
12
|
+
Simplest email API. Generous free tier (100 emails/day). Beautiful templates with React Email.
|
|
13
|
+
|
|
14
|
+
### Step 1: Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install resend
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Step 2: Get API Key
|
|
21
|
+
|
|
22
|
+
> "Go to resend.com, create a free account, and copy your API key. I'll store it securely."
|
|
23
|
+
|
|
24
|
+
Add to `.env.local`:
|
|
25
|
+
```
|
|
26
|
+
RESEND_API_KEY=re_your_api_key_here
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Step 3: Create Send Function
|
|
30
|
+
|
|
31
|
+
`src/utils/email.ts`:
|
|
32
|
+
```typescript
|
|
33
|
+
import { Resend } from 'resend'
|
|
34
|
+
|
|
35
|
+
const resend = new Resend(process.env.RESEND_API_KEY)
|
|
36
|
+
|
|
37
|
+
export async function sendEmail({
|
|
38
|
+
to,
|
|
39
|
+
subject,
|
|
40
|
+
html,
|
|
41
|
+
}: {
|
|
42
|
+
to: string
|
|
43
|
+
subject: string
|
|
44
|
+
html: string
|
|
45
|
+
}) {
|
|
46
|
+
const { error } = await resend.emails.send({
|
|
47
|
+
from: 'Your App <onboarding@resend.dev>',
|
|
48
|
+
to,
|
|
49
|
+
subject,
|
|
50
|
+
html,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
if (error) {
|
|
54
|
+
console.error('Email failed:', error)
|
|
55
|
+
return { success: false }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { success: true }
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Note: `onboarding@resend.dev` is Resend's free testing address. For production, connect a custom domain in Resend dashboard.
|
|
63
|
+
|
|
64
|
+
### Step 4: Connect to an Event
|
|
65
|
+
|
|
66
|
+
Common patterns:
|
|
67
|
+
|
|
68
|
+
**After form submission (notify the designer):**
|
|
69
|
+
```typescript
|
|
70
|
+
'use server'
|
|
71
|
+
|
|
72
|
+
import { createClient } from '@/utils/supabase/server'
|
|
73
|
+
import { sendEmail } from '@/utils/email'
|
|
74
|
+
|
|
75
|
+
export async function saveContact(formData: FormData) {
|
|
76
|
+
const supabase = await createClient()
|
|
77
|
+
const name = formData.get('name') as string
|
|
78
|
+
const email = formData.get('email') as string
|
|
79
|
+
const message = formData.get('message') as string
|
|
80
|
+
|
|
81
|
+
// Save to database
|
|
82
|
+
await supabase.from('contacts').insert({ name, email, message })
|
|
83
|
+
|
|
84
|
+
// Notify you
|
|
85
|
+
await sendEmail({
|
|
86
|
+
to: 'you@youremail.com',
|
|
87
|
+
subject: `New message from ${name}`,
|
|
88
|
+
html: `
|
|
89
|
+
<h2>New contact form submission</h2>
|
|
90
|
+
<p><strong>From:</strong> ${name} (${email})</p>
|
|
91
|
+
<p><strong>Message:</strong></p>
|
|
92
|
+
<p>${message}</p>
|
|
93
|
+
`,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return { success: true }
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Confirmation to the user:**
|
|
101
|
+
```typescript
|
|
102
|
+
// After saving, also email the person who submitted
|
|
103
|
+
await sendEmail({
|
|
104
|
+
to: email,
|
|
105
|
+
subject: 'Thanks for reaching out!',
|
|
106
|
+
html: `
|
|
107
|
+
<h2>Got your message!</h2>
|
|
108
|
+
<p>Hi ${name}, thanks for getting in touch.
|
|
109
|
+
I'll get back to you within 24 hours.</p>
|
|
110
|
+
`,
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Step 5: Test It
|
|
115
|
+
|
|
116
|
+
> "Submit your contact form. Check two things:
|
|
117
|
+
> 1. You should get a notification email
|
|
118
|
+
> 2. The person who submitted should get a confirmation
|
|
119
|
+
>
|
|
120
|
+
> Note: With the free Resend account, emails might go to spam at first. That's normal for testing."
|
|
121
|
+
|
|
122
|
+
## Email Templates
|
|
123
|
+
|
|
124
|
+
Keep emails simple. Designers can style them later.
|
|
125
|
+
|
|
126
|
+
**Minimal template:**
|
|
127
|
+
```html
|
|
128
|
+
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
|
129
|
+
<h2 style="color: #333;">Subject Here</h2>
|
|
130
|
+
<p style="color: #555; line-height: 1.6;">Content here.</p>
|
|
131
|
+
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;" />
|
|
132
|
+
<p style="color: #999; font-size: 12px;">Sent from Your App</p>
|
|
133
|
+
</div>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Common Patterns
|
|
137
|
+
|
|
138
|
+
| When | Email | To |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| Contact form submitted | "New message from [name]" | Designer (admin) |
|
|
141
|
+
| Contact form submitted | "Thanks for reaching out!" | The visitor |
|
|
142
|
+
| Booking created | "New booking: [date/time]" | Designer (admin) |
|
|
143
|
+
| Booking created | "Your booking is confirmed" | The customer |
|
|
144
|
+
| Order placed | "New order #[id]" | Designer (admin) |
|
|
145
|
+
| Account created | "Welcome!" | New user |
|
|
146
|
+
|
|
147
|
+
## Custom Domain (Production)
|
|
148
|
+
|
|
149
|
+
For emails to not go to spam:
|
|
150
|
+
1. Go to Resend dashboard → Domains
|
|
151
|
+
2. Add your domain (e.g., yourbrand.com)
|
|
152
|
+
3. Add the DNS records Resend gives you
|
|
153
|
+
4. Update the `from` address: `'Your Name <hello@yourbrand.com>'`
|
|
154
|
+
|
|
155
|
+
> "Right now emails come from a test address. When you're ready to go live, we'll connect your domain so emails come from you@yourbrand.com."
|
|
156
|
+
|
|
157
|
+
## Common Issues
|
|
158
|
+
|
|
159
|
+
| Problem | Fix |
|
|
160
|
+
|---|---|
|
|
161
|
+
| Email not received | Check spam folder. With free tier, emails often land in spam |
|
|
162
|
+
| "API key invalid" | Check `.env.local` — the key should start with `re_` |
|
|
163
|
+
| Email sends but looks ugly | Use the HTML template above instead of plain text |
|
|
164
|
+
| Rate limited | Free tier is 100/day. Upgrade if needed |
|