Internal Documentation internal
TalkIDE internal documentation

Authenticated admin endpoint that returns a paginated, searchable list of all waitlist entries and supports full CSV export. Requires role ADMIN.

  • Only users with role ADMIN may access these endpoints. Any authenticated user without ADMIN role receives 403 Forbidden.
  • The list view shows: queue position, email, name, role, project_idea (truncated), referral code, confirmed referral count, and created_at.
  • Search filters by email prefix (case-insensitive ILIKE).
  • Pagination is cursor-free — standard page/size with total count returned.
  • CSV export downloads the full dataset (all entries, no pagination limit) as a .csv file. The browser receives a Content-Disposition: attachment; filename="waitlist-{date}.csv" response header.
  • Authorization model follows the existing pattern used in invite-system.md: role ADMIN stored in users table.
sequenceDiagram
    actor Admin

    Admin->>+FE: navigates to /admin/waitlist

    FE->>+BE: GET /api/v1/admin/waitlist?page=0&size=50&search= <br> Authorization: Bearer {token}

    BE->>BE: authenticate and authorize (role = ADMIN)
    alt not authenticated
        BE-->>FE: 401 Unauthorized <br> ErrorResponse
    end
    alt authenticated but not ADMIN
        BE-->>FE: 403 Forbidden <br> ErrorResponse
    end

    BE->>DB: SELECT entries with position calc, total count <br> ILIKE filter on email if search provided <br> ORDER BY created_at DESC, LIMIT/OFFSET
    BE->>-FE: 200 OK <br> WaitlistListResponse

    FE->>-Admin: render table with entries, position, referral stats

    Admin->>+FE: types email into search box

    FE->>FE: debounce 300ms
    FE->>+BE: GET /api/v1/admin/waitlist?page=0&size=50&search={email}

    BE->>DB: SELECT with ILIKE '%{email}%' filter
    BE->>-FE: 200 OK <br> WaitlistListResponse (filtered)

    FE->>-Admin: re-render table with filtered results

    Admin->>+FE: clicks "Export CSV"

    FE->>+BE: GET /api/v1/admin/waitlist/export <br> Authorization: Bearer {token}

    BE->>BE: authenticate and authorize (role = ADMIN)
    BE->>DB: SELECT all entries (no pagination limit) <br> ORDER BY created_at ASC
    BE->>BE: serialize to CSV

    BE->>-FE: 200 OK <br> Content-Type: text/csv <br> Content-Disposition: attachment; filename="waitlist-{yyyy-MM-dd}.csv" <br> CSV body

    FE->>-Admin: browser triggers file download

GET /api/v1/admin/waitlist — query parameters:

ParameterTypeDefaultDescription
pageinteger0Zero-based page index
sizeinteger50Entries per page (max 200)
searchstring(empty)Case-insensitive email filter

200 OK WaitlistListResponse:

{
  "data": {
    "entries": [
      {
        "id": 42,
        "position": 312,
        "email": "jane@example.com",
        "name": "Jane Doe",
        "role": "MAKER",
        "project_idea": "A booking site for my pottery studio",
        "referral_code": "janedoe-x4n2",
        "referral_url": "https://talkide.app/r/janedoe-x4n2",
        "confirmed_referrals": 3,
        "referred_by_code": "friend-ab12",
        "created_at": "2026-05-16T10:23:00Z"
      }
    ],
    "page": 0,
    "size": 50,
    "total": 2184
  }
}

GET /api/v1/admin/waitlist/export — no query parameters (exports full dataset).

200 OK CSV export — response body (plain text, UTF-8 with BOM for Excel compatibility):

position,email,name,role,project_idea,referral_code,confirmed_referrals,referred_by_code,created_at
312,jane@example.com,Jane Doe,MAKER,"A booking site for my pottery studio",janedoe-x4n2,3,friend-ab12,2026-05-16T10:23:00Z
...

401 Unauthorized ErrorResponse:

{
  "status": 401,
  "code": "UNAUTHORIZED",
  "message": "Authentication required"
}

403 Forbidden ErrorResponse:

{
  "status": 403,
  "code": "FORBIDDEN",
  "message": "Admin role required"
}

UX Guidelines

User Flow

  1. Admin je přihlášen a naviguje na /admin/waitlist (z admin navigace nebo přímé URL).
  2. FE okamžitě spustí GET /api/v1/admin/waitlist?page=0&size=50 — zobrazí se skeleton loader (3 řádky tabulky s pulsujícím var(--bg-3) bg).
  3. Po načtení dat se skeleton nahradí tabulkou s prvními 50 záznamy. Nad tabulkou se zobrazí počet všech záznamů jako badge.
  4. Admin může vyhledávat podle emailu — debounce 300ms; při každé změně se stránkování resetuje na page=0 a spustí nový request.
  5. Admin klikne na “Export CSV” — tlačítko zobrazí spinner, prohlížeč automaticky stáhne soubor waitlist-{yyyy-MM-dd}.csv. Tlačítko se vrátí do výchozího stavu po dokončení downloadu.
  6. Admin naviguje tabulkou pomocí Previous / Next tlačítek nebo přímým zadáním čísla stránky.
  7. Admin může kliknout na referral kód v tabulce — zkopíruje se plná referral URL do schránky; buňka krátce zobrazí “Copied!” feedback.

