尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
⚠ This README is outdated. The schema, file structure, and component names below reflect the early project state before the ubiquitous-language rename. For the current schema, routes, environment variables, and component tree see references/PROJECT.md (the canonical reference).檢視完整 README ↗⚠ This README is outdated. The schema, file structure, and component names below reflect the early project state before the ubiquitous-language rename. For the current schema, routes, environment variables, and component tree see
references/PROJECT.md(the canonical reference).
A charitable polling platform where guests share their favourite things while pledging donations to charity. Organisers create favpolls for special occasions — memorials, birthdays, retirements, weddings — each with a customised poll question. Guests pledge money and split it across their favourite answers, building a permanent record of what people love most while raising funds for good causes.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| Language | TypeScript 5 |
| Styling | Tailwind CSS v4, shadcn/ui |
| Auth | Clerk |
| Database | Supabase (PostgreSQL) |
| Payments | Stripe |
| Resend | |
| Package manager | pnpm |
app/
api/
stripe/payment-intent/ POST — creates Stripe PaymentIntent
webhooks/clerk/ POST — syncs Clerk users to Supabase
events/
new/ Create event (canvas form)
[id]/ View event (public or private)
[id]/edit/ Edit event (organiser only)
[id]/manage/ Organiser dashboard
pledges/
withdraw/ Guest pledge withdrawal (token-based)
rankings/ All-time global favourites
topics/[id]/ Per-topic leaderboard with dual ranking toggle
sign-in/, sign-up/ Clerk auth pages
components/
canvas/ Sub-components used only by event-canvas
canvas-sidebar.tsx Charity, date, privacy, shared fund settings
poll-editor.tsx Topic picker and poll framing editor
share-screen.tsx Post-publish share link screen
topic-picker.tsx Browse and select topics by category
event-canvas/ Full create/edit form
index.tsx JSX — occasion tabs, hero, poll editors, sidebar
use-canvas.ts All form state, validation, and submit logic
utils.ts CanvasState type, newPoll factory, MAX_CHARITIES
event-content/ Event view page composition
index.tsx JSX — two-column layout wiring all sub-components
use-event-content.ts pledgeAmount, pollSelections, addItemHandler
pledge-card/ Pledge confirmation card (right sidebar)
index.tsx JSX — amount input, breakdown, confirm button
use-pledge.ts All pledge state, validation, and submit handlers
amount-input.tsx £-prefixed numeric input
amount-presets.tsx Quick-select amount buttons
pledge-breakdown.tsx Itemised cost breakdown
utils.ts GBP formatter, fund colour constants, formatCharityLabel
poll-section/ Individual poll with pledge/results toggle
index.tsx JSX — ranking toggle, RankingList, PledgePanel
use-poll-section.ts view state, rankingView state, hasPledged transition
ranking-list/ Sorted ranking bars with live updates
index.tsx JSX — ordered list with animated bars
use-ranking-items.ts Supabase real-time subscription and re-sort logic
utils.ts rankItems, formatAmount, RankedItem type
ui/ shadcn/ui primitives
button.tsx, tabs.tsx, popover.tsx, toggle.tsx, …
charity-banner.tsx Charity logos with per-charity raised amount
event-hero.tsx Person photo, name, years, bio (view + edit modes)
pledge-panel.tsx Item selection chips and allocation display
stripe-checkout.tsx Stripe Elements payment modal
pot-banner.tsx Shared donation pot display
countdown.tsx Live closing countdown
home-carousel.tsx Homepage event showcase
lib/
email.ts Resend — pledge confirmation emails
occasions.ts OCCASION_LABELS, OCCASION_PLACEHOLDERS
supabase/ Admin and browser Supabase clients
utils.ts cn() and shared utilities
types/
index.ts All TypeScript types
scripts/
seed.ts Populates charities and topics
persons — the person being celebrated
| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| name | text | |
| bio | text | |
| birth_year | int | |
| death_year | int | |
| photo_url | text | |
| created_by | text | Clerk user ID |
events — the main event record
| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| person_id | uuid | FK → persons |
| occasion | text | memorial / birthday / retirement / wedding / other |
| created_by | text | Clerk user ID |
| closes_at | timestamptz | |
| is_private | bool |
event_charities — up to 3 charities per event
| Column | Type | Notes |
|---|---|---|
| event_id | uuid | FK → events |
| charity_id | uuid | FK → charities |
| display_order | int |
charities
| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| name | text | |
| description | text | |
| logo_url | text | |
| registered_number | text |
topics — poll questions (curated or organiser-created)
| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| title | text | e.g. "Favourite biscuit" |
| is_finite | bool | finite = fixed options; infinite = open-ended |
| created_by | text |
topic_items — options within a topic
| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| topic_id | uuid | FK → topics |
| label | text | e.g. "Custard Cream" |
| is_master | bool | curated vs. user-added |
| all_time_pledged | numeric | running total across all events |
| all_time_count | int | times selected across all events |
event_polls — a topic attached to an event with optional personal framing
| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| event_id | uuid | FK → events |
| topic_id | uuid | FK → topics |
| personal_framing | text | e.g. "Belinda's favourite was always the Lake District — what's yours?" |
| personal_quote | text |
event_poll_items — curated item selection for infinite topics
| Column | Type | Notes |
|---|---|---|
| event_poll_id | uuid | FK → event_polls |
| topic_item_id | uuid | FK → topic_items |
| is_guest_added | bool | |
| added_by | text | Clerk user ID |
pledges
| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| event_poll_id | uuid | FK → event_polls |
| clerk_user_id | text | null for guest pledges |
| guest_email | text | null for signed-in pledges |
| guest_token | uuid | one-time withdrawal token; nulled after use |
| total_amount | numeric | in GBP |
| fee | numeric | 3% processing fee |
| withdrawn_at | timestamptz | null if active |
Check constraint: clerk_user_id IS NOT NULL OR guest_email IS NOT NULL
pledge_allocations — how a pledge is split across items
| Column | Type | Notes |
|---|---|---|
| pledge_id | uuid | FK → pledges |
| topic_item_id | uuid | FK → topic_items |
| amount | numeric |
event_pots — optional shared donation pool
| Column | Type | Notes |
|---|---|---|
| event_id | uuid | FK → events |
| total_deposited | numeric | |
| total_allocated | numeric |
pot_allocations — amounts assigned to individual guests
| Column | Type | Notes |
|---|---|---|
| pot_id | uuid | FK → event_pots |
| allocated_to | text | Clerk user ID |
| amount | numeric |
Finite topics have a fixed, pre-curated list of options (e.g. colours, seasons). All items come from topic_items where is_master = true.
Infinite topics have an open-ended list (e.g. films, songs, holiday destinations). Organisers curate a starting set when setting up the poll; guests can add their own during pledging. Items are stored in event_poll_items joined to topic_items.
Create .env.local at the project root:
# Clerk — https://dashboard.clerk.com
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
CLERK_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
# Supabase — https://supabase.com/dashboard
NEXT_PUBLIC_SUPABASE_URL=https://...supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...
# Stripe — https://dashboard.stripe.com
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
# Resend — https://resend.com
RESEND_API_KEY=re_...
[email protected]
NEXT_PUBLIC_BASE_URL=https://yourdomain.com
# Install dependencies
pnpm install
# Start the dev server (Turbopack)
pnpm dev
App runs at http://localhost:3000.
The seed script populates the initial charity and topic data:
pnpm seed
This inserts:
Run seed once after setting up the database. Re-running is safe (uses upsert).
Clerk handles all auth. Protected routes call auth() from @clerk/nextjs/server and redirect to /sign-in if there is no session.
Webhook sync — POST /api/webhooks/clerk receives user.created, user.updated, and user.deleted events from Clerk and mirrors the user record into Supabase. Configure the webhook endpoint in the Clerk dashboard pointing to /api/webhooks/clerk.
POST /api/stripe/payment-intent with the amount in GBPPaymentIntent and returns the clientSecretcreatePledge server action records the pledge and allocations in Supabase and updates all_time_pledged on the relevant topic_itemsA 3% processing fee is added on top of the pledged amount and shown to the guest before confirmation.
pnpm build # Production build
pnpm start # Start production server
pnpm lint # ESLint
pnpm typecheck # tsc --noEmit
pnpm format # Prettier (all .ts/.tsx files)