AddHookDropdown.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { PermissionAction } from '@supabase/shared-types/out/constants'
  2. import { useParams } from 'common'
  3. import { ChevronDown } from 'lucide-react'
  4. import { useMemo } from 'react'
  5. import {
  6. Button,
  7. DropdownMenu,
  8. DropdownMenuContent,
  9. DropdownMenuItem,
  10. DropdownMenuLabel,
  11. DropdownMenuSeparator,
  12. DropdownMenuTrigger,
  13. } from 'ui'
  14. import { Hook, HOOK_DEFINITION_TITLE, HOOKS_DEFINITIONS } from './hooks.constants'
  15. import { extractMethod, isValidHook } from './hooks.utils'
  16. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  17. import { InlineLink } from '@/components/ui/InlineLink'
  18. import { useAuthConfigQuery } from '@/data/auth/auth-config-query'
  19. import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
  20. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  21. import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
  22. interface AddHookDropdownProps {
  23. buttonText?: string
  24. align?: 'end' | 'center'
  25. type?: 'primary' | 'default'
  26. open?: boolean
  27. onOpenChange?: (open: boolean) => void
  28. onSelectHook: (hook: HOOK_DEFINITION_TITLE) => void
  29. }
  30. export const AddHookDropdown = ({
  31. buttonText = 'Add hook',
  32. align = 'end',
  33. type = 'primary',
  34. open,
  35. onOpenChange,
  36. onSelectHook,
  37. }: AddHookDropdownProps) => {
  38. const { ref: projectRef } = useParams()
  39. const { data: organization } = useSelectedOrganizationQuery()
  40. const { data: authConfig } = useAuthConfigQuery({ projectRef })
  41. const { can: canUpdateAuthHook } = useAsyncCheckPermissions(PermissionAction.AUTH_EXECUTE, '*')
  42. const { getEntitlementSetValues: getEntitledHookSet } = useCheckEntitlements('auth.hooks')
  43. const entitledHookSet = getEntitledHookSet()
  44. const { availableHooks, nonAvailableHooks } = useMemo(() => {
  45. const allHooks: Hook[] = HOOKS_DEFINITIONS.map((definition) => ({
  46. ...definition,
  47. enabled: authConfig?.[definition.enabledKey] || false,
  48. method: extractMethod(
  49. authConfig?.[definition.uriKey] || '',
  50. authConfig?.[definition.secretsKey] || ''
  51. ),
  52. }))
  53. const availableHooks: Hook[] = allHooks.filter(
  54. (h) => !isValidHook(h) && entitledHookSet.includes(h.entitlementKey)
  55. )
  56. const nonAvailableHooks: Hook[] = allHooks.filter(
  57. (h) => !isValidHook(h) && !entitledHookSet.includes(h.entitlementKey)
  58. )
  59. return { availableHooks, nonAvailableHooks }
  60. }, [entitledHookSet, authConfig])
  61. if (!canUpdateAuthHook) {
  62. return (
  63. <ButtonTooltip
  64. disabled
  65. type={type}
  66. tooltip={{
  67. content: { side: 'bottom', text: 'You need additional permissions to add auth hooks' },
  68. }}
  69. >
  70. {buttonText}
  71. </ButtonTooltip>
  72. )
  73. }
  74. return (
  75. <DropdownMenu open={open} onOpenChange={onOpenChange}>
  76. <DropdownMenuTrigger asChild>
  77. <Button type={type} iconRight={<ChevronDown />}>
  78. {buttonText}
  79. </Button>
  80. </DropdownMenuTrigger>
  81. <DropdownMenuContent className="w-76" align={align}>
  82. <div>
  83. {availableHooks.length === 0 && (
  84. <DropdownMenuLabel className="text-foreground-light">
  85. All available hooks have been added
  86. </DropdownMenuLabel>
  87. )}
  88. {availableHooks.map((h) => (
  89. <DropdownMenuItem key={h.title} onClick={() => onSelectHook(h.title)}>
  90. {h.title}
  91. </DropdownMenuItem>
  92. ))}
  93. </div>
  94. {nonAvailableHooks.length > 0 && (
  95. <>
  96. {availableHooks.length > 0 && <DropdownMenuSeparator />}
  97. <DropdownMenuLabel className="grid gap-1 bg-surface-200">
  98. <p className="text-foreground-light">Team or Enterprise Plan required</p>
  99. <p className="text-foreground-lighter text-xs">
  100. The following hooks are not available on{' '}
  101. <InlineLink href={`/org/${organization?.slug ?? '_'}/billing`}>
  102. your plan
  103. </InlineLink>
  104. .
  105. </p>
  106. </DropdownMenuLabel>
  107. {nonAvailableHooks.map((h) => (
  108. <DropdownMenuItem key={h.title} disabled={true}>
  109. {h.title}
  110. </DropdownMenuItem>
  111. ))}
  112. </>
  113. )}
  114. </DropdownMenuContent>
  115. </DropdownMenu>
  116. )
  117. }