Skip to content

Commit 4edbdd2

Browse files
chore: onboarding wizard skip school step + migrations docs
Onboarding wizard now skips the school name step if it was already set during school creation. Also adds MIGRATIONS.md documentation for applying migrations via CLI, Management API, or dashboard. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 12cbe4a commit 4edbdd2

3 files changed

Lines changed: 196 additions & 7 deletions

File tree

components/onboarding/onboarding-wizard.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,33 +38,40 @@ interface OnboardingWizardProps {
3838
redirectTo?: string
3939
}
4040

41-
const STEPS = ['welcome', 'school', 'branding', 'payment', 'ready'] as const
42-
type Step = typeof STEPS[number]
41+
const ALL_STEPS = ['welcome', 'school', 'branding', 'payment', 'ready'] as const
42+
type Step = typeof ALL_STEPS[number]
4343

4444
export default function OnboardingWizard({ userId, userName, currentSettings, redirectTo = '/dashboard/admin' }: OnboardingWizardProps) {
4545
const router = useRouter()
4646
const t = useTranslations('onboarding')
47-
const [currentStep, setCurrentStep] = useState<Step>('welcome')
4847
const [isSubmitting, setIsSubmitting] = useState(false)
4948

5049
// Form state
5150
const [schoolName, setSchoolName] = useState(currentSettings.site_name?.value || '')
5251
const [schoolDescription, setSchoolDescription] = useState(currentSettings.site_description?.value || '')
52+
53+
// Skip the school step if the name was already set during school creation
54+
const hasSchoolName = !!currentSettings.site_name?.value
55+
const STEPS = hasSchoolName
56+
? ALL_STEPS.filter(s => s !== 'school')
57+
: ALL_STEPS
58+
59+
const [currentStep, setCurrentStep] = useState<Step>('welcome')
5360
const [isConnectingStripe, setIsConnectingStripe] = useState(false)
5461

55-
const stepIndex = STEPS.indexOf(currentStep)
62+
const stepIndex = STEPS.indexOf(currentStep as any)
5663

5764
function goNext() {
5865
const nextIndex = stepIndex + 1
5966
if (nextIndex < STEPS.length) {
60-
setCurrentStep(STEPS[nextIndex])
67+
setCurrentStep(STEPS[nextIndex] as Step)
6168
}
6269
}
6370

6471
function goBack() {
6572
const prevIndex = stepIndex - 1
6673
if (prevIndex >= 0) {
67-
setCurrentStep(STEPS[prevIndex])
74+
setCurrentStep(STEPS[prevIndex] as Step)
6875
}
6976
}
7077

docs/MIGRATIONS.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Database Migrations Manual
2+
3+
## Overview
4+
5+
This project uses Supabase migrations stored in `supabase/migrations/`. Each file is a timestamped SQL script that runs in order against the database.
6+
7+
## Creating a New Migration
8+
9+
```bash
10+
supabase migration new <descriptive_name>
11+
# Creates: supabase/migrations/<timestamp>_<descriptive_name>.sql
12+
```
13+
14+
Edit the generated file with your SQL. Always make migrations **idempotent** where possible:
15+
- Use `IF NOT EXISTS` / `IF EXISTS` for DDL
16+
- Use `ON CONFLICT DO NOTHING` or `DO UPDATE` for seed data
17+
- Wrap risky operations in `DO $$ ... END $$` blocks with exception handling
18+
19+
## Applying Migrations
20+
21+
### Option 1: Supabase CLI (preferred)
22+
23+
```bash
24+
supabase db push --password '<DB_PASSWORD>'
25+
```
26+
27+
This connects via the Supabase connection pooler and applies all pending migrations. If it prompts for confirmation, type `Y`.
28+
29+
**If the CLI times out** (common on some networks where port 5432 is blocked), use Option 2.
30+
31+
### Option 2: Management API (fallback)
32+
33+
When the CLI can't connect, push migrations via the Supabase Management API:
34+
35+
```bash
36+
# 1. Get your access token (stored in macOS keychain by the CLI)
37+
security find-generic-password -l "supabase" -w | base64 -d
38+
39+
# 2. Apply a migration
40+
TOKEN="<your_access_token>"
41+
PROJECT="tcqqnjfwmbfwcyhafbbt"
42+
43+
node -e "
44+
const fs = require('fs');
45+
const sql = fs.readFileSync('supabase/migrations/<MIGRATION_FILE>.sql', 'utf8');
46+
const fullSql = sql + \"\nINSERT INTO supabase_migrations.schema_migrations (version, name) VALUES ('<VERSION>', '<NAME>');\";
47+
process.stdout.write(JSON.stringify({query: fullSql}));
48+
" | curl -s -X POST "https://api.supabase.com/v1/projects/$PROJECT/database/query" \
49+
-H "Authorization: Bearer $TOKEN" \
50+
-H "Content-Type: application/json" \
51+
-d @-
52+
```
53+
54+
Where:
55+
- `<MIGRATION_FILE>` — full filename (e.g. `20260405215804_fix_missing_profile_trigger.sql`)
56+
- `<VERSION>` — the timestamp prefix (e.g. `20260405215804`)
57+
- `<NAME>` — the descriptive name (e.g. `fix_missing_profile_trigger`)
58+
59+
**Important:** Always append the `INSERT INTO supabase_migrations.schema_migrations` statement so the CLI knows the migration was applied.
60+
61+
### Option 3: Supabase Dashboard
62+
63+
1. Go to **SQL Editor** in the Supabase dashboard
64+
2. Paste the migration SQL
65+
3. Run it
66+
4. Then run the migration record insert separately:
67+
```sql
68+
INSERT INTO supabase_migrations.schema_migrations (version, name)
69+
VALUES ('<VERSION>', '<NAME>');
70+
```
71+
72+
## Checking Migration Status
73+
74+
### What's applied remotely?
75+
76+
```bash
77+
# Via CLI
78+
supabase db remote list --password '<DB_PASSWORD>'
79+
80+
# Via Management API
81+
TOKEN="<your_access_token>"
82+
PROJECT="tcqqnjfwmbfwcyhafbbt"
83+
84+
node -e "
85+
process.stdout.write(JSON.stringify({query: 'SELECT version, name FROM supabase_migrations.schema_migrations ORDER BY version DESC LIMIT 10;'}));
86+
" | curl -s -X POST "https://api.supabase.com/v1/projects/$PROJECT/database/query" \
87+
-H "Authorization: Bearer $TOKEN" \
88+
-H "Content-Type: application/json" \
89+
-d @-
90+
```
91+
92+
### What's pending locally?
93+
94+
Compare local files against remote records:
95+
96+
```bash
97+
# Local migration versions
98+
ls supabase/migrations/*.sql | sed 's|.*/||' | sed 's/_.*//'
99+
100+
# Remote versions (use one of the methods above)
101+
```
102+
103+
Any local version not in the remote list needs to be applied.
104+
105+
## Running the Production Seed
106+
107+
The minimal production seed creates platform plans, default tenant, admin user, gamification data, and course categories:
108+
109+
```bash
110+
# Via Management API
111+
node -e "
112+
const fs = require('fs');
113+
const sql = fs.readFileSync('supabase/seed-prod.sql', 'utf8');
114+
process.stdout.write(JSON.stringify({query: sql}));
115+
" | curl -s -X POST "https://api.supabase.com/v1/projects/$PROJECT/database/query" \
116+
-H "Authorization: Bearer $TOKEN" \
117+
-H "Content-Type: application/json" \
118+
-d @-
119+
```
120+
121+
**Warning:** `seed-prod.sql` starts by truncating ALL tables including `auth.users`. Only run this on a fresh or expendable database.
122+
123+
After seeding, log in with: `owner@e2etest.com` / `password123`
124+
125+
## Troubleshooting
126+
127+
### CLI connection timeout
128+
129+
The Supabase CLI connects via the connection pooler on port 5432. If your network blocks this port, you'll see:
130+
131+
```
132+
failed to connect to postgres: tls error (read tcp ... i/o timeout)
133+
```
134+
135+
**Fix:** Use the Management API method (Option 2 above).
136+
137+
### FK constraint violations
138+
139+
If a migration adding a foreign key fails with `violates foreign key constraint`, there's orphan data. Fix it before retrying:
140+
141+
```sql
142+
-- Find orphans (example: courses.author_id → profiles.id)
143+
SELECT c.course_id, c.author_id
144+
FROM courses c
145+
LEFT JOIN profiles p ON p.id = c.author_id
146+
WHERE p.id IS NULL;
147+
148+
-- Option A: Create missing profiles
149+
INSERT INTO profiles (id, full_name, avatar_url)
150+
SELECT u.id, u.raw_user_meta_data->>'full_name', ''
151+
FROM auth.users u
152+
LEFT JOIN profiles p ON p.id = u.id
153+
WHERE p.id IS NULL;
154+
155+
-- Option B: Delete orphan rows
156+
DELETE FROM courses WHERE author_id NOT IN (SELECT id FROM profiles);
157+
```
158+
159+
### Migration version mismatch
160+
161+
If the remote `schema_migrations` table has different version numbers than local filenames (e.g. migrations were applied via dashboard with auto-generated timestamps):
162+
163+
```sql
164+
-- Update the remote record to match the local filename
165+
UPDATE supabase_migrations.schema_migrations
166+
SET version = '<LOCAL_VERSION>'
167+
WHERE version = '<REMOTE_VERSION>' AND name = '<NAME>';
168+
```
169+
170+
### Prepared statement errors (port 6543)
171+
172+
If you try the session pooler (port 6543) and get `prepared statement already exists`, use port 5432 or the Management API instead. The session pooler doesn't work well with the Supabase CLI's connection handling.
173+
174+
## Project Details
175+
176+
| Key | Value |
177+
|-----|-------|
178+
| Project ref | `tcqqnjfwmbfwcyhafbbt` |
179+
| Region | US East 1 |
180+
| Pooler host | `aws-0-us-east-1.pooler.supabase.com` |
181+
| Direct host | `db.tcqqnjfwmbfwcyhafbbt.supabase.co` |
182+
| Dashboard | https://supabase.com/dashboard/project/tcqqnjfwmbfwcyhafbbt |

next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./.next/dev/types/routes.d.ts";
3+
import "./.next/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

0 commit comments

Comments
 (0)