Skip to content

feat: Techne v1 enrollment platform#2

Open
unforced wants to merge 3 commits into
mainfrom
feat/v1-enrollment-platform
Open

feat: Techne v1 enrollment platform#2
unforced wants to merge 3 commits into
mainfrom
feat/v1-enrollment-platform

Conversation

@unforced

Copy link
Copy Markdown
Contributor

Summary

  • Full Next.js 15 application replacing the static index.html
  • Students can browse cohorts, enroll via Stripe, and access session recordings in a protected members area
  • Admins manage sessions and resources through an authenticated dashboard

What's included

Auth + enrollment

  • Magic link sign-in (no passwords) via Supabase
  • Stripe checkout with allow_promotion_codes: true for sliding scale
  • Post-payment auth gap solved: /enroll/[cohort]/success retrieves Stripe session email and sends a magic link — never redirects directly to /cohort
  • Webhook idempotency via stripe_session_id unique constraint + pre-insert check

Protected routes

  • /cohort — sessions (YouTube embeds), resources (grouped by type), cohort directory
  • /cohort/profile — update display name, toggle directory visibility
  • /admin — manage sessions (add/edit/delete, publish/draft toggle) and resources

Infrastructure

  • 3 Supabase migrations: schema → RLS policies → auto-profile trigger
  • middleware.ts guards /cohort and /admin; admin role check via profiles.is_admin
  • Lazy Stripe initialization to avoid build-time errors

Deploy checklist

  1. Create Supabase project → run migrations 001, 002, 003 in order → run seed.sql
  2. Create Stripe product + price for Cohort 2 → update stripe_price_id in DB
  3. Create Vercel project pointing at RegenHub-Boulder/techne.institute, main branch
  4. Set all env vars in Vercel (see .env.local.example)
  5. Configure Stripe webhook → https://techne.institute/api/webhooks/stripe
  6. Set admin role: update public.profiles set is_admin = true where id = (...)
  7. Update DNS CNAME to Vercel

Build output

Route (app)                    Size     First Load JS
○ /                            185 B    109 kB
ƒ /admin                       3 kB     162 kB
ƒ /cohort                      185 B    109 kB
ƒ /cohort/profile              1.38 kB  161 kB
ƒ /enroll/[cohort]             691 B    106 kB
○ /signin                      1.26 kB  160 kB
○ /writing                     185 B    109 kB

Compound Engineered 🤖 Generated with Claude Code

unforced and others added 3 commits February 23, 2026 17:53
Full web application replacing static index.html. Students can browse
cohorts, enroll via Stripe, receive a magic link, and access session
recordings in a protected members area. Admins manage sessions and
resources via an authenticated dashboard.

- Next.js 15 App Router with hand-crafted CSS (no Tailwind)
- Supabase auth (magic link), Postgres, Row-Level Security
- Stripe checkout with promo codes + idempotent webhook handler
- Post-payment auth gap solved: success page sends magic link via email
- Protected /cohort and /admin routes via middleware
- Admin role via profiles.is_admin boolean

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Security:
- Fix is_admin privilege escalation in RLS update policy (with check constraint)
- Add get_auth_user_id_by_email RPC to replace listUsers() pagination issue
- Move magic link send from unauthenticated success page into webhook handler
- Return 500 on enrollment failure so Stripe retries (was incorrectly returning 200)
- Exclude api/webhooks from middleware (prevents Supabase session overhead on webhook)

Correctness:
- Fix show_in_directory privacy filter applied at DB level in cohort directory query
- Fix .single() to .limit(1) to handle multi-cohort students without 500 error
- Add safeHostname() helper to prevent new URL() crash on malformed admin resource URLs

Performance / indexes:
- Migration 004: indexes on enrollments(user_id), enrollments(cohort_id),
  sessions(cohort_id), sessions(cohort_id, published_at), resources(cohort_id),
  profiles(id, is_admin)

Cleanup:
- Simplify lib/stripe.ts: remove Proxy pattern, export only getStripe()
- Update checkout route to use getStripe() instead of named stripe export
- Add X-Content-Type-Options and Referrer-Policy security headers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Builds tab where enrolled students can share what they're
building. Admins can mark projects as featured. Builds are visible
to all enrolled students within the cohort.

- Migration 005: projects table with RLS (one project/student/cohort,
  featured flag locked from student updates)
- ProjectSubmitForm: upsert with onConflict, collapsed when submitted
- ProjectCard: display component with featured badge
- ProjectAdminTable: toggle featured flag from admin dashboard
- cohort/page.tsx: Builds tab with submit form + projects grid
- admin/page.tsx + AdminDashboard: Builds tab for admin management
- globals.css: project card styles

Bug fixes:
- Sign In button invisible (black on black) — moved outside nav ul
- Programs page error on load — force-dynamic instead of revalidate

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant