Skip to content

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

@0xdevcollins

Description

@0xdevcollins

Why

Right now a merchant signs up, gets a Stellar settlement wallet auto-provisioned (PR #136), but can only create payment links via curl. The dashboard `/links` page exists but was never tested against the new API response shape — it'll likely crash on first render now that the contract changed in PR #136.

This is the highest-leverage unshipped work for the demo: unblocks every "show me how a merchant uses this" conversation.

Scope

Build two things in `apps/dashboard/src/app/(dashboard)/links/`:

  1. List view — paginated table of the merchant's payment links with filter + search
  2. Create form — modal or drawer that POSTs a new link and adds it to the list optimistically

API endpoints (already shipped)

Verb Path Auth
GET `/v1/payment-links?page=1&limit=20&status=active|expired|deactivated|all` JWT
POST `/v1/payment-links` API key (CombinedAuthGuard) — see acceptance criteria #6
DELETE `/v1/payment-links/:id` JWT
GET `/v1/payment-links/:id/stats` JWT

Response shape from `@useroutr/types`:
```ts
interface PaymentLink {
id: string; // "lnk_..."
amount?: number;
currency: string;
description?: string;
type: "single-use" | "multi-use";
status: "active" | "expired" | "deactivated";
usageCount: number;
expiresAt?: string; // ISO 8601
url: string; // pay.useroutr.com/...
createdAt: string;
updatedAt: string;
}
```

`POST` body (note: still snake_case for input — it's the merchant-facing contract):
```json
{
"amount": 25,
"currency": "USD",
"description": "...",
"single_use": false,
"expires_at": "2026-06-01T00:00:00Z"
}
```

Files to read first

  • `apps/dashboard/src/app/(dashboard)/links/page.tsx` — existing page (crashes on render right now)
  • `apps/dashboard/src/components/links/{LinkCard,LinkStatusBadge,CreateLinkModal}.tsx` — existing components
  • `apps/dashboard/src/hooks/` — add `useLinks.ts` here following the pattern in `useSettings.ts`
  • `packages/types/src/payment.types.ts` — source of truth for `PaymentLink`

What to build

List page (`/links`)

  • Card grid (the existing `LinkCard` already renders the right shape — fix what doesn't)
  • Filter chips: All / Active / Expired / Deactivated (drives the `?status=` query param)
  • Search input (client-side filter on `description` for now, server-side later)
  • Empty state with a "Create your first link" CTA
  • "+ New link" button top-right opens the create modal

Create modal

  • Form fields: amount (optional → "open amount" checkbox), currency, description, single-use toggle, expires-at date picker (optional)
  • Submit POSTs to `/v1/payment-links`, on success closes modal + optimistically inserts into the list + toasts the URL + copies to clipboard
  • Handle 422 (insufficient liquidity), 400 (validation), 401 (auth), surface API `error.message` from the envelope

Detail link (each card)

  • Clicking a card or its ID navigates to `/links/[id]` — that page is a separate issue (PR 9)

Acceptance criteria

  • `/links` renders without throwing the `t.bg` crash that was happening before
  • Empty state shows when merchant has 0 links
  • Filter chips drive a `status` query param against the API and update the list
  • Create modal POSTs successfully and the new link appears immediately
  • Newly-created link's URL is auto-copied to clipboard with a toast
  • Deactivate button on a link card fires `DELETE /v1/payment-links/:id` and updates the status badge
  • Pagination works (next/prev or load-more)
  • API key auth gotcha — the merchant only has JWT in the dashboard. `POST /v1/payment-links` currently uses `CombinedAuthGuard` (API key OR JWT). Confirm JWT path works; if not, file a follow-up to loosen the guard on this endpoint or have the dashboard mint an API key behind the scenes.
  • tsc clean, no console errors
  • Manual test: signup → create link → see it in /links → click it → see /links/[id] (PR 9)

Estimated effort

~1 day for a frontend dev familiar with TanStack Query + the existing dashboard component library.

Design

Match the existing `/payments` and `/payouts` pages in this app — same `surface` chrome, same toast pattern, same skeleton loaders. Look at `apps/dashboard/src/app/(dashboard)/payments/page.tsx` as the template.

Metadata

Metadata

Assignees

Labels

Stellar WaveIssues in the Stellar wave programcritical-pathBlocks other workdashboardMerchant Dashboard productenhancementNew feature or requestfrontendFrontend/UI workpayment-linksPayment Links product

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions