Files
Danny White ec53175b8a refactor(ui): rename text-brand to text-primary (#50564)
## What kind of change does this PR introduce?

Refactor. Follow-up to #49871.

## What is the current behavior?

Branded (green) text still uses the `text-brand` classname while the
colour comes from `--primary`.

## What is the new behavior?

**Rename-only:** `text-brand` → `text-primary` across callsites and
docs. Leftover `bg-brand` / `border-brand` alias to `brand-default`. No
intentional colour changes in this PR.

This better matches how we treat our green in other components and
props, like `variant="primary"` for green buttons.

## To test

On light mode: smoke-test that branded text still looks like #49871
(readable green, not the bright fill).

-
[Homepage](https://zone-www-dot-com-git-dnywh-depr-316-text-brand-de2380-supabase.vercel.app/):
“Scale to millions” uses `text-primary`
- [Docs
homepage](https://docs-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/docs):
branded links like “More on self-hosting” are still readable
- [Typography
docs](https://design-system-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/design-system/docs/typography):
documents `text-primary`
- [Colour
usage](https://design-system-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/design-system/docs/color-usage):
`text-primary` is visibly darker than `bg-brand-default` _on light mode_
- [Studio auth
providers](https://studio-staging-git-dnywh-depr-316-text-brand-to-60fa6c-supabase.vercel.app/dashboard/project/_/auth/providers):
enabled provider badge text readable; status dot stays bright green

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

* **Style**
* Updated branded text, link hover states, icons, badges, indicators,
and highlighted content across the Design System, Docs, Studio, Learn,
UI Library, and marketing experiences to use the primary theme color.
* Updated syntax highlighting and table-of-contents styling for
consistent primary-color presentation.
  * Refined brand color fallback behavior for bright fills and borders.

* **Documentation**
* Updated color-usage and typography guidance to recommend the primary
text utility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-22 16:19:03 +10:00

271 lines
12 KiB
TypeScript

import { OAuthScope } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { CheckCircle2, ChevronRight, ChevronsLeftRight } from 'lucide-react'
import Image from 'next/image'
import { useRouter } from 'next/router'
import { toast } from 'sonner'
import { Button, cn, Collapsible, CollapsibleContent, CollapsibleTrigger } from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import { ScopeSection } from '../OAuthApps/AuthorizeRequesterDetails'
import { PERMISSIONS_DESCRIPTIONS } from '../OAuthApps/OAuthApps.constants'
import { ProjectClaimLayout } from './layout'
import { useApiAuthorizationApproveMutation } from '@/data/api-authorization/api-authorization-approve-mutation'
import { ApiAuthorizationResponse } from '@/data/api-authorization/api-authorization-query'
import { useOrganizationProjectClaimMutation } from '@/data/organizations/organization-project-claim-mutation'
import { OrganizationProjectClaimResponse } from '@/data/organizations/organization-project-claim-query'
import { useInvalidateProjectsInfiniteQuery } from '@/data/projects/org-projects-infinite-query'
import { BASE_PATH } from '@/lib/constants'
import type { Organization } from '@/types'
export const ProjectClaimConfirm = ({
selectedOrganization,
projectClaim,
requester,
setStep,
}: {
selectedOrganization: Organization
projectClaim: OrganizationProjectClaimResponse
requester: ApiAuthorizationResponse
setStep: (step: 'choose-org' | 'benefits' | 'confirm') => void
}) => {
const router = useRouter()
const { auth_id, token: claimToken } = useParams()
const { invalidateProjectsQuery } = useInvalidateProjectsInfiniteQuery()
const { mutateAsync: approveRequest, isPending: isApproving } =
useApiAuthorizationApproveMutation()
const { mutateAsync: claimProject, isPending: isClaiming } = useOrganizationProjectClaimMutation()
const onClaimProject = async () => {
try {
const response = await approveRequest({ id: auth_id!, slug: selectedOrganization.slug })
await claimProject({
slug: selectedOrganization.slug,
token: claimToken!,
})
toast.success('Project claimed successfully')
try {
// check if the redirect url is valid. If not, redirect the user to the org dashboard
const url = new URL(response.url)
window.location.href = url.toString()
} catch {
// invalidate the org projects to force them to be refetched
await invalidateProjectsQuery()
router.push(`/org/${selectedOrganization.slug}`)
}
} catch (error: any) {
toast.error(`Failed to claim project ${error.message}`)
}
}
const isLoading = isApproving || isClaiming
return (
<ProjectClaimLayout
title={
<>
Claim a project <span className="text-primary">{projectClaim?.project?.name}</span> from{' '}
<span className="text-primary">{requester?.name}</span>
</>
}
>
<div className="py-6 space-y-8 text-sm">
<div className="flex flex-col items-center mt-6">
<div className="flex items-center">
<div
className={cn(
'w-8 h-8 bg-center bg-no-repeat bg-cover flex items-center justify-center rounded-md'
)}
style={{
backgroundImage: !!requester.icon ? `url('${requester.icon}')` : 'none',
}}
>
{!requester.icon && (
<p className="text-foreground-light text-lg">{requester.name?.[0] ?? '?'}</p>
)}
</div>
<div className="flex items-center justify-center w-28 relative">
<div className="h-0.5 w-full border-2 border-dashed border-stronger" />
<div className="rounded-full border flex items-center justify-center h-10 w-full shadow-xs">
<ChevronsLeftRight className="text-muted-foreground" size={24} />
</div>
<div className="h-0.5 w-full border-2 border-dashed border-stronger z-10" />
</div>
<div className="w-8 h-8">
<Image
src={`${BASE_PATH}/img/supabase-logo.svg`}
alt="Supabase Logo"
className="w-full h-full"
width={100}
height={100}
/>
</div>
</div>
</div>
<div className="space-y-4 text-foreground-light">
<p>
By claiming the <span className="text-foreground">{projectClaim?.project?.name}</span>{' '}
project from <span className="text-foreground">{requester?.name}</span>, the following
will happen:
</p>
<ul className="space-y-3">
<li className="flex space-x-2">
<span>
<CheckCircle2 className="text-primary h-5 w-5" />
</span>
<span>
The project will be transferred to your Supabase organization{' '}
<span className="text-foreground">{selectedOrganization.name}.</span>{' '}
<a
href="#"
onClick={() => setStep('choose-org')}
className="text-foreground-light underline"
>
Choose another organization?
</a>
</span>
</li>
<li className="flex space-x-2">
<span>
<CheckCircle2 className="text-primary h-5 w-5" />
</span>
<span>
<span className="text-foreground">{requester?.name}</span> will receive API access
(permissions listed below) to all projects within your organization to continue
providing its functionality to the application you've built.
</span>
</li>
<li className="flex space-x-2">
<span>
<CheckCircle2 className="text-primary h-5 w-5" />
</span>
<span>
You'll be responsible for maintaining the project, which may include additional
costs.
</span>
</li>
</ul>
<Admonition type="caution">
<div className="text-foreground-light">
Upon claiming, the project may undergo a short downtime (less than 10 minutes) for
resizing.
</div>
</Admonition>
</div>
<div className="flex space-y-4 flex-col">
{requester.scopes.length === 0 ? (
<span className="text-foreground-light">
<span className="text-foreground">{requester?.name}</span> hasn't requested any
permissions to operate. This is normal and no action is needed from your side.
</span>
) : (
<Collapsible>
<CollapsibleTrigger className="pb-3 w-full flex items-center justify-between group">
<p className="text-sm text-foreground-light text-left">
<span className="font-foreground">List of permissions</span> that{' '}
<span className="text-foreground">{requester.name}</span> will have for the{' '}
<span className="text-amber-900">
selected organization and all of its projects.
</span>
</p>
<ChevronRight
size={16}
className="text-foreground-light transition-all group-data-open:rotate-90 w-20"
strokeWidth={1}
/>
</CollapsibleTrigger>
<CollapsibleContent
className={cn(
'flex flex-col gap-8 transition-all',
'data-closed:animate-collapsible-up data-open:animate-collapsible-down'
)}
>
<div>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.ANALYTICS}
hasReadScope={requester.scopes.includes(OAuthScope.ANALYTICS_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.ANALYTICS_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.ANALYTICS_CONFIG}
hasReadScope={requester.scopes.includes(OAuthScope.ANALYTICS_CONFIG_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.ANALYTICS_CONFIG_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.AUTH}
hasReadScope={requester.scopes.includes(OAuthScope.AUTH_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.AUTH_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.DATABASE}
hasReadScope={requester.scopes.includes(OAuthScope.DATABASE_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.DATABASE_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.DOMAINS}
hasReadScope={requester.scopes.includes(OAuthScope.DOMAINS_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.DOMAINS_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.EDGE_FUNCTIONS}
hasReadScope={requester.scopes.includes(OAuthScope.EDGE_FUNCTIONS_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.EDGE_FUNCTIONS_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.ENVIRONMENT}
hasReadScope={requester.scopes.includes(OAuthScope.ENVIRONMENT_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.ENVIRONMENT_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.ORGANIZATIONS}
hasReadScope={requester.scopes.includes(OAuthScope.ORGANIZATIONS_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.ORGANIZATIONS_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.PROJECTS}
hasReadScope={requester.scopes.includes(OAuthScope.PROJECTS_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.PROJECTS_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.REST}
hasReadScope={requester.scopes.includes(OAuthScope.REST_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.REST_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.SECRETS}
hasReadScope={requester.scopes.includes(OAuthScope.SECRETS_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.SECRETS_WRITE)}
/>
<ScopeSection
description={PERMISSIONS_DESCRIPTIONS.STORAGE}
hasReadScope={requester.scopes.includes(OAuthScope.STORAGE_READ)}
hasWriteScope={requester.scopes.includes(OAuthScope.STORAGE_WRITE)}
/>
</div>
</CollapsibleContent>
</Collapsible>
)}
</div>
</div>
<div className="flex justify-center sticky bottom-0">
<Button
variant="primary"
size="medium"
loading={isLoading}
disabled={isLoading}
onClick={onClaimProject}
>
Claim project {projectClaim?.project?.name}
</Button>
</div>
</ProjectClaimLayout>
)
}