Files
supabase/apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.utils.ts
Joshen Lim 37b072aac2 Improve UI for org invites if MFA is enforced (#47067)
## Context

When opening an invite to join an organization that's enforced MFA for
their members, if a member does not have MFA enabled yet, they'll see
this UI which is confusing as there's no clear direction on what to do
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/ca2d1047-20bf-40ca-a9ea-91e81c390e40"
/>

## Changes involved
- Updating the UI to consider this error message and prompt users to set
up MFA
<img width="501" height="317" alt="image"
src="https://github.com/user-attachments/assets/d074ac6d-fd74-4fe0-9078-473fd2401045"
/>
- Small UI nudges to account security page
  - Tight copywriting to explicitly say MFA
- Opt to use Card instead of Collapsible (collapsible seems unnecessary
given that this is the only UI on this page)
  - Before:
<img width="811" height="360" alt="image"
src="https://github.com/user-attachments/assets/1412da3b-3903-4966-85ea-46e0ff443177"
/>
  - After:
<img width="817" height="370" alt="image"
src="https://github.com/user-attachments/assets/02d5a2f5-8c1f-4f78-8a20-10c7a4ff563c"
/>
- Tiny change to the user dropdown, say "account" instead of "account
preferences" + change icon
- This imo aligns better as the account page covers more than just
preferences
  - Before:
<img width="307" height="178" alt="image"
src="https://github.com/user-attachments/assets/fea43cac-9b0c-4fe4-94a3-946ed0925901"
/>
  - After:
<img width="300" height="183" alt="image"
src="https://github.com/user-attachments/assets/800357fe-222f-49b8-b52b-ce4fabff7b95"
/>

## To test
- [ ] Have an organization on paid plan with MFA enforced
- [ ] Invite a user that doesn't have MFA enabled
- [ ] Try to join the organization with that user

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Organization invites now detect and handle MFA requirements with
specific error messaging
* Redesigned Multi-factor authentication section on account security
page

* **Improvements**
  * Updated TOTP authenticator help text for clarity
  * Updated account menu navigation label

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
2026-06-18 18:50:40 +08:00

104 lines
2.8 KiB
TypeScript

import type { OrganizationInviteByToken } from '@/data/organization-members/organization-invitation-token-query'
import type { ResponseError } from '@/types'
type OrganizationInviteStatusVariables = {
data?: OrganizationInviteByToken
error?: ResponseError | null
isErrorInvitation: boolean
isLoadingInvitation: boolean
isLoadingProfile: boolean
isLoggedIn: boolean
isRouterReady: boolean
isSuccessInvitation: boolean
profileExists: boolean
}
export type OrganizationInviteStatus =
| 'signed-out'
| 'loading'
| 'ready'
| 'wrong-account'
| 'expired'
| 'invalid'
| 'no-longer-valid'
| 'error'
export function getOrganizationInviteStatus({
data,
error,
isErrorInvitation,
isLoadingInvitation,
isLoadingProfile,
isLoggedIn,
isRouterReady,
isSuccessInvitation,
profileExists,
}: OrganizationInviteStatusVariables): OrganizationInviteStatus {
const isSignedOut = !isLoggedIn || (!profileExists && !isLoadingProfile)
if (isSignedOut) return 'signed-out'
if (isLoadingProfile || isLoadingInvitation || !isRouterReady) return 'loading'
if (error?.code === 401 && error?.message.includes('Failed to retrieve organization')) {
return 'no-longer-valid'
}
if (
(isSuccessInvitation && !!data?.token_does_not_exist) ||
(isErrorInvitation && error?.code === 404)
) {
return 'invalid'
}
if (isErrorInvitation) return 'error'
if (isSuccessInvitation && !!data?.expired_token) return 'expired'
if (isSuccessInvitation && !!data && !data.email_match) return 'wrong-account'
return 'ready'
}
export function getOrganizationInviteContent({
data,
error,
isSignUpEnabled,
status,
}: {
data?: OrganizationInviteByToken
error?: ResponseError | null
isSignUpEnabled: boolean
status: OrganizationInviteStatus
}) {
const signedOutDescription = `Sign in${
isSignUpEnabled ? ' or create an account' : ''
} to view this invitation`
if (status === 'signed-out') {
return {
title: 'View invitation',
description: signedOutDescription,
}
}
if (status === 'ready') {
return {
title: `Join ${data?.organization_name ?? 'an organization'}`,
description: 'You have been invited to join this Supabase organization',
}
}
if (status === 'wrong-account') return { title: 'Wrong account' }
if (status === 'expired') return { title: 'Invite expired' }
if (status === 'invalid') return { title: 'Invite invalid' }
if (status === 'no-longer-valid') return { title: 'Invite no longer available' }
if (status === 'error') {
if (error?.message.includes('MFA required')) {
return {
title: 'Set up MFA for your account',
description: 'MFA needs to be enabled on your account to join this organization',
}
} else return { title: 'Unable to load invitation' }
}
return {}
}