OrganizationCard.tsx 3.6 KB

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