Layout

  • Screen type: admin list view
  • Responsive: desktop-first; na šíři < lg (1024px) se skryje sloupec “Project idea”; na < md (768px) layout přejde do card-based výpisu (každý záznam jako karta místo řádku tabulky)
  • Container: max-width 1400px, padding 32px 24px; stránka používá stejný admin shell jako ostatní admin panely (TopBar/MiniTopBar z shared.jsx, var(--bg-1) canvas)
  • Stránka nemá AuthBackdrop — jde o autentizovaný admin kontext

Sekce stránky (shora dolů):

  1. Page header — titulek “Waitlist” (var(--font-display) 600 24px -0.02em) + count badge (font-mono 11px uppercase, var(--bg-3) bg, var(--line-2) border, border-radius 999, 4px 10px padding, var(--fg-2)) vedle sebe; vpravo “Export CSV” tlačítko (.btn.primary s Download ikonou — amber bg, var(--primary-fg) text)
  2. Search bar — full-width input, var(--bg-2) bg, var(--line-2) border, border-radius 10px (var(--r-md)), 12px 16px padding, Search ikona vlevo uvnitř inputu (var(--fg-3)), placeholder “Search by email…”; margin-bottom 16px
  3. Datová tabulkavar(--bg-2) bg, var(--line-2) border, border-radius 14px (var(--r-lg)), overflow hidden; <thead> s var(--bg-1) bg a border-bottom: 1px solid var(--line-2); řádky oddělené border-bottom: 1px solid var(--line-1); hover řádku: bg var(--bg-3) transition 0.12s
  4. Pagination bar — flex row, space-between; vlevo “Showing X–Y of Z entries” (13px var(--fg-3)); vpravo Previous a Next tlačítka (.btn.ghost) + “Page N of M” label (font-mono 12px var(--fg-2))

Komponenty

ElementKomponenta / stylPoznámka
Page title<h1>var(--font-display) 600 24px -0.02em var(--fg-1)
Count badge<span> pillfont-mono 11px 0.06em uppercase; var(--bg-3) bg, var(--line-2) border, border-radius 999, 4px 10px
Export CSV button.btn.primaryvar(--amber) bg, var(--primary-fg) text; Download ikona 14px vlevo; loading: spinner vlevo místo ikony + text “Exporting…”; disabled po dobu exportu
Search inputStandalone <input>Vizuálně konzistentní s AuthInput; var(--bg-2) bg, var(--line-2) border, border-radius 10px; Search ikona 14px var(--fg-3) inline vlevo (padding-left 40px); debounce 300ms
Tabulka thead<thead> stickyvar(--bg-1) bg; buňky: font-mono 10px uppercase 0.08em var(--fg-3), 12px 16px padding; kliknutelné buňky (budoucí sort) budou mít cursor: pointer + ChevronUp/Down ikony
Sloupec Position<td>font-mono 13px, text-align right; top 10: color: var(--amber), font-weight 500; ostatní: var(--fg-2)
Sloupec Email<td>13px var(--fg-1) 500; max-width 200px, overflow hidden, text-overflow ellipsis
Sloupec Name<td>13px var(--fg-1)
Sloupec Role<td> pillPill per role: MAKERvar(--amber-soft) bg var(--amber) text; AGENCYvar(--indigo-soft) bg var(--indigo) text; INTERNALvar(--green-soft) bg var(--green) text; OTHERvar(--bg-3) bg var(--fg-2) text; font-mono 10px uppercase, border-radius 999
Sloupec Project idea<td> truncatedMax 80 znaků + ellipsis; title atribut s plným textem pro tooltip; 12px var(--fg-3); skrytý na < lg
Sloupec Referral code<td> kliknutelnáfont-mono 12px var(--fg-2) cursor: pointer; klik: kopíruje plnou referral URL; po kopírování buňka krátce zobrazí “Copied!” (zelené) po 2s reset
Sloupec Referrals<td>13px; hodnota > 0: var(--amber) 500; hodnota = 0: var(--fg-3)
Sloupec Joined<td>Relativní čas 12px var(--fg-3) (např. “3 days ago”); title atribut s ISO timestampem
Pagination Previous.btn.ghostDisabled (opacity 0.4, cursor not-allowed) na první stránce
Pagination Next.btn.ghostDisabled na poslední stránce
Skeleton loader3× skeleton řádekvar(--bg-3) bg, border-radius 4px, 36px výška, animace pulse (opacity 0.5 ↔ 1, 1.4s infinite)

Prázdný stav

