mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 17:29:45 +00:00
PR 6 of the multi-user accounts series. First user-facing surface
for the feature — the Members tab in Settings.
What lands
- `src/components/settings/members-tab.tsx` — roster + pending
invitations. Every member can see the roster (read-only).
Admin+ gets an inline role-select dropdown (excluding the
owner row and self), a Remove button (same restrictions), and
the "Pending invitations" section with revoke.
- `src/components/settings/invite-member-dialog.tsx` — two-step
modal. Form (role + expiry + optional label) → result
(read-only URL with copy-to-clipboard + a "Send via WhatsApp"
deep link). The result step is dismissed-once: the plaintext
token is only ever shown here and never resurfaced, so the
copy carries a warning ("Save this link now").
- `src/app/(dashboard)/settings/page.tsx` — adds `members` to
the tab list, gated behind the existing
`profiles.beta_features.includes('account_sharing')` flag
(same column migration 011 added; flip it via Supabase Studio
on a per-profile basis to dogfood). When the flag is off the
tab trigger AND content render nothing — the page degrades
to the original 5-tab layout. A user landing on
`/settings?tab=members` without the flag is silently bounced
to `?tab=profile` rather than seeing an empty pane.
Why a feature flag at all
- Locked decision from the planning phase: ship the schema and
API unconditionally (they're structural, harmless to existing
single-user installs) but keep the *user-visible* invite
surface behind a flag until the full UI series ships (Join
page, sidebar gating, webhook account routing). Mirrors how
migration 011 / Flows beta worked.
Role-gating
- The tab is reachable by any member of an account. Inside the
tab, mutation controls (Invite member button, role dropdown,
Remove button, pending invitations section + revoke buttons)
are wrapped in `<RequireRole min="admin">` or hidden via
`useAuth().canManageMembers` from #172.
- Server-side RPCs (set_member_role, remove_account_member from
#170; invitation create/revoke from #171) double-check role
anyway — the client gate is UX, not security.
Owner row
- Non-editable. Inline role select and Remove button are hidden
on the owner's row across the board. Owner role changes go
through the (deferred) Transfer Ownership flow.
Self row
- "You" badge on the caller's own row. Role select and Remove
are hidden on it — admins can't accidentally demote or remove
themselves, even though the server RPC would also catch it.
Email visibility
- Admin+ sees teammate emails; agent/viewer sees name + avatar
+ role + joined date only. Implemented server-side by the
`/api/account/members` route in #170; the component just
renders `member.email` when present.
UI patterns
- Mirrors existing tag-manager / template-manager card +
divide-y list layout and the slate-900/slate-700/primary
color tokens from the dark-only theme.
- Dialogs follow the established `bg-slate-900 border-slate-700`
pattern. Destructive actions get the `bg-red-600 hover:bg-red-700`
treatment with `Loader2` spinner.
- "Send via WhatsApp" anchor uses `buttonVariants` directly
rather than `<Button asChild>` — the wacrm Button is the
Base UI ButtonPrimitive and has no Radix-style asChild.
- Base UI Select's `onValueChange` is
`(value: string | null) => void`; every consumer guards on
null. We don't expose a clear affordance, so null shouldn't
fire at runtime — but typescript requires the guard.
Verification
- `npm run lint` → 0 errors (same 19 pre-existing warnings)
- `npm run typecheck` → clean
- `npm test` → 366/366 pass
- `npm run build` → succeeds; /settings stays static (data
fetches happen client-side via useEffect)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| auth | ||
| automations | ||
| broadcasts | ||
| contacts | ||
| dashboard | ||
| flows | ||
| inbox | ||
| layout | ||
| pipelines | ||
| settings | ||
| tremor | ||
| ui | ||