moisestech / moisestech/moises

🪙 [FE] 🟦 Tip Jaar

Open
#3 0 comments 0 reactions 1 assignee Claimed by @moisestech View on GitHub
enhancement good first issue
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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.