Admin

An owner-only control plane, added for one practical reason: Flux is invite-gated, and without this the only way to approve someone waiting for access or to mint them a code was to open a database client and write the rows by hand. Five tabs, all of them thin. Each fetches its own data on mount, takes no props, and knows nothing about the other four.

The gate

2 components

Every value on this page is invented. The emails, names, phone numbers, invite codes, counts, timestamps and log entries below are fabricated sample data written for this showcase. None of it came from the product, its database, or its documentation, and none of it belongs to a real person.

The only two tabs that write anything. Between them they decide who can get in at all: one sets the rule, the other applies it person by person. Everything else in the admin panel just looks.

AccessTab
Two panels in one tab: the queue of people who asked to get in, and the invite codes you hand out. Approving somebody mints them a single-use code and shows it to you once in a dialog, because the app does not send the email. You do.
src/components/admin/access-tab.tsx
Backend not configured

Supabase is not configured.

Loading

Loading…

Access requests · empty
Access requests
People who requested access. Approving mints a single-use invite code for you to send them manually.

No access requests yet.

Access requests · loaded, all three statuses
Access requests
People who requested access. Approving mints a single-use invite code for you to send them manually.
NameEmailPhoneRequestedStatus Actions
Rachel Okonkwo r.okonkwo@example.com +44 7700 900118 04/03/2025 waiting
Liam Brennan l.brennan@example.com +44 7700 900204 28/02/2025 approved
Mira Sandoval m.sandoval@example.com +44 7700 900377 21/02/2025 rejected
Invite codes · loaded, with the three derived states
Invite codes
Create and manage signup invite codes.
CodeUsesStatusNoteCreatedActions
FLUX-2K4M 0 / 1 active For the design review 04/03/2025
FLUX-8QRT 1 / 1 exhausted Minted on approval 28/02/2025
FLUX-VX30 2 / 5 inactive Turned off after the trial 19/02/2025
Approved · the minted code, shown once
Approved — invite code minted
Send this single-use code to the applicant. It stays listed in the codes table below.
FLUX-2K4M
Load failed · there is no error screen, only a toast
Access requests
People who requested access. Approving mints a single-use invite code for you to send them manually.

No access requests yet.

Failed to load access data
The request did not complete.

Three details worth reading off the source. The waitlist status badge is a three-way map: approved goes to the default violet, rejected to destructive, and anything else, including "waiting", falls through to secondary. The primary button's label swaps to "Re-issue code" once a row is approved, so the same control mints a fresh code rather than doing nothing. And Reject only renders while the status is exactly waiting: a rejected row cannot be rejected again, but it can still be approved, which is the panel's undo.

The invite code status is derived, not stored. A code is "exhausted" when used_count >= max_uses, which wins over everything else, and only a code that is both active and not exhausted gets the violet default badge. Everything else is grey. That means an exhausted code and a deactivated code look identical, and the action button still reads "Deactivate" on an exhausted one, because the button tracks the stored active flag while the badge tracks the derived state. The FLUX-8QRT row above is that disagreement, reproduced as shipped.

Failure is a toast, not a state. There is no error branch in the render: a failed load fires a destructive toast and then the tab draws its empty state, so the last variant above is what "everything is broken" looks like. It is indistinguishable from "nobody has asked for access yet" once the toast goes away.

AdminSettingsTab
One radio group, three options, and it decides what happens to every stranger who reaches the login page. There is no Save button: picking an option is the save.
src/components/admin/settings-tab.tsx
Backend not configured

Supabase is not configured.

Loading

Loading…

Loaded · the three-way union
Signup mode
Controls what happens on the login page’s “Request access” flow.

Valid invite codes sign up immediately; everyone else joins the waitlist.

Valid invite codes sign up; the waitlist is closed.

No signups at all — codes and waitlist both rejected.

Optimistic · the instant you click "Closed", before the write lands

Selected. Nothing has been written yet.

Reverted · the write failed and the selection jumped back

Back where it was, without being touched.

Failed to update signup mode
The change was not saved.
Saved · the confirming toast names the new mode
Signup mode updated
invite_only

The save is optimistic with a rollback, so there is a state the code has and the design never names. Clicking an option moves the dot immediately, then writes. If the write fails, the previous value is put back and a destructive toast fires. So the radio can visibly change and then visibly change back on its own, which is the middle two variants above. Nothing is disabled while the write is in flight: a fast hand can pick a third option on top of a request that has not answered yet.

The confirming toast prints the raw union value. The success toast's description is the mode's stored identifier, not the human label from the radio directly above it, so choosing "Invite only" confirms with invite_only. Reproduced as shipped.

