A new public /docs area covering the whole platform, organized by Diátaxis: tutorials (learning), how-to guides (tasks), reference (facts), and explanation (the why). 29 pages, each grounded in the actual routes, actions, schema, and UI — verified against the code and corrected for accuracy (e.g. admin-only vs admin+board gating, where the permissions matrix actually lives, how Stripe reconciliation matches). Implementation: - lib/docs/nav.ts is the single source of truth (categories + pages), driving the sidebar, static params, and prev/next. - Content authored as content/docs/<category>/<slug>.md, read at build time (the routes are statically generated), rendered by <DocsMarkdown> — reuses the existing react-markdown/GFM stack, no new dependencies. - (docs) route group with its own theme (docs.css) matching the public site's dark / monospace / lime aesthetic; sidebar with active-state, breadcrumb, prev/next, and a Diátaxis home. Public — signed-in users can read it too. - "Docs" link added to the landing nav. typecheck 0 · lint 0 errors · build ok (all /docs pages prerendered)
5.1 KiB
A member is one person's record in your space, stored in the space_members table. This page lists every member status and tier, the fields on the member record, and the settings that control who can see the directory. Manage members at /members; members edit their own record at /me.
Statuses
status is the member_status enum. It defaults to unverified. It governs whether a member can act in the app and whether they hold their role's privileges.
| Status | Can use the app | Holds role privileges | Shown on |
|---|---|---|---|
current |
Yes | Yes | All members |
late |
Yes | Yes | All members (dues lapsed) |
unverified |
Yes | No — pending approval | Pending Approval tab |
inactive |
No — blocked from all actions | No | Inactive tab |
Notes:
- The active set is
current,unverified, andlate. Only these three are loaded as a member's active membership;inactivemembers are blocked from every server action. lateis a dues state, not an authorization downgrade — a late member keeps their role and privileges.unverifiedis the approval gate. In arequire_approvalspace, a new member staysunverifiedand holds no privileged capability until an admin approves them, even if they redeemed a role-bearing invite. Approving setsstatustocurrentandapprovedtotrue.- The /members page tabs are All, Payment Issues, Pending Approval (
unverified), and Inactive. The All tab hidesinactivemembers.
Tiers
tier is the member_tier enum. It defaults to basic and records the membership level. It is set by an admin when adding or editing a member; it does not by itself grant permissions (roles do that).
| Tier |
|---|
plus |
basic |
associate |
Record fields
Key columns on space_members. user_id is nullable on purpose: admins can add "offline" members who have no auth account.
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
space_id |
uuid | Owning space |
user_id |
uuid, nullable | Auth account; null for offline members |
role |
member_role |
Default member; see roles |
tier |
member_tier |
Default basic |
tier_id |
uuid, nullable | FK to space_tiers; canonical custom-tier reference |
status |
member_status |
Default unverified |
approved |
boolean | Default true; set true on approval |
display_name |
text | Self- or admin-editable |
handle |
text | Self- or admin-editable |
email |
text | Unique per space when present |
phone |
text | |
bio |
text | Self-editable |
avatar_url |
text | |
has_card_access |
boolean | Door access flag |
payment_status |
text | Non-current value drives the Payment Issues tab |
payment_note |
text | |
last_payment_at / last_paid_at |
timestamptz | |
dues_paid_until |
timestamptz | Dues coverage end |
joined_at |
timestamptz | Sort key on /members |
stripe_customer_id |
text | |
skills / interests / willing_to / affiliations |
text[] | Self-editable profile arrays |
coi_last_disclosed_at |
timestamptz | Set when affiliations are disclosed |
onboarding_completed_at |
timestamptz, nullable | Set when onboarding is finished or skipped |
onboarding_progress |
jsonb | Default {}; tracks completed onboarding step IDs |
created_at / updated_at |
timestamptz |
Constraints: unique on (space_id, user_id), and a partial unique index on (space_id, email) when email is present.
Who edits what
- Self (at /me):
display_name,handle,phone,bio,skills,interests,willing_to, andaffiliations. Changing affiliations re-runs conflict-of-interest disclosure and updatescoi_last_disclosed_at. - Admin (at /members):
email,display_name,phone,handle,role,tier,status,has_card_access,payment_status, andpayment_note.joined_atis only set when adding a member;updateMemberdoes not accept it.
On the /members page, some per-member panels are gated by capability rather than role: the access-cards panel (card UID) requires door.manage, the certifications panel requires certifications.grant, and the submitted-forms panel requires forms.manage.
Directory visibility
Who can see the member directory is set per space by member_directory_visibility (the directory_visibility enum) under Member directory visibility in /settings. It defaults to members_visible.
| Value | Who sees the directory |
|---|---|
members_visible |
Any authenticated member (default) |
public_members_visible |
Any authenticated member; behaves identically to members_visible (there is no public, unauthenticated directory route) |
board_only |
Elevated roles only (admin, board, treasurer) |
member_count_visible |
Elevated roles only; treated identically to board_only — non-elevated members get the same restricted notice (no count is shown) |
When a member is not permitted to see the directory, /members shows a "restricted in this space" notice instead of the table.