Seasonal, emotionally engaging web app where couples upload a photo, it becomes a shuffled puzzle behind a paywall, and the partner solves it to reveal the image + a love message. Nigeria-focused MVP.
- Next.js 16 (App Router, TypeScript)
- Tailwind CSS v4 + Framer Motion
- Drag & Drop: @dnd-kit/core + @dnd-kit/sortable
- Puzzle rendering: HTML5 Canvas (client-side slicing only)
- Database & Storage: Supabase
- Payments: Paystack (amounts in kobo)
- Email: Resend
- Image compression: Sharp
- Sender uploads a couple photo and customizes the puzzle.
- Image is compressed and stored in Supabase Storage.
- Puzzle record is created with a shuffled tile order.
- Sender pays via Paystack.
- Receiver opens the puzzle, solves it, and reveals the image + message.
- 24-hour expiration starts on first open.
Install dependencies:
npm installRun the dev server:
npm run devCreate a .env.local using .env.example as a template:
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
SUPABASE_SERVICE_ROLE_KEY
PAYSTACK_SECRET_KEY
NEXT_PUBLIC_PAYSTACK_PUBLIC_KEY
NEXT_PUBLIC_APP_URL
RESEND_API_KEY
CRON_SECRET
Run supabase-setup.sql in your Supabase SQL editor. It creates:
puzzlestablemagic_linkstablepaymentstable (for tracking Paystack references)- RLS policies and indexes
Also create a storage bucket named puzzle-images with:
- Public access
- 5MB file size limit
- Allowed types: JPEG, PNG, WebP
npm run dev— Start dev servernpm run build— Build for productionnpm run start— Start production servernpm run lint— Lint the codebasenpm run test— Run tests
-
Create flow
- Upload image (<= 5MB, JPEG/PNG/WebP)
- Choose difficulty
- Add message + sender name
- Optional reveal date
- Paystack redirect works
-
Payment verification
- Webhook activates puzzle (primary)
- Callback activates puzzle if webhook fails (fallback)
- Idempotent updates (no double activation)
-
Puzzle solving
- Puzzle opens only when
active - Reveal date gate works
- First open starts 24-hour timer
- Tile swapping works and completion is detected
- Completion modal shows image + message
- Confetti triggers
- Puzzle opens only when
-
Expiration
- Puzzle expires after 24h from first open
expire-puzzlescron marks opened + expired
-
Dashboard
- Magic link email sent
- Auth cookie set
- Puzzles list displays
- No user accounts; email-based magic links only.
- Tile images are never stored server-side; only the full image is stored.
- Prices are hard-coded and stored in kobo.
- Create a Supabase project and run
supabase-setup.sql. - Create storage bucket
puzzle-imageswith the limits described above. - Configure environment variables in Vercel (see Env Vars section).
- Set Paystack webhook URL to
https://<your-domain>/api/payment/verify. - Set cron to call
https://<your-domain>/api/cron/expire-puzzleswithAuthorization: Bearer <CRON_SECRET>.