Pokud total === 0 (bez search filtru — waitlist je prázdná):

  • Tabulka renderuje jediný řádek přes celou šíři s výškou 160px
  • Ikona Users 32px var(--fg-4) vycentrována vertikálně
  • Text “No entries yet.” 14px var(--fg-3) pod ikonou
  • Žádné akční tlačítko

Pokud total === 0 (se search filtrem — žádný výsledek):

  • Stejný layout, text: “No results for “{search}”.”
  • Podtext 12px var(--fg-4): “Try a different email prefix.”

Validační chování

  • Search field: žádná validace — libovolný vstup je přijat; trim whitespace před odesláním requestu
  • Stránkování: Previous/Next tlačítka jsou disabled (opacity 0.4) na mezích; nelze přejít za rozsah stránek
  • Export CSV: tlačítko disabled po celou dobu stahování souboru (spinner); pokud BE vrátí 403, toast “Access denied. Admin role required.” (var(--rose) bg)

Accessibility

  • Tabulka: <table> s role="grid", <caption> “Waitlist entries” (vizuálně skrytý přes .sr-only), scope="col" na <th> buňkách
  • Search input: aria-label="Search waitlist by email", aria-controls="waitlist-table", aria-busy="true" po dobu loadingu
  • Skeleton rows: aria-busy="true" na <tbody> po dobu načítání, aria-live="polite" při refresh
  • Referral code copy: aria-label="Copy referral link {code}", po kopírování aria-label="Copied!" po dobu 2s
  • Export CSV button: aria-busy="true" po dobu exportu, text “Exporting…” jako viditelný label (ne jen spinner)
  • Pagination: aria-label="Previous page" / "Next page"; aria-disabled="true" + disabled atribut když není dostupné
  • Kontrast: amber hodnoty ve sloupcích Position a Referrals splňují WCAG AA na var(--bg-2) pozadí

Loading a Error stavy

  • Prvotní load: skeleton (3 řádky) místo tabulky; počet záznamů badge zobrazí ”—” dokud nedorazí response
  • Přechod při search / stránkování: tabulka se zakryje overlay var(--bg-2) opacity 0.6 a spinner po dobu nového requestu (instant replace by byl blikání — overlay je lepší)
  • Network error / 5xx při load: error banner v místě tabulky — ikona AlertTriangle 20px var(--rose), text “Failed to load waitlist. Try again.” + “Retry” button (.btn.ghost)
  • 403 při mount (přišel non-admin): FE přesměruje na /dashboard bez renderování stránky; toast “Access denied.”
  • 401 při mount (token expiroval): FE přesměruje na /login s returnUrl=/admin/waitlist
  • Export CSV — success: prohlížeč automaticky spustí download; tlačítko se vrátí do výchozího stavu
  • Export CSV — error: toast zpráva pravý dolní roh: “CSV export failed. Please try again.” var(--rose) bg, auto-dismiss 5s

Frontend

Validations

FieldConstraintsSizePatternNote
searchoptional0 - 255Debounced 300ms; triggers new page=0 request on change
pagepositive_or_zeroResets to 0 on search change
sizepositive1 - 200Fixed at 50 in UI; available as URL param

Backend

Validations

FieldConstraintsSizePatternNote
pagenot_null, min=0Defaults to 0
sizenot_null, min=1, max=200Defaults to 50
searchoptional0 - 255Applied as ILIKE ‘%{search}%’ on email column

Test Cases

GIVENWHENTHEN
authenticated admin, 50 waitlist entrieslist is called (page=0, size=50)200 OK; all 50 entries returned with correct positions
authenticated admin, search=“jane”list is called with search param200 OK; only entries whose email contains “jane” returned
authenticated admin, 2184 entriesexport is called200 OK; CSV attachment with all 2184 rows, correct headers
authenticated adminexport is calledresponse has Content-Disposition: attachment; filename contains today’s date
unauthenticated requestlist is called401 UNAUTHORIZED
authenticated user with role USER (not ADMIN)list is called403 FORBIDDEN
authenticated user with role USER (not ADMIN)export is called403 FORBIDDEN
page=0, size=0list is called400 VALIDATION_ERROR (size must be at least 1)
page=-1list is called400 VALIDATION_ERROR (page must be non-negative)
size=201list is called400 VALIDATION_ERROR (size exceeds max 200)

FEEDBACK

Admin stránka nemá handoff referenci — navrhoval jsem od základu na základě design tokenů a vzorů z ostatních admin panelů (UC-08004). Chybělo mi: (1) reference na skutečný admin shell — existující invite-system.md ho zmiňuje, ale bez vizuálního popisu; bylo by přínosné mít admin-shell.jsx nebo alespoň screenshot. (2) Role color mapping — pro admin table jsem zvolil amber/indigo/green/neutral pro MAKER/AGENCY/INTERNAL/OTHER; pokud má projekt jinou konvenci, je potřeba to sladit.


Was this page helpful?

Thanks for the feedback.