mirror of
https://github.com/supabase/supabase.git
synced 2026-06-12 08:29:15 +08:00
## Context Enforce `noUnusedLocals` and `noUnusedParameters` in tsconfig.json + fix all related issues
196 lines
7.2 KiB
TypeScript
196 lines
7.2 KiB
TypeScript
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 type="default">
|
||
<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>
|
||
)
|
||
}
|