# Supabase + Dodo — build a bidding website

SQL source of truth, Dodo for global cards.

This is a copy-paste agent prompt for cloning [ihatereading.lol](https://www.ihatereading.lol): a pay-to-rank board. Database: Supabase (Postgres + RLS). Payments: Dodo Payments.

- HTML: https://www.ihatereading.lol/build/supabase-dodo
- Markdown: https://www.ihatereading.lol/build/supabase-dodo.md
- Playbook index: https://www.ihatereading.lol/build

## Platforms in this stack

- [Next.js + shadcn + Tailwind](https://www.ihatereading.lol/build/platforms/next.md)
- [Supabase](https://www.ihatereading.lol/build/platforms/supabase.md)
- [Dodo Payments](https://www.ihatereading.lol/build/platforms/dodo.md)

## Prompt

```
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Supabase
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Supabase Dashboard: https://supabase.com/dashboard
- API keys: https://supabase.com/docs/guides/api/api-keys
- Next.js guide: https://supabase.com/docs/guides/getting-started/quickstarts/nextjs
- Server-side Next.js: https://supabase.com/docs/guides/auth/server-side/nextjs
- Row Level Security: https://supabase.com/docs/guides/auth/row-level-security
- JS client: https://supabase.com/docs/reference/javascript/introduction
- @supabase/ssr npm: https://www.npmjs.com/package/@supabase/ssr

NPM PACKAGES
- @supabase/supabase-js
- @supabase/ssr

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SUPABASE_URL: Supabase Dashboard → Project Settings → API → Project URL
- NEXT_PUBLIC_SUPABASE_ANON_KEY: Same page → Project API keys → anon / public
- SUPABASE_SERVICE_ROLE_KEY: Same page → service_role (secret). Server only. Bypasses RLS.

SETUP STEPS
1. https://supabase.com/dashboard → New project
2. Project Settings → API → copy URL, anon key, service_role
3. SQL Editor: create listings, bids, pending_checkouts + indexes
4. RLS: public SELECT on live listings; writes only with service_role in Route Handlers

### Dodo Payments
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Dodo Dashboard: https://app.dodopayments.com/
- API keys: https://app.dodopayments.com/developer/api-keys
- Webhooks: https://app.dodopayments.com/developer/webhooks
- Integration guide: https://docs.dodopayments.com/developer-resources/integration-guide
- Checkout sessions: https://docs.dodopayments.com/developer-resources/checkout-sessions
- Webhook docs: https://docs.dodopayments.com/developer-resources/webhooks
- Test vs live mode: https://docs.dodopayments.com/miscellaneous/test-mode-vs-live-mode
- dodopayments npm: https://www.npmjs.com/package/dodopayments
- @dodopayments/nextjs: https://www.npmjs.com/package/@dodopayments/nextjs

NPM PACKAGES
- dodopayments
- @dodopayments/nextjs

API KEYS — name and exactly where to create them
- DODO_PAYMENTS_API_KEY: https://app.dodopayments.com/developer/api-keys → Add API key (Bearer). Server only.
- DODO_PAYMENTS_WEBHOOK_KEY: https://app.dodopayments.com/developer/webhooks → endpoint Overview → signing secret
- DODO_PAYMENTS_PRODUCT_ID: Dashboard → Products → create a one-time Pay What You Want product with $1 minimum
- DODO_PAYMENTS_ENVIRONMENT: Must be test_mode or live_mode. Never the string "live".

SETUP STEPS
1. https://app.dodopayments.com/ → create merchant account
2. Developer → API Keys → DODO_PAYMENTS_API_KEY
3. Products → one-time Pay What You Want, minimum $1 → DODO_PAYMENTS_PRODUCT_ID
4. checkoutSessions.create with product_cart[].amount in CENTS
5. Developer → Webhooks → URL {SITE}/api/webhooks/dodo → DODO_PAYMENTS_WEBHOOK_KEY
6. DODO_PAYMENTS_ENVIRONMENT=test_mode until you go live (then live_mode)
7. Return URL {SITE}/success?checkout_id={pending_id}
8. Also poll GET /api/status — webhooks can arrive after the browser returns

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Supabase (Postgres + RLS)
- Payments: Dodo Payments

ADAPTER RULES
Supabase tables + service-role writes only.
Dodo PWYW checkout (amount in cents), webhook + status poll fallback.
Use a Postgres transaction when applying a successful payment.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_SUPABASE_URL
- NEXT_PUBLIC_SUPABASE_ANON_KEY
- SUPABASE_SERVICE_ROLE_KEY
- DODO_PAYMENTS_API_KEY
- DODO_PAYMENTS_PRODUCT_ID
- DODO_PAYMENTS_WEBHOOK_KEY
- DODO_PAYMENTS_ENVIRONMENT=test_mode

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.
```
