This site is one vertical: best reads. The product underneath is a pay-to-rank board. Same engine works for best SaaS, best YouTube video, best Mac app, best CMS — you only change the noun, the rules, and the theme.
The idea
Attention is ranked by money, in public. No accounts. No likes. The listing with the most paid bids sits on top until someone pays more. That is the whole loop.
01
Someone drops a thing
A URL, an app, a video, a CMS — whatever the vertical is. You preview it so the board looks real.
02
They bid money
Minimum $1. The input can start at the current #1 bid. They can type any amount at or above the floor.
03
They pay
You create a pending checkout, send them to Stripe, Polar, or Dodo, then fulfill only after payment succeeds.
04
The board re-ranks
Rank is cumulative. A second person can bid on the same listing and the total stacks. Highest total stays first.
Rules that make it work
Minimum bid $1. Store money as integer cents.
Same URL = same listing. New submit of a duplicate must stack, not clone.
Fulfill on the server. Webhook plus a success-page poll so a missed webhook still ranks.
Idempotent payments. One payment_id creates one bid.
Increment totals in a transaction. Do not read-then-write the rank.
Vertical rules (block videos, allow only Product Hunt, etc.) sit in config — not in the payment code.
Every prompt below assumes Next.js App Router, TypeScript, Tailwind, and shadcn/ui. Paste it into Cursor, Claude, or Codex and let the agent scaffold the repo.
Keys and official links
Each prompt includes these dashboards so the agent (and you) know exactly where to create API keys, products, and webhooks.
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
### Firebase / Firestore
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Firebase Console: https://console.firebase.google.com/
- Create a web app: https://firebase.google.com/docs/web/setup
- Firestore: https://firebase.google.com/docs/firestore
- Firestore security rules: https://firebase.google.com/docs/firestore/security/get-started
- Admin / service account: https://firebase.google.com/docs/admin/setup
- firebase npm: https://www.npmjs.com/package/firebase
- firebase-admin npm: https://www.npmjs.com/package/firebase-admin
NPM PACKAGES
- firebase
- firebase-admin
API KEYS — name and exactly where to create them
- NEXT_PUBLIC_FIREBASE_API_KEY: Firebase Console → Project settings → Your apps → Web app → apiKey
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: Same page → authDomain
- NEXT_PUBLIC_FIREBASE_PROJECT_ID: Same page → projectId
- NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: Same page → storageBucket
- NEXT_PUBLIC_FIREBASE_APP_ID: Same page → appId
- FIREBASE_SERVICE_ACCOUNT_JSON: Project settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).
SETUP STEPS
1. https://console.firebase.google.com/ → Create project
2. Build → Firestore Database → Create (test mode only for local, then lock rules)
3. Project settings (gear) → General → Your apps → Add web app → copy the firebaseConfig object
4. Project settings → Service accounts → Generate new private key → FIREBASE_SERVICE_ACCOUNT_JSON
5. Never put the service account in NEXT_PUBLIC_*
### Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards
NPM PACKAGES
- stripe
API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
SETUP STEPS
1. https://dashboard.stripe.com → sign up, stay in Test mode
2. Developers → API keys → STRIPE_SECRET_KEY
3. Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
4. Success URL: {SITE}/success?checkout_id={pending_id}
5. Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
6. Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
7. Test card: 4242 4242 4242 4242
---
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: Firebase Firestore
- Payments: Stripe Checkout (Pay What You Want / adaptive pricing)
ADAPTER RULES
Firestore collections: listings, bids, pending_checkouts.
Use firebase-admin in API routes for all writes. Client may read listings for the live board.
Stripe: checkout.sessions.create with mode=payment, line_items price_data unit_amount = bid_amount_cents, metadata.pending_id.
Webhook: checkout.session.completed. Fulfill from session.metadata.pending_id.
Success URL: {SITE}/success?checkout_id={pending_id}
Idempotency key = Stripe session.id / payment_intent.
ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_FIREBASE_API_KEY
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
- NEXT_PUBLIC_FIREBASE_PROJECT_ID
- NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET
- NEXT_PUBLIC_FIREBASE_APP_ID
- FIREBASE_SERVICE_ACCOUNT_JSON
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET
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.
POLAR_PRODUCT_IDProducts → create a one-time product → copy the product id
POLAR_SERVEROptional. sandbox vs production. See Polar Next.js guide.
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
### Firebase / Firestore
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Firebase Console: https://console.firebase.google.com/
- Create a web app: https://firebase.google.com/docs/web/setup
- Firestore: https://firebase.google.com/docs/firestore
- Firestore security rules: https://firebase.google.com/docs/firestore/security/get-started
- Admin / service account: https://firebase.google.com/docs/admin/setup
- firebase npm: https://www.npmjs.com/package/firebase
- firebase-admin npm: https://www.npmjs.com/package/firebase-admin
NPM PACKAGES
- firebase
- firebase-admin
API KEYS — name and exactly where to create them
- NEXT_PUBLIC_FIREBASE_API_KEY: Firebase Console → Project settings → Your apps → Web app → apiKey
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: Same page → authDomain
- NEXT_PUBLIC_FIREBASE_PROJECT_ID: Same page → projectId
- NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: Same page → storageBucket
- NEXT_PUBLIC_FIREBASE_APP_ID: Same page → appId
- FIREBASE_SERVICE_ACCOUNT_JSON: Project settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).
SETUP STEPS
1. https://console.firebase.google.com/ → Create project
2. Build → Firestore Database → Create (test mode only for local, then lock rules)
3. Project settings (gear) → General → Your apps → Add web app → copy the firebaseConfig object
4. Project settings → Service accounts → Generate new private key → FIREBASE_SERVICE_ACCOUNT_JSON
5. Never put the service account in NEXT_PUBLIC_*
### Polar
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Polar: https://polar.sh
- Polar docs: https://polar.sh/docs
- Next.js guide: https://polar.sh/docs/guides/nextjs
- Access tokens: https://polar.sh/docs/integrate/oat
- Webhook endpoints: https://polar.sh/docs/integrate/webhooks/endpoints
- Checkout API: https://polar.sh/docs/api-reference/checkouts
- @polar-sh/nextjs: https://www.npmjs.com/package/@polar-sh/nextjs
- @polar-sh/sdk: https://www.npmjs.com/package/@polar-sh/sdk
NPM PACKAGES
- @polar-sh/sdk
- @polar-sh/nextjs
API KEYS — name and exactly where to create them
- POLAR_ACCESS_TOKEN: Polar org settings → Organization Access Token (polar_oat_… or polar_pat_…). Server only.
- POLAR_WEBHOOK_SECRET: Org settings → Webhooks → Add endpoint → generate signing secret
- POLAR_PRODUCT_ID: Products → create a one-time product → copy the product id
- POLAR_SERVER: Optional. sandbox vs production. See Polar Next.js guide.
SETUP STEPS
1. https://polar.sh → create organization (use sandbox while building)
2. Org settings → create Organization Access Token → POLAR_ACCESS_TOKEN
3. Products → one-time product, min $1 → POLAR_PRODUCT_ID
4. Settings → Webhooks → Add Endpoint → {SITE}/api/webhooks/polar → copy secret → POLAR_WEBHOOK_SECRET
5. Subscribe to order.paid (and checkout events you need)
6. Local tunnel: polar listen http://localhost:3000/ (https://polar.sh/docs/integrate/webhooks/endpoints)
7. Put pending_id in checkout metadata
---
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: Firebase Firestore
- Payments: Polar.sh Checkout
ADAPTER RULES
Firestore + firebase-admin for writes (same collections as above).
Polar: create a one-time product. checkout.create with product_id, custom amount in cents if supported, metadata pending_id.
Webhook: order.paid. Verify Polar signature with POLAR_WEBHOOK_SECRET.
Fulfill exactly once using Polar order id as payment_id.
Success URL includes your pending_id, not only Polar's checkout id.
ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_FIREBASE_API_KEY
- NEXT_PUBLIC_FIREBASE_PROJECT_ID
- FIREBASE_SERVICE_ACCOUNT_JSON
- POLAR_ACCESS_TOKEN
- POLAR_WEBHOOK_SECRET
- POLAR_PRODUCT_ID
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.
DODO_PAYMENTS_PRODUCT_IDDashboard → Products → create a one-time Pay What You Want product with $1 minimum
DODO_PAYMENTS_ENVIRONMENTMust be test_mode or live_mode. Never the string "live".
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
### Firebase / Firestore
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Firebase Console: https://console.firebase.google.com/
- Create a web app: https://firebase.google.com/docs/web/setup
- Firestore: https://firebase.google.com/docs/firestore
- Firestore security rules: https://firebase.google.com/docs/firestore/security/get-started
- Admin / service account: https://firebase.google.com/docs/admin/setup
- firebase npm: https://www.npmjs.com/package/firebase
- firebase-admin npm: https://www.npmjs.com/package/firebase-admin
NPM PACKAGES
- firebase
- firebase-admin
API KEYS — name and exactly where to create them
- NEXT_PUBLIC_FIREBASE_API_KEY: Firebase Console → Project settings → Your apps → Web app → apiKey
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: Same page → authDomain
- NEXT_PUBLIC_FIREBASE_PROJECT_ID: Same page → projectId
- NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: Same page → storageBucket
- NEXT_PUBLIC_FIREBASE_APP_ID: Same page → appId
- FIREBASE_SERVICE_ACCOUNT_JSON: Project settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).
SETUP STEPS
1. https://console.firebase.google.com/ → Create project
2. Build → Firestore Database → Create (test mode only for local, then lock rules)
3. Project settings (gear) → General → Your apps → Add web app → copy the firebaseConfig object
4. Project settings → Service accounts → Generate new private key → FIREBASE_SERVICE_ACCOUNT_JSON
5. Never put the service account in NEXT_PUBLIC_*
### 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: Firebase Firestore
- Payments: Dodo Payments (Pay What You Want product, min $1)
ADAPTER RULES
Match ihatereading.lol: checkoutSessions.create with product_cart[].amount in CENTS.
Environment must be test_mode or live_mode (never "live").
Webhook POST /api/webhooks/dodo using @dodopayments/nextjs onPaymentSucceeded.
Metadata: pending_id, bid_amount_cents. Return URL /success?checkout_id={pending_id}.
Also poll GET /api/status and retrieve payment if webhook is late.
Store provider fields as checkout_id / payment_id (not dodo_* names).
ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_FIREBASE_API_KEY
- NEXT_PUBLIC_FIREBASE_PROJECT_ID
- FIREBASE_SERVICE_ACCOUNT_JSON
- 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.
Supabase (Postgres + RLS) · Stripe Checkout
Supabase + Stripe
Best default for most buyers. SQL ranking + Stripe.
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
### Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards
NPM PACKAGES
- stripe
API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
SETUP STEPS
1. https://dashboard.stripe.com → sign up, stay in Test mode
2. Developers → API keys → STRIPE_SECRET_KEY
3. Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
4. Success URL: {SITE}/success?checkout_id={pending_id}
5. Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
6. Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
7. Test card: 4242 4242 4242 4242
---
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: Stripe Checkout
ADAPTER RULES
SQL tables listings, bids, pending_checkouts with indexes on url and total_bid_cents.
RLS: public SELECT on live listings + bids; all inserts/updates via service role in API routes only.
Fulfillment must use a transaction: UPDATE listings SET total_bid_cents = total_bid_cents + $amount (no read-modify-write race).
Stripe Checkout + checkout.session.completed as above.
Optional: supabase realtime on listings for the board.
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
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET
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.
Supabase (Postgres + RLS) · Polar.sh Checkout
Supabase + Polar
Postgres board with Polar for indie SaaS payments.
POLAR_PRODUCT_IDProducts → create a one-time product → copy the product id
POLAR_SERVEROptional. sandbox vs production. See Polar Next.js guide.
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
### Polar
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Polar: https://polar.sh
- Polar docs: https://polar.sh/docs
- Next.js guide: https://polar.sh/docs/guides/nextjs
- Access tokens: https://polar.sh/docs/integrate/oat
- Webhook endpoints: https://polar.sh/docs/integrate/webhooks/endpoints
- Checkout API: https://polar.sh/docs/api-reference/checkouts
- @polar-sh/nextjs: https://www.npmjs.com/package/@polar-sh/nextjs
- @polar-sh/sdk: https://www.npmjs.com/package/@polar-sh/sdk
NPM PACKAGES
- @polar-sh/sdk
- @polar-sh/nextjs
API KEYS — name and exactly where to create them
- POLAR_ACCESS_TOKEN: Polar org settings → Organization Access Token (polar_oat_… or polar_pat_…). Server only.
- POLAR_WEBHOOK_SECRET: Org settings → Webhooks → Add endpoint → generate signing secret
- POLAR_PRODUCT_ID: Products → create a one-time product → copy the product id
- POLAR_SERVER: Optional. sandbox vs production. See Polar Next.js guide.
SETUP STEPS
1. https://polar.sh → create organization (use sandbox while building)
2. Org settings → create Organization Access Token → POLAR_ACCESS_TOKEN
3. Products → one-time product, min $1 → POLAR_PRODUCT_ID
4. Settings → Webhooks → Add Endpoint → {SITE}/api/webhooks/polar → copy secret → POLAR_WEBHOOK_SECRET
5. Subscribe to order.paid (and checkout events you need)
6. Local tunnel: polar listen http://localhost:3000/ (https://polar.sh/docs/integrate/webhooks/endpoints)
7. Put pending_id in checkout metadata
---
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: Polar.sh Checkout
ADAPTER RULES
Same Supabase schema + RLS + transactional increment as Stripe variant.
Polar checkout + signed webhook. Map Polar order to bids.payment_id.
Keep pending_id in Polar metadata so fulfillment does not depend on query-string only.
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
- POLAR_ACCESS_TOKEN
- POLAR_WEBHOOK_SECRET
- POLAR_PRODUCT_ID
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.
DODO_PAYMENTS_PRODUCT_IDDashboard → Products → create a one-time Pay What You Want product with $1 minimum
DODO_PAYMENTS_ENVIRONMENTMust be test_mode or live_mode. Never the string "live".
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.
Prisma + PostgreSQL · Stripe Checkout
Prisma + Stripe
Classic Next.js SaaS stack. Prisma schema you can own.
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
### Prisma + PostgreSQL
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Prisma docs: https://www.prisma.io/docs
- Prisma + Next.js: https://www.prisma.io/docs/orm/more/help-and-troubleshooting/nextjs-guide
- Prisma schema: https://www.prisma.io/docs/orm/prisma-schema
- Transactions: https://www.prisma.io/docs/orm/prisma-client/queries/transactions
- Neon (hosted Postgres): https://console.neon.tech
- Neon connection string: https://neon.tech/docs/connect/connect-from-any-app
- Vercel Postgres: https://vercel.com/docs/storage/vercel-postgres
- prisma npm: https://www.npmjs.com/package/prisma
NPM PACKAGES
- prisma
- @prisma/client
API KEYS — name and exactly where to create them
- DATABASE_URL: Postgres connection string from Neon, Vercel Postgres, Supabase, or Railway. prisma+postgres://... or postgresql://...
SETUP STEPS
1. Create a Postgres database (Neon is fastest: https://console.neon.tech)
2. Copy the connection string → DATABASE_URL
3. npx prisma init && define Listing, Bid, PendingCheckout
4. npx prisma migrate dev --name init
### Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards
NPM PACKAGES
- stripe
API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
SETUP STEPS
1. https://dashboard.stripe.com → sign up, stay in Test mode
2. Developers → API keys → STRIPE_SECRET_KEY
3. Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
4. Success URL: {SITE}/success?checkout_id={pending_id}
5. Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
6. Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
7. Test card: 4242 4242 4242 4242
---
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: Prisma + PostgreSQL
- Payments: Stripe Checkout
ADAPTER RULES
prisma/schema.prisma with Listing, Bid, PendingCheckout.
Unique index on Listing.url. Bid.paymentId unique.
Fulfill with prisma.$transaction and increment totalBidCents.
Stripe Checkout + webhook. Next.js Route Handlers only — no client DB writes.
Include prisma migrate + seed.
ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- DATABASE_URL
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET
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.
Prisma + PostgreSQL · Polar.sh Checkout
Prisma + Polar
Prisma schema with Polar checkout for creator tools.
POLAR_PRODUCT_IDProducts → create a one-time product → copy the product id
POLAR_SERVEROptional. sandbox vs production. See Polar Next.js guide.
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
### Prisma + PostgreSQL
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Prisma docs: https://www.prisma.io/docs
- Prisma + Next.js: https://www.prisma.io/docs/orm/more/help-and-troubleshooting/nextjs-guide
- Prisma schema: https://www.prisma.io/docs/orm/prisma-schema
- Transactions: https://www.prisma.io/docs/orm/prisma-client/queries/transactions
- Neon (hosted Postgres): https://console.neon.tech
- Neon connection string: https://neon.tech/docs/connect/connect-from-any-app
- Vercel Postgres: https://vercel.com/docs/storage/vercel-postgres
- prisma npm: https://www.npmjs.com/package/prisma
NPM PACKAGES
- prisma
- @prisma/client
API KEYS — name and exactly where to create them
- DATABASE_URL: Postgres connection string from Neon, Vercel Postgres, Supabase, or Railway. prisma+postgres://... or postgresql://...
SETUP STEPS
1. Create a Postgres database (Neon is fastest: https://console.neon.tech)
2. Copy the connection string → DATABASE_URL
3. npx prisma init && define Listing, Bid, PendingCheckout
4. npx prisma migrate dev --name init
### Polar
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Polar: https://polar.sh
- Polar docs: https://polar.sh/docs
- Next.js guide: https://polar.sh/docs/guides/nextjs
- Access tokens: https://polar.sh/docs/integrate/oat
- Webhook endpoints: https://polar.sh/docs/integrate/webhooks/endpoints
- Checkout API: https://polar.sh/docs/api-reference/checkouts
- @polar-sh/nextjs: https://www.npmjs.com/package/@polar-sh/nextjs
- @polar-sh/sdk: https://www.npmjs.com/package/@polar-sh/sdk
NPM PACKAGES
- @polar-sh/sdk
- @polar-sh/nextjs
API KEYS — name and exactly where to create them
- POLAR_ACCESS_TOKEN: Polar org settings → Organization Access Token (polar_oat_… or polar_pat_…). Server only.
- POLAR_WEBHOOK_SECRET: Org settings → Webhooks → Add endpoint → generate signing secret
- POLAR_PRODUCT_ID: Products → create a one-time product → copy the product id
- POLAR_SERVER: Optional. sandbox vs production. See Polar Next.js guide.
SETUP STEPS
1. https://polar.sh → create organization (use sandbox while building)
2. Org settings → create Organization Access Token → POLAR_ACCESS_TOKEN
3. Products → one-time product, min $1 → POLAR_PRODUCT_ID
4. Settings → Webhooks → Add Endpoint → {SITE}/api/webhooks/polar → copy secret → POLAR_WEBHOOK_SECRET
5. Subscribe to order.paid (and checkout events you need)
6. Local tunnel: polar listen http://localhost:3000/ (https://polar.sh/docs/integrate/webhooks/endpoints)
7. Put pending_id in checkout metadata
---
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: Prisma + PostgreSQL
- Payments: Polar.sh Checkout
ADAPTER RULES
Same Prisma models. Polar checkout + webhook verification.
paymentId unique on Bid. Transactional increment on Listing.
ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- DATABASE_URL
- POLAR_ACCESS_TOKEN
- POLAR_WEBHOOK_SECRET
- POLAR_PRODUCT_ID
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.
Prisma + PostgreSQL · Dodo Payments
Prisma + Dodo
Prisma + the same payment provider as this live site.
DODO_PAYMENTS_PRODUCT_IDDashboard → Products → create a one-time Pay What You Want product with $1 minimum
DODO_PAYMENTS_ENVIRONMENTMust be test_mode or live_mode. Never the string "live".
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
### Prisma + PostgreSQL
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Prisma docs: https://www.prisma.io/docs
- Prisma + Next.js: https://www.prisma.io/docs/orm/more/help-and-troubleshooting/nextjs-guide
- Prisma schema: https://www.prisma.io/docs/orm/prisma-schema
- Transactions: https://www.prisma.io/docs/orm/prisma-client/queries/transactions
- Neon (hosted Postgres): https://console.neon.tech
- Neon connection string: https://neon.tech/docs/connect/connect-from-any-app
- Vercel Postgres: https://vercel.com/docs/storage/vercel-postgres
- prisma npm: https://www.npmjs.com/package/prisma
NPM PACKAGES
- prisma
- @prisma/client
API KEYS — name and exactly where to create them
- DATABASE_URL: Postgres connection string from Neon, Vercel Postgres, Supabase, or Railway. prisma+postgres://... or postgresql://...
SETUP STEPS
1. Create a Postgres database (Neon is fastest: https://console.neon.tech)
2. Copy the connection string → DATABASE_URL
3. npx prisma init && define Listing, Bid, PendingCheckout
4. npx prisma migrate dev --name init
### 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: Prisma + PostgreSQL
- Payments: Dodo Payments
ADAPTER RULES
Prisma models + Dodo checkoutSessions (cents) + webhook + status poll.
Never store secrets in the client. live_mode vs test_mode only.
ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- DATABASE_URL
- 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.
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
### Convex
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Convex Dashboard: https://dashboard.convex.dev
- Next.js quickstart: https://docs.convex.dev/quickstart/nextjs
- HTTP actions / webhooks: https://docs.convex.dev/functions/http-actions
- convex npm: https://www.npmjs.com/package/convex
NPM PACKAGES
- convex
API KEYS — name and exactly where to create them
- CONVEX_DEPLOYMENT: Created by npx convex dev (written to .env.local)
- NEXT_PUBLIC_CONVEX_URL: Convex Dashboard → Settings → URL, or from npx convex dev
SETUP STEPS
1. https://dashboard.convex.dev → create project
2. npx convex dev (writes CONVEX_DEPLOYMENT + NEXT_PUBLIC_CONVEX_URL)
3. Money writes only from mutations called by the Stripe webhook / httpAction
### Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards
NPM PACKAGES
- stripe
API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
SETUP STEPS
1. https://dashboard.stripe.com → sign up, stay in Test mode
2. Developers → API keys → STRIPE_SECRET_KEY
3. Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
4. Success URL: {SITE}/success?checkout_id={pending_id}
5. Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
6. Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
7. Test card: 4242 4242 4242 4242
---
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: Convex
- Payments: Stripe Checkout
ADAPTER RULES
Convex tables for listings/bids/pending. Mutations only from Convex httpActions / Next webhook that calls an internal mutation.
Stripe Checkout. Board uses useQuery so ranks update live after pay.
ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- CONVEX_DEPLOYMENT
- NEXT_PUBLIC_CONVEX_URL
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET
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.
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
### Upstash Redis
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Upstash Console: https://console.upstash.com
- Redis REST: https://upstash.com/docs/redis/overall/getstarted
- Next.js + Upstash: https://upstash.com/docs/redis/howto/nextjs
- @upstash/redis npm: https://www.npmjs.com/package/@upstash/redis
NPM PACKAGES
- @upstash/redis
API KEYS — name and exactly where to create them
- UPSTASH_REDIS_REST_URL: Upstash Console → database → REST API → UPSTASH_REDIS_REST_URL
- UPSTASH_REDIS_REST_TOKEN: Same page → UPSTASH_REDIS_REST_TOKEN
SETUP STEPS
1. https://console.upstash.com → Create Redis database
2. Copy REST URL + token
3. Use a ZSET score = total_bid_cents for ranking
### Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards
NPM PACKAGES
- stripe
API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
SETUP STEPS
1. https://dashboard.stripe.com → sign up, stay in Test mode
2. Developers → API keys → STRIPE_SECRET_KEY
3. Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
4. Success URL: {SITE}/success?checkout_id={pending_id}
5. Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
6. Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
7. Test card: 4242 4242 4242 4242
---
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: Upstash Redis
- Payments: Stripe Checkout
ADAPTER RULES
Store listing hashes + a Redis ZSET score = total_bid_cents for ranking.
Bids as a list per listing. All writes in Route Handlers.
Stripe Checkout + webhook. Fine for a small board; document the scale limit.
ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- UPSTASH_REDIS_REST_URL
- UPSTASH_REDIS_REST_TOKEN
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET
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.