OrganizationCard.tsx111 lines · main
1import { useIsMFAEnabled } from 'common'
2import { Boxes, Lock, Plus } from 'lucide-react'
3import Link from 'next/link'
4import { Fragment, type ReactNode } from 'react'
5import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
6
7import { ActionCard } from '@/components/ui/ActionCard'
8import PartnerIcon from '@/components/ui/PartnerIcon'
9import { useOrgProjectsInfiniteQuery } from '@/data/projects/org-projects-infinite-query'
10import type { Organization } from '@/types'
11
12export const OrganizationCard = ({
13 organization,
14 href,
15 isLink = true,
16 className,
17 onClick,
18 description,
19}: {
20 organization: Organization
21 href?: string
22 isLink?: boolean
23 className?: string
24 onClick?: () => void
25 description?: ReactNode
26}) => {
27 const isUserMFAEnabled = useIsMFAEnabled()
28 const isPlatformOrg = organization.plan?.id === 'platform'
29 const shouldRenderDefaultDescription = description === undefined
30 const { data } = useOrgProjectsInfiniteQuery(
31 { slug: organization.slug },
32 { enabled: !isPlatformOrg && shouldRenderDefaultDescription }
33 )
34 const numProjects = data?.pages[0].pagination.count ?? 0
35 const isMfaRequired = organization.organization_requires_mfa
36
37 const renderContent = () => (
38 <ActionCard
39 bgColor="bg border"
40 className={cn(
41 'flex items-center min-h-[70px] [&>div]:w-full [&>div]:items-center max-h-min',
42 className
43 )}
44 icon={<Boxes size={18} strokeWidth={1} className="text-foreground" />}
45 title={organization.name}
46 onClick={onClick}
47 description={
48 shouldRenderDefaultDescription ? (
49 <div className="flex items-center justify-between text-xs text-foreground-light font-sans">
50 <div className="flex items-center gap-x-1">
51 <span>{organization.plan.name} Plan</span>
52 {numProjects > 0 && (
53 <>
54 <span className="text-foreground-lighter">·</span>
55 <span>
56 {numProjects} project{numProjects > 1 ? 's' : ''}
57 </span>
58 </>
59 )}
60 </div>
61 <div className="flex items-center gap-x-2">
62 <PartnerIcon organization={organization} />
63 {isMfaRequired && (
64 <Tooltip>
65 <TooltipTrigger className="cursor-default">
66 <Lock size={12} />
67 </TooltipTrigger>
68 <TooltipContent side="bottom" className={!isUserMFAEnabled ? 'w-80' : ''}>
69 MFA enforced
70 </TooltipContent>
71 </Tooltip>
72 )}
73 </div>
74 </div>
75 ) : (
76 description
77 )
78 }
79 />
80 )
81
82 if (isLink) {
83 return <Link href={href ?? `/org/${organization.slug}`}>{renderContent()}</Link>
84 } else {
85 return <Fragment>{renderContent()}</Fragment>
86 }
87}
88
89export const CreateOrganizationCard = ({
90 params = {},
91 label = 'Create new organization',
92}: {
93 params?: { [key: string]: string }
94 label?: string
95}) => {
96 const createOrganizationHref = `/new${Object.keys(params).length > 0 ? `?${new URLSearchParams(params).toString()}` : ''}`
97
98 return (
99 <Link href={createOrganizationHref}>
100 <ActionCard
101 bgColor="bg border"
102 className={cn(
103 'flex items-center min-h-[70px] [&>div]:w-full [&>div]:items-center max-h-min',
104 'border-dashed shadow-none transition-colors group-hover:border-default group-hover:bg-surface-200'
105 )}
106 icon={<Plus size={18} strokeWidth={1} className="text-foreground" />}
107 title={label}
108 />
109 </Link>
110 )
111}