mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 21:05:06 +03:00
## What kind of change does this PR introduce? Feature and design-system cleanup. Resolves DEPR-551. ## What is the current behavior? Admonition supports several overlapping content shapes, but it previously did not support a first-class success state or description-only usage cleanly. Title-only usage was also possible, which made some callouts read like floating headings without body copy. Docs MDX Admonitions could also pick up prose spacing around rich children, while the design-system Tailwind config emitted an ESM/CommonJS warning in the design-system app. ## What is the new behavior? Adds a `success` Admonition type, description-only support, and a stricter content contract: `title` or legacy `label` now requires either `description` or `children`. Existing title-only Studio callsites have been converted to description-only callouts. The design-system docs now include examples for description-only and success Admonitions, plus guidance for `title`, `description`, `children`, and legacy `label` usage. This also tightens Admonition body spacing so rich MDX children keep docs link/code styling without inheriting excessive prose margins, and renames the design-system Tailwind config to `tailwind.config.cjs` so it matches its CommonJS syntax. Warning and destructive alerts now explicitly set `text-foreground`, preventing nested Admonition titles from inheriting muted form-description colour after the Tailwind v4 cascade changes. | Before | After | | --- | --- | | <img width="1818" height="388" alt="Image" src="https://github.com/user-attachments/assets/283a1853-348a-4d74-a408-013957350e5e" /> | <img width="1380" height="462" alt="Image" src="https://github.com/user-attachments/assets/e5761e8e-3697-423b-805b-45110205099a" /> | | <img width="1398" height="550" alt="CleanShot 2026-04-28 at 15 12 41@2x" src="https://github.com/user-attachments/assets/982694d9-5461-4362-8bae-a6e2b4c60e8b" /> | <img width="1402" height="450" alt="CleanShot 2026-04-28 at 15 13 09@2x" src="https://github.com/user-attachments/assets/0b1257c4-6b58-4c39-a182-4861a9e378ee" /> | | <img width="1640" height="716" alt="CleanShot 2026-04-28 at 15 17 25@2x" src="https://github.com/user-attachments/assets/a5be4d5f-2bf7-4dc2-b396-56129fe64ec9" /> | <img width="1630" height="716" alt="CleanShot 2026-04-28 at 15 16 00@2x" src="https://github.com/user-attachments/assets/0d589252-aaf8-4efc-9d81-15ec4f99ec61" /> | | Design System Docs | | --- | | <img width="1646" height="1864" alt="CleanShot 2026-04-28 at 14 59 15@2x" src="https://github.com/user-attachments/assets/12d13595-8972-4fb2-a04a-fb916388ebb6" /> | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a "success" admonition variant and new example previews demonstrating success and description-only usages. * **Documentation** * Clarified admonition guidance: when to use title vs description vs children; added example sections for short callouts and success messages. * **Refactor** * Standardized UI by moving short/advisory text into description across the app and harmonized trailing punctuation. * **Style** * Ensured warning/destructive admonitions use consistent foreground text styling. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
152 lines
5.4 KiB
TypeScript
152 lines
5.4 KiB
TypeScript
import { useIsLoggedIn, useParams } from 'common'
|
|
import { CheckSquare } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useRouter } from 'next/router'
|
|
import { toast } from 'sonner'
|
|
import { Button, cn } from 'ui'
|
|
import { Admonition, GenericSkeletonLoader } from 'ui-patterns'
|
|
|
|
import { OrganizationInviteError } from './OrganizationInviteError'
|
|
import { useOrganizationAcceptInvitationMutation } from '@/data/organization-members/organization-invitation-accept-mutation'
|
|
import { useOrganizationInvitationTokenQuery } from '@/data/organization-members/organization-invitation-token-query'
|
|
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
|
import { useProfile } from '@/lib/profile'
|
|
|
|
export const OrganizationInvite = () => {
|
|
const router = useRouter()
|
|
const isLoggedIn = useIsLoggedIn()
|
|
const { profile, isLoading: isLoadingProfile } = useProfile()
|
|
const { slug, token } = useParams()
|
|
|
|
const isSignUpEnabled = useIsFeatureEnabled('dashboard_auth:sign_up')
|
|
|
|
const {
|
|
data,
|
|
error,
|
|
isSuccess: isSuccessInvitation,
|
|
isError: isErrorInvitation,
|
|
isPending: isLoadingInvitation,
|
|
} = useOrganizationInvitationTokenQuery(
|
|
{ slug, token },
|
|
{
|
|
retry: false,
|
|
refetchOnWindowFocus: false,
|
|
enabled: !!profile,
|
|
}
|
|
)
|
|
const hasError =
|
|
isErrorInvitation ||
|
|
(isSuccessInvitation && (data.token_does_not_exist || data.expired_token || !data.email_match))
|
|
const inviteIsNoLongerValid =
|
|
error?.code === 401 && error?.message.includes('Failed to retrieve organization')
|
|
|
|
const organizationName = isSuccessInvitation ? data?.organization_name : 'An organization'
|
|
const loginRedirectLink = `/sign-in?returnTo=${encodeURIComponent(`/join?token=${token}&slug=${slug}`)}`
|
|
const signupRedirectLink = `/sign-up?returnTo=${encodeURIComponent(`/join?token=${token}&slug=${slug}`)}`
|
|
|
|
const { mutate: joinOrganization, isPending: isJoining } =
|
|
useOrganizationAcceptInvitationMutation({
|
|
onSuccess: () => {
|
|
router.push('/organizations')
|
|
},
|
|
onError: (error) => {
|
|
toast.error(`Failed to join organization: ${error.message}`)
|
|
},
|
|
})
|
|
|
|
async function handleJoinOrganization() {
|
|
if (!slug) return console.error('Slug is required')
|
|
if (!token) return console.error('Token is required')
|
|
joinOrganization({ slug, token })
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'mx-auto overflow-hidden rounded-md border',
|
|
'border-muted bg-alternative text-center shadow-sm',
|
|
'md:w-[400px]'
|
|
)}
|
|
>
|
|
{!isLoggedIn || (!profile && !isLoadingProfile) ? (
|
|
<>
|
|
<Admonition
|
|
showIcon={false}
|
|
type="default"
|
|
description={`Sign in${isSignUpEnabled ? ' or create an account' : ''} first to view this invitation.`}
|
|
className="border-0 rounded-none text-left"
|
|
/>
|
|
<div className="p-4 border-muted border-t flex gap-x-3 justify-center">
|
|
<Button asChild type="default">
|
|
<Link href={loginRedirectLink}>Sign in</Link>
|
|
</Button>
|
|
{isSignUpEnabled && (
|
|
<Button asChild type="default">
|
|
<Link href={signupRedirectLink}>Create an account</Link>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</>
|
|
) : isLoadingProfile || isLoadingInvitation ? (
|
|
<div className="p-5">
|
|
<GenericSkeletonLoader />
|
|
</div>
|
|
) : inviteIsNoLongerValid ? (
|
|
<>
|
|
<Admonition
|
|
type="default"
|
|
title="Invalid invitation"
|
|
description="This organization invite is no longer valid as it has either been accepted or declined"
|
|
className="border-0 rounded-none text-left"
|
|
/>
|
|
<div className="p-4 border-muted border-t">
|
|
<Button type="default" asChild>
|
|
<Link href="/">Back to dashboard</Link>
|
|
</Button>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="flex flex-col gap-y-1 px-6 py-6">
|
|
<p className="text-sm text-foreground-light">You have been invited to join </p>
|
|
<p className="text-foreground text-2xl">{organizationName}</p>
|
|
{isSuccessInvitation && (
|
|
<p className="text-xs text-foreground-lighter">{`Organization slug: ${slug}`}</p>
|
|
)}
|
|
</div>
|
|
<div
|
|
className={cn('border-t border-muted', hasError ? 'bg-alternative' : 'bg-transparent')}
|
|
>
|
|
<div
|
|
className={cn(
|
|
'flex flex-col gap-4',
|
|
!isLoadingInvitation && !hasError && 'px-6 py-4'
|
|
)}
|
|
>
|
|
{hasError && (
|
|
<OrganizationInviteError data={data} error={error} isError={isErrorInvitation} />
|
|
)}
|
|
{isSuccessInvitation && !hasError && (
|
|
<div className="flex flex-row items-center justify-center gap-3">
|
|
<Button type="default" disabled={isJoining} asChild>
|
|
<Link href="/projects">Decline</Link>
|
|
</Button>
|
|
<Button
|
|
type="primary"
|
|
loading={isJoining}
|
|
disabled={isJoining}
|
|
onClick={handleJoinOrganization}
|
|
icon={<CheckSquare />}
|
|
>
|
|
Join organization
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|