A failed load leaves this tab on "Loading…" forever. The tab shows the loading line while the mode is null, and the mode is only ever set on a successful read. If the read errors, a toast fires and nothing sets the value, so the loading line stays. Found while forcing this component into every branch; not fixed here.

Looking, not touching

3 components

Three read-only tabs. None of them has a single control that changes anything: they are tables and counters. That is a deliberate choice in one case and simply the job in the other two.

UsersTab
The roster: who has an account, when they made it, when they last signed in. Three columns and nothing else. There is no delete, no disable, no password reset, and that is on purpose.
src/components/admin/users-tab.tsx
Backend not configured

Supabase is not configured.

Loading

Loading…

Loaded · note the third row
Users
Everyone with an account (3). Read-only, so accounts are never modified from here.
EmailSigned upLast sign-in
l.brennan@example.com28/02/2025, 09:14:2205/03/2025, 08:02:41
j.faraday@example.com14/01/2025, 17:41:0503/03/2025, 21:19:57
a.whitlock@example.com02/03/2025, 11:26:33never
Load failed · a toast over an empty table
Users
Everyone with an account (0). Read-only, so accounts are never modified from here.
EmailSigned upLast sign-in
Failed to load users
The request did not complete.

The literal string "never". When a user has an account but has not signed in yet, the last-sign-in cell renders the word never rather than a dash or a blank. It is the only place in the admin panel where a null is spelled out in words, and it is what makes an account that was created but never used visible at a glance.

Read-only is a decision, not an oversight. The pool of accounts this reads from is shared with another application, so a delete here would not be a Flux-scoped delete. Rather than build a mutation that could reach further than the person clicking it expects, the tab was shipped with no mutations at all and says so in its own description.

The failure variant is the same shape as the AccessTab one: an error is a toast, and the table underneath renders its header over nothing. The header count in the description reads (0) in that state, because it counts the rows it has rather than the rows that exist.

StatsTab
Seven counters and a table of who did what. It is the only place in Flux that says how much is in the product at all, and it exists mostly so the answer to "is anyone using this" does not require a query.
src/components/admin/stats-tab.tsx
Backend not configured

Supabase is not configured.

Loading

Loading…

Loaded · seven tiles, in the order the source lists them
Users
3
Entries
248
Notes
63
Journal entries
41
Audio journals
9
Waiting on waitlist
1
Active invite codes
1
Per-user activity · four right-aligned count columns
Per-user activity
EmailEntriesNotesJournalAudio
l.brennan@example.com11231225
j.faraday@example.com8718123
a.whitlock@example.com491471

The tiles are a Card with only a header: a CardDescription for the label and a CardTitle at text-3xl for the number, with the content and footer omitted entirely. The hierarchy is inverted on purpose, the small word on top and the big number under it, and it is done with size rather than with order.

This tab has no error state and no empty state. The loading line renders while loading is true or the stats object is null, and the object is only set on a successful read. A failed read therefore fires a toast and leaves the tab reading "Loading…" permanently. It is the same shape of gap as the Settings tab. Found by forcing the branch; not fixed here.

AuditTab
A log of the last 100 admin actions, newest first. It is the answer to "who approved that", and it is the only tab whose entire reason for existing is that the other four can be used to change things.
src/components/admin/audit-tab.tsx
Backend not configured

Supabase is not configured.

Loading

Loading…

Empty · a real, named empty state
Admin audit log
The last 0 admin actions, newest first.

No admin actions recorded yet.

Loaded · the Details cell truncates
Admin audit log
The last 4 admin actions, newest first.
WhenActionTargetDetails
05/03/2025, 08:41:12 invite_code.create FLUX-2K4M {"code":"FLUX-2K4M","max_uses":1,"note":"For the design review"}
04/03/2025, 19:03:50 signup_mode.update signup_mode {"from":"invite_or_waitlist","to":"invite_only"}
28/02/2025, 10:22:07 waitlist.approve l.brennan@example.com {"minted":"FLUX-8QRT","status":"approved"}
21/02/2025, 16:55:39 waitlist.reject m.sandoval@example.com {"status":"rejected"}
Load failed · the empty state, plus a toast
Admin audit log
The last 0 admin actions, newest first.

No admin actions recorded yet.

Failed to load audit log
The request did not complete.

The header counts what arrived, not what happened. The description reads "The last N admin actions", where N is the number of rows the component is holding, so it says 100 when the log is full and 4 when it has four. When the load fails it says 0 and then the empty state says nothing was ever recorded, which is the third variant above: a broken log and a clean one are the same picture.

The Details cell is the row's payload serialised straight to JSON and clipped with a max width, so what you see is the beginning of an object and no way to read the rest. There is no expand, no tooltip, no copy. The action labels and payloads shown above are invented for this page.