Files
Alaister YoungandKevin Grüneberg 70da0f1d1d chore: cleanup packages (#27770)
* chore: cleanup packages

- Avoid circular imports
- Export API-types as types
- pg-format without depending on Node internal Buffer (not browser-compatible)
- Avoid importing from barrel files in ui dir

* chore: avoid barrel file imports in studio (#27771)

* chore: avoid barrel file imports

- Removes some unused imports
- Avoids barrel file import for faster builds + less memory

* add eslint rule

* type fixes

* delete layouts barrel

* delete components/grid barrel file

* delete components/grid/utils barrel file

* delete components/grid/components/common barrel file

* delete components/grid/components/editor barrel file

* delete components/grid/components/formatter barrel file

* delete components/grid/components/grid barrel file

* delete components/grid/components/header/filter barrel file

* remote components/grid/store barrel file

* remove components/interfaces/Auth/Policies barrel file

* delete components/interfaces/Settings/Logs barrel file

* delete components/ui/CodeEditor barrel file

* delete components/ui/Forms barrel file

* delete components/ui/Shimmers barrel file

* delete data/analytics barrel file

* delete hooks barrel file

* cleanup lib/common/fetch barrel file

* final * barral files cleanup

* global react-data-grid styles

* remove console.log

---------

Co-authored-by: Kevin Grüneberg <k.grueneberg1994@gmail.com>

* fix build

---------

Co-authored-by: Kevin Grüneberg <k.grueneberg1994@gmail.com>
2024-07-04 14:48:10 +08:00

175 lines
6.8 KiB
TypeScript

import { Check, Minus, User, X } from 'lucide-react'
import Image from 'next/legacy/image'
import { useState } from 'react'
import Table from 'components/to-be-cleaned/Table'
import { useOrganizationRolesV2Query } from 'data/organization-members/organization-roles-query'
import { OrganizationMember } from 'data/organizations/organization-members-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useFlag } from 'hooks/ui/useFlag'
import { useProfile } from 'lib/profile'
import { Badge, TooltipContent_Shadcn_, TooltipTrigger_Shadcn_, Tooltip_Shadcn_ } from 'ui'
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
import { getUserDisplayName, isInviteExpired } from '../Organization.utils'
import { MemberActions } from './MemberActions'
interface MemberRowProps {
member: OrganizationMember
}
export const MemberRow = ({ member }: MemberRowProps) => {
const { profile } = useProfile()
const { data: projects } = useProjectsQuery()
const selectedOrganization = useSelectedOrganization()
const [hasInvalidImg, setHasInvalidImg] = useState(false)
const projectLevelPermissionsEnabled = useFlag('projectLevelPermissions')
const { data: roles, isLoading: isLoadingRoles } = useOrganizationRolesV2Query({
slug: selectedOrganization?.slug,
})
const memberIsUser = member.primary_email == profile?.primary_email
const isInvitedUser = Boolean(member.invited_id)
const isEmailUser = member.username === member.primary_email
const isFlyUser = Boolean(member.primary_email?.endsWith('customer.fly.io'))
// [Joshen] From project role POV, mask any roles for other projects
const isObfuscated =
member.role_ids.filter((id) => {
const role = [
...(roles?.org_scoped_roles ?? []),
...(roles?.project_scoped_roles ?? []),
].find((role) => role.id === id)
const isOrgScope = role?.project_ids === null
if (isOrgScope) return false
const appliedProjects = (role?.project_ids ?? [])
.map((id) => {
return projects?.find((p) => p.id === id)?.name ?? ''
})
.filter((x) => x.length > 0)
return appliedProjects.length === 0
}).length > 0
return (
<Table.tr>
<Table.td>
<div className="flex items-center space-x-4">
<div>
{isInvitedUser || isEmailUser || isFlyUser || hasInvalidImg ? (
<div className="w-[40px] h-[40px] bg-surface-100 border border-overlay rounded-full text-foreground-lighter flex items-center justify-center">
<User size={20} strokeWidth={1.5} />
</div>
) : (
<Image
alt={member.username}
src={`https://github.com/${member.username}.png?size=80`}
width="40"
height="40"
className="border rounded-full"
onError={() => {
setHasInvalidImg(true)
}}
/>
)}
</div>
<div className="flex item-center gap-x-2">
{isInvitedUser === undefined ? (
<p className="text-foreground-light truncate">{member.primary_email}</p>
) : (
<p className="text-foreground truncate">{getUserDisplayName(member)}</p>
)}
{member.primary_email === profile?.primary_email && <Badge color="scale">You</Badge>}
</div>
</div>
</Table.td>
<Table.td>
{isInvitedUser && member.invited_at && (
<Badge variant={isInviteExpired(member.invited_at) ? 'destructive' : 'warning'}>
{isInviteExpired(member.invited_at) ? 'Expired' : 'Invited'}
</Badge>
)}
</Table.td>
<Table.td>
<div className="flex items-center justify-center">
{member.mfa_enabled ? (
<Check className="text-brand" strokeWidth={2} size={20} />
) : (
<X className="text-foreground-light" strokeWidth={1.5} size={20} />
)}
</div>
</Table.td>
<Table.td className="max-w-64">
{isLoadingRoles ? (
<ShimmeringLoader className="w-32" />
) : isObfuscated ? (
<Minus strokeWidth={1.5} size={20} />
) : (
member.role_ids.map((id) => {
const orgScopedRole = (roles?.org_scoped_roles ?? []).find((role) => role.id === id)
const projectScopedRole = (roles?.project_scoped_roles ?? []).find(
(role) => role.id === id
)
const role = orgScopedRole || projectScopedRole
const roleName = (role?.name ?? '').split('_')[0]
const projectsApplied =
role?.project_ids === null
? projects?.map((p) => p.name) ?? []
: (role?.project_ids ?? [])
.map((id) => {
return projects?.find((p) => p.id === id)?.name ?? ''
})
.filter((x) => x.length > 0)
if (projectsApplied.length === 0) return null
return (
<div key={`role-${id}`} className="flex items-center gap-x-2">
<p>{roleName}</p>
{projectLevelPermissionsEnabled && (
<>
<span>•</span>
{projectsApplied.length === 1 ? (
<span className="text-foreground truncate" title={projectsApplied[0]}>
{projectsApplied[0]}
</span>
) : (
<Tooltip_Shadcn_>
<TooltipTrigger_Shadcn_ asChild>
<span className="text-foreground">
{role?.project_ids === null
? 'Organization'
: `${projectsApplied.length} project${projectsApplied.length > 1 ? 's' : ''}`}
</span>
</TooltipTrigger_Shadcn_>
<TooltipContent_Shadcn_ side="bottom" className="flex flex-col gap-y-1">
{projectsApplied
?.slice(0, 2)
.map((name) => <span key={name}>{name}</span>)}
{projectsApplied.length > 2 && (
<span>
And {projectsApplied.length - 2} other project
{projectsApplied.length > 4 ? 's' : ''}
</span>
)}
</TooltipContent_Shadcn_>
</Tooltip_Shadcn_>
)}
</>
)}
</div>
)
})
)}
</Table.td>
<Table.td>{!memberIsUser && <MemberActions member={member} />}</Table.td>
</Table.tr>
)
}