Skip to content

feat(dashboard): payment links list and create form (#144) - #206

Merged
0xdevcollins merged 1 commit into
mainfrom
feat/144-links-list-and-create
Aug 9, 2026
Merged

feat(dashboard): payment links list and create form (#144)#206
0xdevcollins merged 1 commit into
mainfrom
feat/144-links-list-and-create

Conversation

@0xdevcollins

Copy link
Copy Markdown
Owner

Closes #144.

A merchant could sign up, get a settlement wallet auto-provisioned, and then had no way to create a payment link except curl. This closes that.

The routing bug underneath it

/links was rendering the detail page — it called useParams<{ id }>() on a route with no [id] segment, so id was always undefined and the page requested /payment-links/undefined. There was no list view and no create form anywhere in the app.

The detail page moves to /links/[id], where its param exists. /links becomes the list it always claimed to be.

Mostly wiring, plus two real fixes

LinkCard, CreateLinkModal, LinkCreatedModal, QRCodeModal and usePaymentLinks all already existed — nothing rendered them. Two things didn't survive contact with the API:

PaymentLinksResponse described a shape the API has never sent. It declared total, page, limit as siblings of data. The API answers { data, meta }, and the client deliberately skips unwrapping when meta is present, so both halves arrive intact. The single consumer only read .data, so pagination had nothing to render from and nothing caught it.

LinkCard had no route to the detail page. The ID is now the link rather than the whole card — the footer holds copy, QR and deactivate buttons, and nesting those inside an anchor is invalid markup that would navigate on every click.

Acceptance criteria

criterion status
/links renders without crashing
Empty state at 0 links ✅ (EmptyState variant="links" + create CTA)
Filter chips drive ?status= ✅ All 5 / Active 5 / Deactivated 1
Create posts, appears immediately ✅ count 4 → 5
New link URL auto-copied + toast ✅ (clipboard best-effort, URL also shown in modal)
Deactivate fires DELETE, badge updates ✅ flips to DEACTIVATED in place
Pagination works ✅ driven by meta.totalPages, hidden at 1 page
JWT works for POST ✅ verified — CombinedAuthGuard accepts the dashboard's JWT, no API key needed, no follow-up required
tsc clean, no console errors

Search filters the loaded page client-side — the API has no search parameter for links yet, so filtering server-side would silently return nothing. Flagged in the code to move once the endpoint supports it.

Verified in the browser against the running API

List renders, filters drive the query param, create → count increments, deactivate → badge flips, /links/[id] loads the link and its stats with real ids. No console errors.

Dashboard: typecheck clean, 0 lint errors, 38/38 tests.

Note, not in scope: created links point at https://pay.useroutr.com/<code> because PAYMENT_LINK_BASE_URL is unset locally — so they aren't clickable in dev. That's the #113 config item.

A merchant could sign up, get a settlement wallet provisioned, and then had no
way to create a payment link except curl. This closes that.

The `/links` route was rendering the *detail* page: it called
`useParams<{ id }>()` on a route with no `[id]` segment, so `id` was always
undefined and the page requested `/payment-links/undefined`. There was no list
view and no create form anywhere. The detail page moves to `/links/[id]`, where
its param actually exists, and `/links` becomes the list it always claimed to
be.

Nearly every piece already existed — LinkCard, CreateLinkModal,
LinkCreatedModal, QRCodeModal, usePaymentLinks — but nothing rendered them.
This wires them up and fixes what did not survive contact with the API:

- `PaymentLinksResponse` declared `total`, `page` and `limit` as siblings of
  `data`, a shape the API has never sent. It answers `{ data, meta }`, and the
  client deliberately skips unwrapping when `meta` is present, so both halves
  arrive intact. The one consumer only ever read `.data`, so pagination had
  nothing to render from and nothing caught the mismatch.
- LinkCard had no route to the detail page. The ID is now the link, rather than
  the whole card: the footer holds copy, QR and deactivate buttons, and nesting
  those inside an anchor is invalid markup that would navigate on every click.

Search filters the loaded page client-side, because the API has no search
parameter for links yet — filtering server-side would silently return nothing.
Marked in the code for a move once the endpoint supports it.

Verified in the browser against the running API: list renders 5 links, filter
chips drive `?status=` (All 5 / Deactivated 1), create posts and the count goes
4 to 5 with the URL auto-copied, deactivate flips the badge in place, and
`/links/[id]` loads the link and its stats with real ids. No console errors.

Dashboard: typecheck clean, 0 lint errors, 38/38 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
useroutr-www Ready Ready Preview Aug 9, 2026 8:20pm

@0xdevcollins
0xdevcollins merged commit 07d19c5 into main Aug 9, 2026
8 checks passed
@0xdevcollins
0xdevcollins deleted the feat/144-links-list-and-create branch August 9, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PR 8 — Dashboard: /links list page + create-link form

1 participant