Skip to content

ankitnegi-dev/parforcharity

Repository files navigation

ParForCharity

Play your round. Win big. Give back.

A subscription-driven golf scoring + charity fundraising + monthly prize draw platform. Golfers subscribe, log scores, pick a charity, and enter an automated monthly draw with real cash prizes.

Live: https://parforcharity.vercel.app


Tech Stack

Layer Technology
Framework Next.js 14 (App Router)
Database Supabase (PostgreSQL + RLS)
Auth Supabase Auth + custom JWT hook
Payments Stripe Checkout + Webhooks
Deployment Vercel (region: lhr1)
Styling Tailwind CSS + CVA
Language TypeScript (strict mode)
Validation Zod

Features

  • Subscription system — Monthly / yearly plans via Stripe Checkout
  • Score tracking — Rolling 5-score window; future dates and scores >1 year old rejected
  • Charity directory — Subscribers choose a charity and contribution % (10–100%)
  • Monthly prize draw — Automated draw engine with random and algorithmic modes
  • Prize tiers — 5 matches (40% + jackpot), 4 matches (35%), 3 matches (25%)
  • Jackpot rollover — Rolls over to next month if no 5-match winner
  • Admin panel — Draw management, snapshot, publish, winner verification
  • JWT role gating — Admin routes protected via custom Supabase access token hook

Project Structure

golf-charity-platform/
├── app/
│   ├── (auth)/            # Login, Signup pages
│   ├── (dashboard)/       # Subscriber dashboard (5 modules)
│   ├── (admin)/           # Admin panel
│   ├── api/               # Route handlers (scores, draws, charities, Stripe)
│   ├── charities/         # Public charity listing
│   └── page.tsx           # Homepage
├── components/
│   ├── auth/              # LoginForm, SignupForm
│   ├── charities/         # CharityPicker
│   ├── dashboard/         # DashboardNav
│   ├── scores/            # ScoreEntry, ScoreList
│   └── ui/                # Button, Card, Badge, Input
├── lib/
│   ├── draws/             # Draw engine + snapshot
│   ├── stripe/            # Stripe client + webhook helpers
│   ├── supabase/          # Browser, server, middleware clients
│   └── utils.ts           # Shared utilities
├── supabase/
│   └── migrations/        # 4 SQL migration files
└── types/                 # TypeScript types for all 11 DB tables

Getting Started

1. Clone the repo

git clone https://github.com/ankitnegi-dev/parforcharity.git
cd parforcharity/golf-charity-platform
npm install

2. Set environment variables

Copy .env.example to .env.local and fill in your values:

cp .env.example .env.local
Variable Where to find it
NEXT_PUBLIC_SUPABASE_URL Supabase → Project Settings → API
NEXT_PUBLIC_SUPABASE_ANON_KEY Supabase → Project Settings → API
SUPABASE_SERVICE_ROLE_KEY Supabase → Project Settings → API (server-only)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY Stripe → Developers → API keys
STRIPE_SECRET_KEY Stripe → Developers → API keys
STRIPE_WEBHOOK_SECRET Stripe → Webhooks → Signing secret
STRIPE_MONTHLY_PRICE_ID Stripe → Products → Monthly plan Price ID
STRIPE_YEARLY_PRICE_ID Stripe → Products → Yearly plan Price ID
NEXT_PUBLIC_APP_URL Your deployed URL (e.g. https://parforcharity.vercel.app)

3. Run database migrations

Run these in order in Supabase Dashboard → SQL Editor:

supabase/migrations/001_initial_schema.sql   ← 11 tables + triggers
supabase/migrations/002_rls_policies.sql     ← Row-level security + is_admin()
supabase/migrations/003_charity_split.sql    ← charity_selections + charity_donations
supabase/migrations/004_jwt_role_hook.sql    ← JWT role embedding hook

After running 004, enable the hook in Supabase Dashboard → Authentication → Hooks → custom_access_token_hook.

4. Run locally

npm run dev

Open http://localhost:3000.


Deployment (Vercel)

  1. Push to GitHub
  2. Go to vercel.comAdd New Project → import the repo
  3. Set Root Directory to golf-charity-platform
  4. Add all environment variables from Step 2 above
  5. Set Node.js version to 20.x
  6. Click Deploy

Stripe Webhook (post-deploy)

Create a webhook endpoint in Stripe Dashboard → Developers → Webhooks:

  • URL: https://your-domain.vercel.app/api/stripe/webhook
  • Events: checkout.session.completed, customer.subscription.updated, customer.subscription.deleted, payment_intent.succeeded, payment_intent.payment_failed

Copy the signing secret into the STRIPE_WEBHOOK_SECRET env var and redeploy.


Creating an Admin User

Run in Supabase → SQL Editor (replace with real email):

UPDATE auth.users
SET raw_app_meta_data = raw_app_meta_data || '{"role": "admin"}'
WHERE email = 'your-admin@email.com';

UPDATE public.users
SET role = 'admin'
WHERE email = 'your-admin@email.com';

The user must sign out and back in for the new JWT claim to take effect. Admin panel is at /admin.


Seeding Sample Charities

INSERT INTO public.charities (name, description, website_url, is_active) VALUES
  ('British Heart Foundation', 'Fighting heart and circulatory diseases.', 'https://www.bhf.org.uk', true),
  ('Macmillan Cancer Support', 'Supporting people living with cancer.', 'https://www.macmillan.org.uk', true),
  ('Age UK', 'Improving later life for older people.', 'https://www.ageuk.org.uk', true),
  ('RNLI', 'Saving lives at sea since 1824.', 'https://rnli.org', true),
  ('Mind', 'Mental health support and advocacy.', 'https://www.mind.org.uk', true);

Stripe Test Cards

Card Use
4242 4242 4242 4242 Successful payment
4000 0000 0000 9995 Declined (insufficient funds)
4000 0025 0000 3155 3D Secure required

Expiry: any future date. CVC: any 3 digits.


Draw Engine

The draw engine lives in lib/draws/engine.ts and supports two modes:

  • Random — Fisher-Yates selection from a pool of 1–45
  • Algorithmic — Frequency-weighted selection based on subscriber score history

Prize pool split per PRD §08:

Tier Match Pool share
Jackpot 5 numbers 40% + accumulated jackpot
Second 4 numbers 35%
Third 3 numbers 25%

If no 5-match winner, the jackpot rolls over to the following month.


Known Post-Launch Actions

Priority Action
🔴 Critical Rotate Supabase service role key (was exposed during development)
🟡 Before first real draw Replace Math.random() with crypto.randomInt() in draw engine
🟡 Recommended Replace is_admin() RLS helper with auth.jwt() ->> 'role' = 'admin'
🟢 Nice to have Add security headers (CSP, HSTS) to next.config.ts

License

Private — all rights reserved. Built for the ParForCharity trainee assignment, March 2026.

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors