Context. surface customer · domain onboarding-tenant · route /tenant/invite · auth signed-in · source apps/oshun/web/src/app/tenant/invite/page.tsx
Last walked. 2026-06-26 automated runtime coverage added - Playwright creates a real tenant invitation through the BFF, signs in as the invitee, accepts the token from this page, and reads the tenant-console cockpit state back through the BFF. Outbound email, tenant SSO/MFA, and full onboarding completion remain journey-level gaps.
Purpose#
Signed-in handoff page for a one-time tenant member invitation. It accepts an
inviteId + token pair through the tenant invitation BFF and then links the
member into onboarding.
Entry Points#
- Invite URL after authentication:
/tenant/invite?inviteId=<inviteId>&token=<token>&tenant=<tenantId>&next=/onboarding - Anonymous arrivals are protected by the app proxy and redirect to
/welcome?redirect=...before this page renders. nextis sanitized bysanitizeRedirectPath; default target is/onboarding.
Layout Regions#
LWebShellwithLCustomerNavactive on Profile.LMastheadtitled "Join your tenant." with the tenant id or membership fallback in the right rail.TenantInviteAcceptPanelbelow the masthead.
States#
- Ready - invite id and token are present; the accept button is enabled.
- Submitting - the accept button is disabled while the BFF request is in flight.
- Accepted - renders the accepted member id, tenant id, status, roles,
and a link to the sanitized
nextroute. - Missing - missing invite id or token disables submit and reports the incomplete link state.
- Error - maps BFF status codes to operator-readable invite messages: unauthenticated, token mismatch, already used/revoked, expired, or incomplete.
Interactions#
- Accept invitation
- Selector:
[data-tenant-invite-accept] - BFF:
POST /v1/admin/tenant-console/members/invite/:inviteId/accept - Body:
{ token } - On success, the panel moves to
data-tenant-invite-status="accepted".
- Selector:
- Continue to onboarding
- Selector:
[data-tenant-invite-next] - Target: sanitized
nextquery param, default/onboarding.
- Selector:
Data & Contracts#
- Reads: query params
inviteId,token,tenant, andnext. - Writes:
api.post('/v1/admin/tenant-console/members/invite/:inviteId/accept', { token })- The BFF creates or activates the member under the invite's tenant and marks the invitation accepted.
- Auth/role check: signed-in customer session. The BFF uses the current authenticated user as the accepted member.
- Post-condition proof: Playwright reads
GET /v1/tenant-console/members?tenantId=<tenantId>with an admin bearer and asserts the active member, accepted invitation, hidden token hash, role, and seat allocation.
E2E Coverage#
Spec: apps/oshun/web/e2e/tenant-member-actions.spec.ts
Coverage depth: partial. The browser page, protected anonymous redirect, live
BFF accept, and tenant-console read-back are covered. The outbound email that
generates the link, tenant SSO/MFA, full OnboardingWizard completion, first
tenant action, and tenant audit reconciliation remain journey-level gaps.
Cross-References#
- Journey:
../../journeys/tenant-onboard.md - Bulk invite journey:
../../journeys/tenant-bulk-member-invite-scim.md - Onboarding destination:
onboarding.md - Auth handoff:
../../shell/04-auth-session.md