mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 10:55:06 +03:00
## What kind of change does this PR introduce? Feature. Part of DEPR-279. ## What is the current behavior? The organization invite page has its own bespoke centered card and page-level Supabase logo. ## What is the new behavior? Introduces a minimal shared interstitial layout and migrates `/join` onto it as the first small connect-surface slice. The invite API and accept-invite mutation paths are unchanged. | Before | After | | --- | --- | | <img width="1024" height="794" alt="Supabase-F2325C57-D5DE-445D-8083-12EF8A1EE0CA" src="https://github.com/user-attachments/assets/b23dcc7a-c649-4b59-9393-9232d74f0c6b" /> | <img width="1024" height="794" alt="Join Organization Supabase-66CDA329-0531-4B12-AC32-A7E21931F876" src="https://github.com/user-attachments/assets/454917ce-1a96-4e50-b003-6c16a541b39a" /> | | <img width="1060" height="822" alt="CleanShot 2026-03-13 at 11 04 43@2x-2616AECB-8203-4439-A1CD-45AB18FC4CA8 1-584A0600-CCE0-4F16-9111-9BEB94BE85EC" src="https://github.com/user-attachments/assets/871c7dcb-120e-40cd-afc8-2cec95e4b7ae" /> | <img width="1024" height="794" alt="Join Organization Supabase-26AD978E-4CF9-4600-9885-082084349E94" src="https://github.com/user-attachments/assets/ee9bfaff-dde4-4366-abae-77dc8a95c4ef" /> | | <img width="1024" height="794" alt="Supabase-4993D74C-D62B-43B7-9681-826BE1591AC4" src="https://github.com/user-attachments/assets/1c411ae0-90e7-481d-a4cc-3eac26267291" /> | <img width="1024" height="794" alt="Join Organization Supabase-C84D4E4C-24F5-463D-B1D6-D11D3256596F" src="https://github.com/user-attachments/assets/688387a4-3c49-41db-b89c-7c5531e91aed" /> | | <img width="1024" height="794" alt="Supabase-D9BD2601-98A4-489D-A51D-CEB73F51FA6F" src="https://github.com/user-attachments/assets/6d1da65f-d655-4047-9f6a-db65f8c0a729" /> | <img width="1024" height="794" alt="Join Organization Supabase-50065F40-179A-4BD6-8F1D-6106FFD8A15C" src="https://github.com/user-attachments/assets/e61809f9-dcec-4e51-ba94-91b04010ec50" /> | ## Testing notes Staging invite emails are generated with the fixed staging dashboard origin, for example: ```text https://supabase.green/dashboard/join?token=...&slug=... ``` To test this PR preview with a real invite token, keep the path and query string from the email but replace the origin with the Vercel preview origin, for example: ```text https://studio-staging-git-dnywh-featconnect-interstitial-join-supabase.vercel.app/dashboard/join?token=...&slug=... ``` ### Manual state checks - **Signed out:** open the swapped invite URL in an incognito window or a browser signed out of Studio. Expected: `View invitation`, sign-in/create-account actions, and no loading skeleton hang. - **Wrong account:** sign in to the PR preview as an account that is not the invite recipient, then open the swapped invite URL. Expected: `Wrong account`, warning callout, and `Sign out`. - **Happy path:** sign in as the invited email address, then open the swapped invite URL. Expected: `Join {Organization}`, signed-in account row, `Accept invite`, and `Decline`. Accepting should join the organization. - **Invalid token:** alter one character in the token in the swapped invite URL. Expected: invalid invite state. - **No longer valid:** accept the invite once, then open the same swapped invite URL again. Expected: no-longer-valid/already-used state, depending on the backend response. ### Test-covered states Expired invites, generic backend error, loading, and create-account-disabled states are harder to force manually in staging. They are covered by `tests/components/OrganizationInvite.test.tsx`. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Redesigned the organization invitation experience with an interstitial layout, clearer early-return flows for signed-out, loading, expired/invalid, wrong-account, and accepted-invite states; primary CTA now reads “Accept invite”. * Streamlined error and sign-out flows with clearer, focused messaging. * **New Features** * Added a reusable interstitial layout and compact account row for invitation screens. * **Tests** * Added comprehensive tests covering invite states, accept/decline actions, and error handling. [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45774) <!-- end of auto-generated comment: release notes by coderabbit.ai -->
90 lines
2.2 KiB
TypeScript
90 lines
2.2 KiB
TypeScript
import { useRouter } from 'next/router'
|
|
import { Button } from 'ui'
|
|
import { Admonition } from 'ui-patterns'
|
|
|
|
import { OrganizationInviteByToken } from '@/data/organization-members/organization-invitation-token-query'
|
|
import { useSignOut } from '@/lib/auth'
|
|
import { useProfile } from '@/lib/profile'
|
|
import type { ResponseError } from '@/types'
|
|
|
|
interface OrganizationInviteError {
|
|
data?: OrganizationInviteByToken
|
|
error?: ResponseError | null
|
|
isError: boolean
|
|
isInvalidInvite?: boolean
|
|
}
|
|
|
|
export const OrganizationInviteError = ({
|
|
data,
|
|
error,
|
|
isError,
|
|
isInvalidInvite,
|
|
}: OrganizationInviteError) => {
|
|
const router = useRouter()
|
|
const signOut = useSignOut()
|
|
const { profile } = useProfile()
|
|
|
|
const handleSignOut = async () => {
|
|
await signOut()
|
|
router.reload()
|
|
}
|
|
|
|
if (isInvalidInvite) {
|
|
return (
|
|
<Admonition
|
|
type="warning"
|
|
description="Open the full invite link again, or ask the organization owner for a new invite."
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (isError) {
|
|
return (
|
|
<Admonition
|
|
type="danger"
|
|
description={error?.message ?? 'Open the full invite link again, or ask for a new invite.'}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (!data?.email_match) {
|
|
return (
|
|
<div className="flex flex-col gap-3">
|
|
<Admonition
|
|
type="warning"
|
|
description={
|
|
profile?.primary_email ? (
|
|
<>
|
|
You are signed in as{' '}
|
|
<span className="font-medium text-foreground">{profile.primary_email}</span>. Sign
|
|
in with the email address that received this invite.
|
|
</>
|
|
) : (
|
|
'Sign in with the email address that received this invite.'
|
|
)
|
|
}
|
|
/>
|
|
<Button type="default" block onClick={handleSignOut}>
|
|
Sign out
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (data.expired_token) {
|
|
return (
|
|
<Admonition
|
|
type="warning"
|
|
description="Ask the organization owner to send you a new invite."
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Admonition
|
|
type="warning"
|
|
description="Open the full invite link again, or ask the organization owner for a new invite."
|
|
/>
|
|
)
|
|
}
|