Files
supabase__supabase/apps/studio/components/interfaces/Settings/General/ProjectAccessSection.tsx
Danny White 476d4a5851 refactor(ui): drop redundant Button variant="default" props (#50161)
## What kind of change does this PR introduce?

Mechanical cleanup on top of the Button default-variant change (#50160).

## What is the current behavior?

Many callsites still pass `variant="default"` even though that is now
the component default.

## What is the new behavior?

Removes redundant static `variant="default"` from legacy `Button` and
`ButtonTooltip` callsites. Keeps explicit defaults where they document
the API:

- `button-default.tsx` and `button-sizes.tsx` demos
- `DocsButton`, which pins neutral styling at the wrapper boundary

## To test

Studio:

- [Auth → Rate
Limits](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/auth/rate-limits):
dirty the form so Cancel appears; Cancel stays neutral, Save stays green
- [Project Settings → API
Keys](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/settings/api-keys):
`DocsButton` in the header actions stays neutral

Design system:

- [Design system →
Button](https://design-system-git-dnywh-dc924ac1-supabase.vercel.app/design-system/docs/components/button):
`button-default` / `button-sizes` still show explicit default styling;
Primary (green) is restricted to the Primary section (and `asChild`)

WWW:

- [www → Brand
assets](https://zone-www-dot-com-git-dnywh-dc924ac1-supabase.vercel.app/brand-assets):
Download logo kit / Download button kit stay neutral
2026-09-11 17:05:26 +10:00

196 lines
7.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from 'next/link'
import {
Badge,
Button,
Card,
CardContent,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from 'ui'
import {
PageSection,
PageSectionContent,
PageSectionMeta,
PageSectionSummary,
PageSectionTitle,
} from 'ui-patterns/PageSection'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { summarizeProjectAccess } from './General.utils'
import { AlertError } from '@/components/ui/AlertError'
import { useOrganizationRolesV2Query } from '@/data/organization-members/organization-roles-query'
import { useOrganizationMembersQuery } from '@/data/organizations/organization-members-query'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { useProfile } from '@/lib/profile'
export const ProjectAccessSection = () => {
const { data: project } = useSelectedProjectQuery()
const { data: organization } = useSelectedOrganizationQuery()
const { profile } = useProfile()
const isBranch = Boolean(project?.parent_project_ref)
const projectRef = project?.parent_project_ref ?? project?.ref
const {
data: organizationMembers = [],
error: organizationMembersError,
isPending: isLoadingOrganizationMembers,
isError: isErrorOrganizationMembers,
} = useOrganizationMembersQuery(
{ slug: organization?.slug },
{
enabled: !!organization?.slug,
}
)
const {
data: organizationRoles,
error: organizationRolesError,
isPending: isLoadingOrganizationRoles,
isError: isErrorOrganizationRoles,
} = useOrganizationRolesV2Query(
{ slug: organization?.slug },
{
enabled: !!organization?.slug,
}
)
const userMemberData = organizationMembers.find(
(member) => member.gotrue_id === profile?.gotrue_id
)
const orgScopedRoleIds = new Set(
(organizationRoles?.org_scoped_roles ?? []).map((role) => role.id)
)
const hasProjectScopedRoles = (organizationRoles?.project_scoped_roles ?? []).length > 0
const isOrgScopedRole = (userMemberData?.role_ids ?? []).some((roleId) =>
orgScopedRoleIds.has(roleId)
)
const hasLimitedVisibility = hasProjectScopedRoles && !isOrgScopedRole
const {
visibleMembers,
hiddenMembersCount,
projectMemberCount,
organizationMemberCount,
shouldShowOrgComparison,
hasOrganizationWideAccess,
} = summarizeProjectAccess({
organizationMembers,
roles: organizationRoles,
projectRef,
hasLimitedVisibility,
currentUserId: profile?.gotrue_id,
})
const isLoadingProjectAccess = isLoadingOrganizationMembers || isLoadingOrganizationRoles
const isErrorProjectAccess = isErrorOrganizationMembers || isErrorOrganizationRoles
const projectAccessError = organizationMembersError ?? organizationRolesError
if (isBranch) return null
const projectAccessTitle = hasLimitedVisibility
? 'You have limited visibility in this organization'
: shouldShowOrgComparison && hasOrganizationWideAccess
? 'Organization-wide access'
: 'Restricted project access'
const projectAccessDescription = hasLimitedVisibility
? 'Your access is limited to specific projects, so you can’t see all members or settings.'
: shouldShowOrgComparison
? hasOrganizationWideAccess
? `All ${organizationMemberCount} organization members can access this project.`
: `${projectMemberCount} of ${organizationMemberCount} organization members can access this project.`
: `${projectMemberCount} project member${projectMemberCount === 1 ? '' : 's'} currently ${projectMemberCount === 1 ? 'has' : 'have'} access.`
return (
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Project access</PageSectionTitle>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
{isErrorProjectAccess ? (
<AlertError error={projectAccessError} subject="Failed to retrieve project members" />
) : (
<Card>
{isLoadingProjectAccess ? (
<CardContent>
<GenericSkeletonLoader />
</CardContent>
) : (
<>
<CardContent className="flex flex-col gap-4">
<div className="flex flex-col @lg:flex-row @lg:items-center @lg:justify-between gap-3">
<div>
<p className="text-sm">{projectAccessTitle}</p>
<p className="text-sm text-foreground-light">{projectAccessDescription}</p>
</div>
{!!organization?.slug && (
<Button asChild>
<Link href={`/org/${organization.slug}/team`}>
{hasLimitedVisibility ? 'View team' : 'Manage members'}
</Link>
</Button>
)}
</div>
</CardContent>
{visibleMembers.length > 0 ? (
<CardContent className="p-0">
<Table>
<TableHeader>
<TableRow>
<TableHead>Member</TableHead>
<TableHead className="w-[180px]">Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{visibleMembers.map((member) => (
<TableRow key={member.id}>
<TableCell className="align-top">
<div className="flex items-center gap-2">
<p className="text-sm text-foreground break-all">{member.email}</p>
{member.id === profile?.gotrue_id && (
<Badge variant="default">You</Badge>
)}
</div>
</TableCell>
<TableCell className="align-top text-sm text-foreground-light w-[180px]">
{member.role ?? ''}
</TableCell>
</TableRow>
))}
{hiddenMembersCount > 0 && (
<TableRow className="[&>td]:hover:bg-inherit">
<TableCell colSpan={2}>
<p className="text-sm text-foreground-lighter">
+{hiddenMembersCount} more project member
{hiddenMembersCount === 1 ? '' : 's'}
</p>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</CardContent>
) : (
<CardContent className="pt-0">
<p className="text-sm text-foreground-lighter">
No visible project members in your current access scope.
</p>
</CardContent>
)}
</>
)}
</Card>
)}
</PageSectionContent>
</PageSection>
)
}