ContextSearchCommands.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. 'use client'
  2. import { IS_PLATFORM } from 'common'
  3. import { Auth, EdgeFunctions, Storage } from 'icons'
  4. import { Database } from 'lucide-react'
  5. import { useMemo } from 'react'
  6. import type { ICommand } from 'ui-patterns/CommandMenu'
  7. import {
  8. CommandHeader,
  9. CommandMenuInput,
  10. CommandWrapper,
  11. PageType,
  12. useQuery,
  13. useRegisterCommands,
  14. useRegisterPage,
  15. useSetPage,
  16. } from 'ui-patterns/CommandMenu'
  17. import { COMMAND_MENU_SECTIONS } from './CommandMenu.utils'
  18. import { ContextSearchResults } from './ContextSearchResults'
  19. import { orderCommandSectionsByPriority } from './ordering'
  20. import type { SearchContextValue } from './SearchContext.types'
  21. import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
  22. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  23. interface SearchContextOption {
  24. value: SearchContextValue
  25. label: string
  26. pageName: string
  27. placeholder: string
  28. icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
  29. }
  30. const SEARCH_CONTEXT_OPTIONS: SearchContextOption[] = [
  31. {
  32. value: 'database-tables',
  33. label: 'Database Tables',
  34. pageName: 'Search Database Tables',
  35. placeholder: 'Search database tables...',
  36. icon: Database,
  37. },
  38. {
  39. value: 'auth-policies',
  40. label: 'RLS Policies',
  41. pageName: 'Search RLS Policies',
  42. placeholder: 'Search rls policies...',
  43. icon: Auth,
  44. },
  45. {
  46. value: 'edge-functions',
  47. label: 'Edge Functions',
  48. pageName: 'Search Edge Functions',
  49. placeholder: 'Search edge functions...',
  50. icon: EdgeFunctions,
  51. },
  52. {
  53. value: 'storage',
  54. label: 'Storage',
  55. pageName: 'Search Storage',
  56. placeholder: 'Search buckets...',
  57. icon: Storage,
  58. },
  59. ]
  60. function ContextSearchPage({
  61. context,
  62. placeholder,
  63. }: {
  64. context: SearchContextValue
  65. placeholder: string
  66. }) {
  67. const query = useQuery()
  68. return (
  69. <CommandWrapper>
  70. <CommandHeader>
  71. <CommandMenuInput placeholder={placeholder} />
  72. </CommandHeader>
  73. <ContextSearchResults context={context} query={query} />
  74. </CommandWrapper>
  75. )
  76. }
  77. export function useContextSearchCommands() {
  78. const { data: project } = useSelectedProjectQuery()
  79. const setPage = useSetPage()
  80. const {
  81. projectAuthAll: authEnabled,
  82. projectEdgeFunctionAll: edgeFunctionsEnabled,
  83. projectStorageAll: storageEnabled,
  84. } = useIsFeatureEnabled(['project_auth:all', 'project_edge_function:all', 'project_storage:all'])
  85. const pageDefinitions = [
  86. { title: 'Search Database Tables', context: 'database-tables' as const },
  87. { title: 'Search RLS Policies', context: 'auth-policies' as const },
  88. { title: 'Search Edge Functions', context: 'edge-functions' as const },
  89. { title: 'Search Storage', context: 'storage' as const },
  90. ]
  91. // Register pages - pageDefinitions is constant, so hooks are called in consistent order
  92. for (const { title, context } of pageDefinitions) {
  93. const placeholder =
  94. SEARCH_CONTEXT_OPTIONS.find((opt) => opt.value === context)?.placeholder ?? ''
  95. // eslint-disable-next-line react-hooks/rules-of-hooks
  96. useRegisterPage(title, {
  97. type: PageType.Component,
  98. component: () => <ContextSearchPage context={context} placeholder={placeholder} />,
  99. })
  100. }
  101. const contextCommands = useMemo(() => {
  102. return SEARCH_CONTEXT_OPTIONS.filter((option) => {
  103. let isFeatureEnabled = false
  104. switch (option.value) {
  105. case 'database-tables':
  106. isFeatureEnabled = true
  107. break
  108. case 'auth-policies':
  109. isFeatureEnabled = authEnabled
  110. break
  111. case 'edge-functions':
  112. isFeatureEnabled = edgeFunctionsEnabled
  113. break
  114. case 'storage':
  115. isFeatureEnabled = storageEnabled
  116. break
  117. }
  118. if (!isFeatureEnabled) return false
  119. // If self-hosted, show if feature is enabled
  120. if (!IS_PLATFORM) {
  121. return true
  122. }
  123. // only show when inside a project if not self-hosted
  124. return !!project
  125. }).map((option) => ({
  126. id: `search-${option.value}`,
  127. name: `Search ${option.label}...`,
  128. action: () => setPage(option.pageName),
  129. icon: () => <option.icon className="h-4 w-4" strokeWidth={1.5} />,
  130. })) as ICommand[]
  131. }, [setPage, authEnabled, edgeFunctionsEnabled, storageEnabled, project])
  132. useRegisterCommands(COMMAND_MENU_SECTIONS.QUERY, contextCommands, {
  133. orderSection: orderCommandSectionsByPriority,
  134. sectionMeta: { priority: 3 },
  135. enabled: !IS_PLATFORM || !!project,
  136. })
  137. }