Your graduation lasts a day. The memories should last forever.
A graduation memory capsule. Graduates pay ₦500 once to activate a personal PortalBox; friends, family, lecturers and mentors leave free messages on it, forever.
- Next.js 14 (App Router) + TypeScript + Tailwind CSS + Framer Motion
- Supabase (Postgres + Auth + RLS)
- Paystack (one-time ₦500 charge)
- Vercel (hosting)
app/
page.tsx Landing page
signup/page.tsx Step 1: create account
login/page.tsx Login
create/page.tsx Step 2: graduate profile (name, school, dept, year)
payment/page.tsx ₦500 Paystack checkout
payment/callback/page.tsx Paystack redirect target — verifies + activates
[slug]/page.tsx Public PortalBox (the memory wall)
[slug]/message/page.tsx Visitor "leave a memory" form
dashboard/page.tsx Graduate's private dashboard
api/paystack/initialize Starts a Paystack transaction
api/paystack/verify Verifies a transaction by reference
api/paystack/webhook Paystack server-to-server confirmation (source of truth)
components/ Button, Navbar, Footer, MessageCard, MessageForm, etc.
lib/ Supabase clients, Paystack client, payment helper, utils
supabase/schema.sql Full database schema + Row Level Security policies
- Create a project at supabase.com.
- Go to SQL Editor → New query, paste the contents of
supabase/schema.sql, and run it. This creates theusers,messages, andpaymentstables with Row Level Security already locked down correctly:- Anyone can read a graduate's public profile and messages (needed for the public PortalBox page).
- Anyone can insert a message (visitors never log in).
- Only the graduate can update their own profile or delete their own messages.
- Payments are only readable by the owning graduate; writes happen only through the server using the service role key.
- Go to Authentication → Providers and confirm Email is enabled.
- For the fastest signup → pay → dashboard flow, also go to
Authentication → Settings and turn off "Confirm email". With it
on,
supabase.auth.signUp()won't return a live session, so the graduate would be sent to check their inbox before they could continue to/createand pay — adding friction you don't want for a ₦500 impulse purchase. You can turn confirmation back on later once you care more about verified emails than launch speed.
- For the fastest signup → pay → dashboard flow, also go to
Authentication → Settings and turn off "Confirm email". With it
on,
- Go to Project Settings → API and copy:
Project URL→NEXT_PUBLIC_SUPABASE_URLanon publickey →NEXT_PUBLIC_SUPABASE_ANON_KEYservice_rolekey →SUPABASE_SERVICE_ROLE_KEY(never expose this client-side)
- Create an account at paystack.com.
- Go to Settings → API Keys & Webhooks and copy your test (or live) keys
into
NEXT_PUBLIC_PAYSTACK_PUBLIC_KEYandPAYSTACK_SECRET_KEY. - In the same screen, set your Webhook URL to:
https://yourdomain.com/api/paystack/webhookThis is what reliably activates a PortalBox even if the graduate closes their browser tab right after paying — the/payment/callbackredirect is a fast-path for a good UX, but the webhook is the real source of truth.
Copy .env.local.example to .env.local and fill in the values from steps 3–4:
cp .env.local.example .env.localSet NEXT_PUBLIC_SITE_URL to http://localhost:3000 for local development,
and to your production domain once deployed.
npm install
npm run devVisit http://localhost:3000. Use a Paystack test card to confirm the
full flow end to end: sign up → create profile → pay ₦500 → land on dashboard
→ copy your link → open it in an incognito tab → leave a message → see it
appear on the dashboard.
- Push this project to a GitHub repo.
- Go to vercel.com/new and import the repo.
- Add all six environment variables from
.env.localin the Vercel project settings (setNEXT_PUBLIC_SITE_URLto your real production URL, e.g.https://portalbox.ng). - Deploy.
- Update the Paystack webhook URL (step 4.3) to point at your live domain.
- Switch Paystack from test keys to live keys once you're ready to take real payments, and redeploy.
Point your domain's DNS at Vercel (Vercel's UI walks you through this under
Project → Settings → Domains). Because PortalBox pages are already
path-based (/[slug]), no extra routing work is needed — portalbox.ng/luciid
will just work once the domain is attached.
- Schema run in Supabase, RLS confirmed on
- Test payment completes and dashboard shows "paid"
- Webhook URL set in Paystack and receiving
charge.successevents - Switched to Paystack live keys
- Custom domain attached
- Share your own PortalBox link to get the first real graduate