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

133 lines
5.1 KiB
TypeScript

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import dayjs from 'dayjs'
import { Check, Key, Trash } from 'lucide-react'
import { useState } from 'react'
import { toast } from 'sonner'
import { cn } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import CopyButton from '@/components/ui/CopyButton'
import { useClientSecretDeleteMutation } from '@/data/oauth-secrets/client-secret-delete-mutation'
import { Secret, useClientSecretsQuery } from '@/data/oauth-secrets/client-secrets-query'
import { useOrganizationMembersQuery } from '@/data/organizations/organization-members-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
export interface SecretRowProps {
secret: Secret
appId?: string
}
export const SecretRow = ({ secret, appId }: SecretRowProps) => {
const { slug } = useParams()
const [showDeleteModal, setShowDeleteModal] = useState(false)
const { can: canManageSecrets } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'oauth_apps')
const { data } = useClientSecretsQuery({ slug, appId })
const secrets = data?.client_secrets ?? []
const isLast = secrets.length === 1
const { data: members = [] } = useOrganizationMembersQuery({ slug })
const generatedBy = members.find((x) => x.gotrue_id === secret.created_by)
const generatedByName = generatedBy?.username ?? generatedBy?.primary_email ?? secret.created_by
const { mutate: deleteSecret, isPending: isDeleting } = useClientSecretDeleteMutation({
onSuccess: () => {
// Show success toast and close modal after successful deletion
toast.success('Successfully deleted client secret')
setShowDeleteModal(false)
},
})
const handleDelete = () => {
if (!slug) return console.error('Slug is required')
if (!appId) return console.error('App ID is required')
deleteSecret({ slug, appId, secretId: secret.id })
}
const isNew = secret.client_secret !== undefined
return (
<>
<div
className={cn(
'flex items-center justify-between p-4 border first:rounded-t last:rounded-b',
isNew && 'bg-background-alternative'
)}
>
<div className="flex flex-row gap-6 items-center">
<Key size={16} strokeWidth={2} />
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<div className="flex items-center gap-2">
{isNew && <Check size={14} className="text-primary" strokeWidth={3} />}
<p className="font-mono text-sm">
{isNew ? secret.client_secret : `${secret.client_secret_alias}${'*'.repeat(36)}`}
</p>
{isNew && secret.client_secret && (
<CopyButton text={secret.client_secret} variant="default" iconOnly />
)}
</div>
</div>
<div className="flex flex-col gap-0">
<p className="text-sm text-foreground-lighter">
Added {isNew ? 'now' : dayjs(secret.created_at).fromNow()} by {generatedByName}
</p>
{secret.last_used_at && (
<p className="text-sm text-foreground-lighter">
Last used {dayjs(secret.last_used_at).fromNow()}
</p>
)}
{!secret.last_used_at && !isNew && (
<p className="text-sm text-foreground-lighter">Never used</p>
)}
</div>
</div>
</div>
<div className="flex items-center gap-2">
<ButtonTooltip
className="w-7"
icon={<Trash />}
disabled={!appId || !canManageSecrets || isLast}
onClick={() => setShowDeleteModal(true)}
tooltip={{
content: {
className: 'w-64 text-center',
side: 'bottom',
text: !canManageSecrets
? 'You need additional permissions to delete client secrets'
: isLast
? 'You must have at least one client secret for the OAuth application to function.'
: undefined,
},
}}
/>
</div>
</div>
<ConfirmationModal
visible={showDeleteModal}
variant="destructive"
title="Confirm to delete client secret"
confirmLabel="Delete"
confirmLabelLoading="Deleting"
loading={isDeleting}
onCancel={() => setShowDeleteModal(false)}
onConfirm={handleDelete}
disabled={isLast}
alert={{
title: 'This action cannot be undone',
description: 'The client secret will be permanently removed and cannot be recovered.',
}}
>
<p className="text-sm text-foreground-light">
{isLast
? 'The last client secret cannot be deleted. Please generate a new secret before deleting this one.'
: 'Are you sure you want to delete this client secret?'}
</p>
</ConfirmationModal>
</>
)
}