Files
supabase/apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.utils.ts
T
Danny White 9660b0075c refine organisation invite state helpers (#45813)
## What kind of change does this PR introduce?

Code cleanup. Follow-up to #45774.

## What is the current behavior?

The organisation invite interstitial derives invite states, titles, and
descriptions from nested conditional logic in the component. That makes
the component harder to scan and pushes too much state coverage into
render tests.

## What is the new behavior?

See #45774 for screenshots of the general UI before-and-after (which
this one builds upon). That PR also contains testing instructions.

Extracts the invite status and content decisions into small pure
helpers, then covers those helpers with focused unit tests.

The component keeps the user-facing render and interaction coverage,
including the invalid lookup regression where a 404 should render the
invalid invite state instead of raw backend copy.


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

## Summary by CodeRabbit

* **Refactor**
* Improved organization invite flow with enhanced error state handling
for expired, invalid, and wrong-account scenarios.
* Better consistency in error messages and user guidance throughout the
invite process.

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45813)

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-13 10:40:41 +10:00

97 lines
2.6 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
}
type OrganizationInviteContentVariables = {
data?: OrganizationInviteByToken
isSignUpEnabled: boolean
status: OrganizationInviteStatus
}
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,
isSignUpEnabled,
status,
}: OrganizationInviteContentVariables) {
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') return { title: 'Unable to load invitation' }
return {}
}