PolicySearchResults.tsx159 lines · main
1'use client'
2
3import { useParams } from 'common'
4import { Auth } from 'icons'
5import { Loader2 } from 'lucide-react'
6import { useMemo } from 'react'
7
8import {
9 EmptyState,
10 ResultsList,
11 SkeletonResults,
12 type SearchResult,
13} from './ContextSearchResults.shared'
14import { useDatabasePoliciesQuery } from '@/data/database-policies/database-policies-query'
15import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
16
17interface PolicySearchResultsProps {
18 query: string
19}
20
21export function PolicySearchResults({ query }: PolicySearchResultsProps) {
22 const { ref: projectRef } = useParams()
23 const { data: project } = useSelectedProjectQuery()
24
25 const trimmedQuery = query.trim()
26
27 const {
28 data: policies,
29 isLoading: isLoadingPolicies,
30 isError: isErrorPolicies,
31 } = useDatabasePoliciesQuery(
32 {
33 projectRef: project?.ref,
34 connectionString: project?.connectionString,
35 },
36 {
37 enabled: !!project?.ref,
38 }
39 )
40
41 const policyResults: SearchResult[] = useMemo(() => {
42 if (!policies) return []
43
44 const filtered = trimmedQuery
45 ? policies.filter((policy) => {
46 const searchLower = trimmedQuery.toLowerCase()
47 const policyName = policy.name?.toLowerCase() || ''
48 const tableName = policy.table?.toLowerCase() || ''
49 const schemaName = policy.schema?.toLowerCase() || ''
50 const fullTableName = `${schemaName}.${tableName}`
51 const command = policy.command?.toLowerCase() || ''
52
53 return (
54 policyName.includes(searchLower) ||
55 tableName.includes(searchLower) ||
56 schemaName.includes(searchLower) ||
57 fullTableName.includes(searchLower) ||
58 command.includes(searchLower)
59 )
60 })
61 : policies
62
63 // Limit results for performance
64 return filtered.slice(0, 20).map((policy) => {
65 const displayName = policy.name || 'Untitled Policy'
66 const tableDisplay =
67 policy.schema && policy.schema !== 'public'
68 ? `${policy.schema}.${policy.table}`
69 : policy.table || 'unknown table'
70 const commandDisplay = policy.command || 'ALL'
71 const description = `${commandDisplay} on ${tableDisplay}`
72
73 return {
74 id: String(policy.id),
75 name: displayName,
76 description,
77 }
78 })
79 }, [policies, trimmedQuery])
80
81 const totalPolicies = policies?.length ?? 0
82
83 const renderFooter = () => (
84 <div className="absolute bottom-0 left-0 right-0 flex items-center justify-between min-h-9 h-9 px-4 border-t bg-surface-200 text-xs text-foreground-light z-10">
85 <div className="flex items-center gap-x-2">
86 {isLoadingPolicies ? (
87 <span className="flex items-center gap-2">
88 <Loader2 size={14} className="animate-spin" /> Loading...
89 </span>
90 ) : (
91 <span>
92 Total: {totalPolicies.toLocaleString()} polic{totalPolicies !== 1 ? 'ies' : 'y'}
93 </span>
94 )}
95 </div>
96 </div>
97 )
98
99 if (isLoadingPolicies) {
100 return (
101 <div className="relative h-full flex flex-col">
102 <div className="flex-1 min-h-0 overflow-hidden">
103 <SkeletonResults />
104 </div>
105 {renderFooter()}
106 </div>
107 )
108 }
109
110 if (isErrorPolicies) {
111 return (
112 <div className="relative h-full flex flex-col">
113 <div className="flex-1 min-h-0 overflow-hidden">
114 <div className="h-full flex flex-col items-center justify-center py-12 px-4 gap-4 text-center text-foreground-lighter">
115 <Auth className="h-6 w-6" strokeWidth={1.5} />
116 <p className="text-sm">Failed to load policies</p>
117 </div>
118 </div>
119 {renderFooter()}
120 </div>
121 )
122 }
123
124 if (policyResults.length === 0) {
125 return (
126 <div className="relative h-full flex flex-col">
127 <div className="flex-1 min-h-0 overflow-hidden">
128 <EmptyState icon={Auth} label="RLS Policies" query={query} />
129 </div>
130 {renderFooter()}
131 </div>
132 )
133 }
134
135 return (
136 <div className="relative h-full flex flex-col">
137 <div className="flex-1 min-h-0 overflow-hidden">
138 <ResultsList
139 results={policyResults}
140 icon={Auth}
141 getRoute={(result) => {
142 const policy = policies?.find((p) => String(p.id) === result.id)
143 if (!policy || !projectRef)
144 return `/project/${projectRef}/auth/policies` as `/${string}`
145
146 const params = new URLSearchParams()
147 params.set('edit', String(policy.id))
148 if (policy.schema) {
149 params.set('schema', policy.schema)
150 }
151 return `/project/${projectRef}/auth/policies?${params.toString()}` as `/${string}`
152 }}
153 className="pb-9"
154 />
155 </div>
156 {renderFooter()}
157 </div>
158 )
159}