Files
supabase/apps/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx
Ivan Vasilov 56de26fe22 chore: Migrate the monorepo to use Tailwind v4 (#45318)
This PR migrates the whole monorepo to use Tailwind v4:
- Removed `@tailwindcss/container-queries` plugin since it's included by
default in v4,
- Bump all instances of Tailwind to v4. Made minimal changes to the
shared config to remove non-supported features (`alpha` mentions),
- Migrate all apps to be compatible with v4 configs,
- Fix the `typography.css` import in 3 apps,
- Add missing rules which were included by default in v3,
- Run `pnpm dlx @tailwindcss/upgrade` on all apps, which renames a lot
of classes
- Rename all misnamed classes according to
https://tailwindcss.com/docs/upgrade-guide#renamed-utilities in all
apps.

---------

Co-authored-by: Jordi Enric <jordi.err@gmail.com>
2026-04-30 10:53:24 +00:00

164 lines
5.9 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 { useParams } from 'common'
import { partition } from 'lodash'
import { AlertCircle } from 'lucide-react'
import { useMemo } from 'react'
import {
Card,
Loading,
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from 'ui'
import { Admonition } from 'ui-patterns'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { MemberRow } from './MemberRow'
import AlertError from '@/components/ui/AlertError'
import { useOrganizationRolesV2Query } from '@/data/organization-members/organization-roles-query'
import { useOrganizationMembersQuery } from '@/data/organizations/organization-members-query'
import { useProfile } from '@/lib/profile'
export interface MembersViewProps {
searchString: string
}
const MembersView = ({ searchString }: MembersViewProps) => {
const { slug } = useParams()
const { profile } = useProfile()
const {
data: members = [],
error: membersError,
isPending: isLoadingMembers,
isError: isErrorMembers,
isSuccess: isSuccessMembers,
} = useOrganizationMembersQuery({ slug })
const {
data: roles,
error: rolesError,
isSuccess: isSuccessRoles,
isError: isErrorRoles,
} = useOrganizationRolesV2Query({
slug,
})
const filteredMembers = useMemo(() => {
return !searchString
? members
: members.filter((member) => {
if (member.invited_at) {
return member.primary_email?.includes(searchString)
}
if (member.gotrue_id) {
return (
member.username.includes(searchString) || member.primary_email?.includes(searchString)
)
}
return false
})
}, [members, searchString])
const [[user], _otherMembers] = partition(
filteredMembers,
(m) => m.gotrue_id === profile?.gotrue_id
)
const userMember = members.find((m) => m.gotrue_id === profile?.gotrue_id)
const orgScopedRoleIds = (roles?.org_scoped_roles ?? []).map((r) => r.id)
const isOrgScopedRole = orgScopedRoleIds.includes(userMember?.role_ids?.[0] ?? -1)
// [Joshen] Temp wait on API level changes but I think it makes sense to hide invites for
// project scoped users since they can't see other members to begin with. Not a security issue nonetheless
const otherMembers = isOrgScopedRole
? _otherMembers
: _otherMembers.filter((x) => !('invited_id' in x))
const sortedMembers = otherMembers.sort((a, b) =>
(a.primary_email ?? '').localeCompare(b.primary_email ?? '')
)
return (
<>
{isLoadingMembers && <GenericSkeletonLoader />}
{isErrorMembers && (
<AlertError error={membersError} subject="Failed to retrieve organization members" />
)}
{isErrorRoles && (
<AlertError error={rolesError} subject="Failed to retrieve organization roles" />
)}
{isSuccessMembers && (
<div className="rounded-sm w-full overflow-hidden overflow-x-scroll">
<Card>
<Loading active={!filteredMembers}>
<Table>
<TableHeader>
<TableRow>
<TableHead key="header-user">Member</TableHead>
<TableHead key="header-mfa">MFA</TableHead>
<TableHead key="header-role">Role</TableHead>
<TableHead key="header-action" />
</TableRow>
</TableHeader>
<TableBody>
{[
...(isSuccessRoles && isSuccessMembers && !isOrgScopedRole
? [
<TableRow key="project-scope-notice">
<TableCell colSpan={12} className="p-0!">
<Admonition
type="note"
title="You have limited visibility in this organization"
description="Your access is limited to specific projects, so you cant see all members or settings."
className="border-0 rounded-none"
/>
</TableCell>
</TableRow>,
]
: []),
...(!!user ? [<MemberRow key={user.gotrue_id} member={user} />] : []),
...sortedMembers.map((member) => (
<MemberRow key={member.gotrue_id} member={member} />
)),
...(searchString.length > 0 && filteredMembers.length === 0
? [
<TableRow key="no-results" className="bg-panel-secondary-light">
<TableCell colSpan={12}>
<div className="flex items-center space-x-3 opacity-75">
<AlertCircle size={16} strokeWidth={2} />
<p className="text-foreground-light">
No members matched the search query "{searchString}"
</p>
</div>
</TableCell>
</TableRow>,
]
: []),
]}
</TableBody>
<TableFooter className="font-normal">
<TableRow className="border-b-0 [&>td]:hover:bg-inherit">
<TableCell colSpan={4} className="text-foreground-muted">
{searchString
? `${filteredMembers.length} of ${members.length} ${members.length === 1 ? 'member' : 'members'}`
: `${members.length || 0} ${members.length === 1 ? 'member' : 'members'}`}
</TableCell>
</TableRow>
</TableFooter>
</Table>
</Loading>
</Card>
</div>
)}
</>
)
}
export default MembersView