Contract-freeze · UX approval · FFRNT-306 (epic FFRNT-304)

Org invitations v2 — Approval Frames

Inviting people into an organization, done properly: choose who (an existing account-holder or an external person), how to reach them (email or WhatsApp), their role and their member type (employee or customer); a brand-new invitee resolves the link, signs up (incl. Google / Facebook) and is auto-bound to the org; and the root owner invites external customers directly. These frames freeze the flows, screens, states and build inventory before any UI is written. Rendered in the fuse-seam design system (tokens only). No identity-vendor name appears — the browser only ever sees app.fuzefront.com and, for social sign-up, accounts.google.com / facebook.com.

Walk the flows

Flow 1 · Invite — OrgInviteFlow · route /settings/access
Frame (a) · route /settings/access

Invite modal

Invitee kind (existing / external), channel (email / WhatsApp), role (admin / member / viewer), member type (employee / customer), live review → POST /invitations.

Frame (b) · route /settings/access

Pending invitations

Per-org pending list on the Members tab: status pills, resend (rate-limited), revoke. GET /invitations.

Flow 2 · Accept → sign up → bind — InvitationAcceptFlow · route /invitations/accept
Frame (c) · route /invitations/accept

Accept · resolve token

Public landing resolves the token and previews org + role + member type before sign-up. GET /invitations/resolve.

Frame (d) · route /invitations/accept

Sign up & bind

Create account (Google / Facebook / email, locked to the invited address) → POST /invitations/accept auto-binds → confirms org + role + member type.

Flow 3 · Root customer invite — RootCustomerInviteFlow · route /root/customers
Frame (e) · route /root/customers

Root customer invite

Root owner invites an external person (Gmail / WhatsApp) as a customer; member type fixed, role defaults to Viewer. POST /invitations.

Shared · all flows

States & fail-closed

Loading / empty / error, channel & validation variants, and the fail-closed contract: 410 expired-revoked, 403 mismatch, 409 already-accepted, 409 last-owner, 403 forbidden.

Build inventory — approving a flow approves its slice of this plan

Flows (per-flow approval)

invite
OrgInviteFlow route /settings/access · approved: false
accept-bind
InvitationAcceptFlow route /invitations/accept · approved: false
root-customer-invite
RootCustomerInviteFlow route /root/customers · approved: false

React components

OrgInviteButtonInviteModal InviteeKindToggleInviteChannelToggle InviteeAccountPickerEmailInviteField PhoneE164FieldInviteRolePicker MemberTypeToggleInviteReviewSummary PendingInvitationsPanelPendingInvitationRow InviteStatusPillResendInviteButton RevokeInviteDialogInvitationAcceptCard InvitationSignupPanelInvitationBindSuccess InvitationErrorStateRootCustomerInviteForm

npm packages

@fuzefront/identity-ui @fuzefront/auth-ui @fuzefront/security-client
Primary home is @fuzefront/identity-ui (beside authz-admin, member-directory, employee-console). Reuses SocialButton from @fuzefront/auth-ui and the generated @fuzefront/security-client. Orchestrator decision: keep in identity-ui unless the multi-channel + public accept-bind scope earns an independent @fuzefront/org-invitations-ui package — flagged here, not assumed.

Design-system additions

frontend-engineer adds these to @fuzefront/design-system as a foundation PR before the feature UI depends on them — drawn here from tokens only, never one-offed:

Contract deltas

Invitations have no OpenAPI contract today — v1 is un-contracted Express (backend/src/routes/invitations.ts, organizations.ts). This approval commissions contract-designer to freeze the following in packages/security/openapi.yaml → @fuzefront/security-client:
● Awaiting approval — per flow

Design system: @fuzefront/design-system (fuse-seam) · Contract: packages/security/openapi.yaml → @fuzefront/security-client (commissioned) · Flag: fuzefront.identity.org-invitations (default OFF) · Siblings: authz-admin, member-directory, employee-console, auth-experience.
Approve per flow by setting approved: true (+ approver + date) on that flow in manifest.json, or reply @claude approve <flow> / @claude reject: <reason>.

Review · org-invitations-v2 approve or reject each flow ↓
inviteapproved · izzywdev
accept-bindapproved · izzywdev
root-customer-inviteapproved · izzywdev