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