moisestech / moisestech/moises
🪙 [FE] 🟦 Tip Jaar
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- Avg merge
- 2h 44m
- Merged PRs (30d)
- 24
Description
# 🎨 Tip-Jar Dash & Components \[FE]
### 🍽 Appetite
Ship in **1 six-day cycle**.
### ❓ Problem
Creators can’t see tip earnings. Viewers can’t tip natively.
---
### 💡 Solution (fat-marker)
1. **Viewer Tip CTA** → amount picker → `POST /api/tip/create-checkout-session` → Stripe Checkout
2. **Creator Earnings** (cards + sparkline) from `creator_earnings_mv` (fallback API)
3. **Tips Feed** (last 50 + realtime append via Supabase)
4. **Payout History** (monthly rows)
5. **Inline Realtime Toast** on tip INSERT for logged-in creator
```mermaid
graph TD
subgraph Viewer
TB[" \n amount pills + Other"] --> POST["POST /api/tip/create-checkout-session"]
POST --> STRIPE[(Stripe Checkout)]
end
subgraph Creator Dashboard
ED[" \n cards + sparkline"] --> MV["creator_earnings_mv \n (GET /api/creator/earnings fallback)"]
TF[" \n + realtime"] --> TIPS["GET /api/creator/tips?limit=50"]
PH[""] --> PAY["GET /api/creator/payouts"]
RT["Supabase channel \n tips:creator:{id}"] --> TF
RT --> TOAST["Inline toast 🔔"]
end
```
---
### 🔌 Interfaces (treat as truthy mocks)
* **POST** `/api/tip/create-checkout-session`
Body `{ creatorId, contentId?, amountCents }` → `200 { url }`
Errors: `400 INVALID_CREATOR | AMOUNT_NOT_ALLOWED`, `409 CREATOR_NOT_ONBOARDED`, `500`
* **GET** `/api/creator/earnings?creatorId=UUID` → **EarningsDTO** (MV shape)
* **GET** `/api/creator/tips?creatorId=UUID&limit=50&cursor?` → `{ data: TipDTO[], nextCursor? }`
* **GET** `/api/creator/payouts?creatorId=UUID` → `PayoutDTO[]`
* **Realtime** Supabase channel `tips:creator:{creatorId}` INSERT on `public.tips` (de-dupe by `id`)
> If your app currently uses `/api/stripe/create-checkout-session`, add a thin proxy at
> `src/app/api/tip/create-checkout-session/route.ts` to keep FE contracts stable.
---
### 🧱 Scope (what we will ship)
**Components**
* `` (popover/inline variants, pills: \$3/\$5/\$10 + Other)
* `` (4 cards + 12-week sparkline via Recharts)
* `` (virtualized list up to 500; realtime append)
* `` (month/status/amount/transfer/runAt/note)
* `TipToasts` helper (subscribe + toast “🔥 New \$X tip …”)
**Hooks**
* `useTipCheckout`, `useCreatorEarnings`, `useTipsFeed`, `usePayouts`
**Routes**
* `/artist/[slug]` → ``
* `/watch/[streamId]` → floating ``
* `/profile/creator/revenue` → dashboard + Tabs( Tips, Payouts )
**Feature flags & guards**
* `NEXT_PUBLIC_TIPS_ENABLED` gates all Tip UI
* Hide Tip UI if `creatorId` missing; disable if not onboarded (`creators.stripe_account` absent)
* Offline → disabled + “Offline” hint
**A11y**
* Keyboardable popover, focus trap, aria-labels; toasts `role="status"`; numeric input `inputMode="numeric"`
---
### 🧵 Deliverables (done = demoable)
* Components + hooks implemented and **Storybook** stories (all states)
* **MSW** handlers mirroring the contracts
* **Postman** collection + envs (`tools/postman/...`) and scripts:
* `pnpm postman:run:local` / `:preview` (or Newman alt)
* **Tests**
* Unit (Vitest + RTL): TipButton validation & API payloads; TipsFeed realtime append; currency utils
* E2E (Playwright sketch): \$5 redirects to Checkout URL; realtime toast appears on mock insert
* Loading/empty/error states with skeletons & toasts
* Proxy route for `/api/tip/create-checkout-session` if needed
---
### 🧭 Non-Goals / No-Gos
* Admin tooling, Stripe Connect onboarding UI
* Multi-currency (USD-only)
* Fancy charts (stick to Recharts area sparkline)
* Server-side net math (use BE‐provided `net_*`)
---
### 🐇 Rabbit Holes to avoid
* D3 custom viz; “theme engine” for Tip UI; creator-specific presets (future)
* Realtime reconnect edge-case over-engineering (simple de-dupe by `id` is enough)
---
### ⚠️ Risks & Mitigations
* **Contract drift** between FE and BE → Zod parse in hooks + **Postman** in PR CI
* **Realtime dupes** on reconnect → `dedupeById` util
* **Stripe URL nav blocked** by popup blockers → use `window.location.href` (full nav), not `window.open`
* **Rate-limit/409** → surface toast, keep selection intact
---
### ✅ Acceptance criteria
* TipButton: presets + Other (\$1–\$500), invalid input blocked; 409 shows tooltip “Tips coming soon”
* Selecting **\$5** from TipButton triggers POST and **navigates** to returned `url`
* Creator Revenue page:
* Cards reflect `gross_*` and `net_*` correctly
* Sparkline renders 12 points (or available)
* Tips tab shows items, loads more, **realtime item** appears without duplicates
* Payouts tab renders sortable table; Stripe transfer links open in new tab
* Realtime toast displays within 1s of INSERT during local dev (mocked)
* All surfaces respect **dark mode** and flags; A11y checks pass
---
### 🧩 Implementation notes (repo fit)
* New module: `src/features/revenue/*`
* `components/revenue/...` and `hooks/revenue/...` as scaffolded
* Use existing `app/profile/creator/revenue/page.tsx` and artist/watch pages to mount components
* Env: `NEXT_PUBLIC_TIPS_ENABLED`, `NEXT_PUBLIC_TIP_PRESETS`, Supabase URL/Anon key
* Postman files live in `tools/postman/` (collection + envs); optional GH Action `postman.yml`
---
### 📌 Out-of-cycle Qs to resolve by Day 2 (defaults if silent)
1. Per-creator presets? **Default** global `300,500,1000`.
2. Bind tips to `contentId` on `/watch`? **Yes** when provided.
3. Post-success UX? **Return URL** to `/artist/[slug]?tip=success` + optional ``.
4. Tipper anonymity? **Not in v1** (placeholder name “Anonymous” if BE sends it).
---
Tooling & Verification (non-optional for FE)
Storybook with msw-storybook-addon:
Stories for: TipButton, EarningsDashboard, TipsFeed, PayoutHistoryTable, TipCongratsModal.
Each story covers: loading/empty/error/success; “not onboarded” state for TipButton.
MSW handlers mirroring BE contracts (paths, payloads, and errors):
File: src/msw/handlers-revenue.ts (or apps/web/msw/...), used by Storybook & tests.
Must include error cases: 400 AMOUNT_NOT_ALLOWED, 409 CREATOR_NOT_ONBOARDED.
Postman collection & environments (CLI runnable):
tools/postman/tip-jar.postman_collection.json
tools/postman/env/{local,preview,prod}.postman_environment.json
NPM scripts:
"postman:run:local" and "postman:run:preview" that export a JUnit or HTML report to reports/.
(Optional) CI gate:
.github/workflows/postman.yml runs collection on PRs touching src/app/api/** or tools/postman/**.
✅ Acceptance Criteria (updated)
UI/Flows
TipButton validates $1–$500, shows “Tips coming soon” when 409, and navigates to returned url.
Revenue page shows 4 cards, 12-week sparkline, Tips feed (with realtime append + de-dupe), Payouts table with Stripe transfer links.
Inline toast appears on Supabase INSERT within 1s in dev (mocked acceptable).
Storybook/MSW
All component stories render without console errors; MSW handlers satisfy Storybook requests.
Postman
pnpm postman:run:local passes for all four endpoints (Checkout, Earnings, Tips, Payouts).
Error variations (409/400) return expected codes/messages.
Tests
Unit: TipButton posts correct payload; useTipsFeed appends realtime; currency utils correct.
E2E smoke: selecting $5 leads to checkout URL navigation (mocked).
Contracts
Zod schemas parse all API responses; failure surfaces toast (no silent crashes).
Flags & Guardrails
NEXT_PUBLIC_TIPS_ENABLED respected; Tip UI hidden if creatorId absent; offline disables submit.
⚙️ Definition of Ready (pre-work the FE may do if missing)
Proxy route exists: src/app/api/tip/create-checkout-session/route.ts → forwards to /api/stripe/create-checkout-session.
Env set: NEXT_PUBLIC_TIPS_ENABLED, NEXT_PUBLIC_TIP_PRESETS, Supabase URL/ANON.
Repo paths aligned: src/features/revenue/{components,hooks,lib} and tools/postman/**.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.