EdgeFunctionSearchResults.tsx136 lines · main
| 1 | 'use client' |
| 2 | |
| 3 | import { useParams } from 'common' |
| 4 | import { EdgeFunctions } from 'icons' |
| 5 | import { Loader2 } from 'lucide-react' |
| 6 | import { useMemo } from 'react' |
| 7 | |
| 8 | import { |
| 9 | EmptyState, |
| 10 | ResultsList, |
| 11 | SkeletonResults, |
| 12 | type SearchResult, |
| 13 | } from './ContextSearchResults.shared' |
| 14 | import { useEdgeFunctionsQuery } from '@/data/edge-functions/edge-functions-query' |
| 15 | |
| 16 | interface EdgeFunctionSearchResultsProps { |
| 17 | query: string |
| 18 | } |
| 19 | |
| 20 | export function EdgeFunctionSearchResults({ query }: EdgeFunctionSearchResultsProps) { |
| 21 | const { ref: projectRef } = useParams() |
| 22 | |
| 23 | const trimmedQuery = query.trim() |
| 24 | |
| 25 | const { |
| 26 | data: functions, |
| 27 | isLoading: isLoadingFunctions, |
| 28 | isError: isErrorFunctions, |
| 29 | } = useEdgeFunctionsQuery( |
| 30 | { |
| 31 | projectRef, |
| 32 | }, |
| 33 | { |
| 34 | enabled: !!projectRef, |
| 35 | } |
| 36 | ) |
| 37 | |
| 38 | const functionResults: SearchResult[] = useMemo(() => { |
| 39 | if (!functions) return [] |
| 40 | |
| 41 | const filtered = trimmedQuery |
| 42 | ? functions.filter((func) => { |
| 43 | const searchLower = trimmedQuery.toLowerCase() |
| 44 | const functionName = func.name?.toLowerCase() || '' |
| 45 | const functionSlug = func.slug?.toLowerCase() || '' |
| 46 | |
| 47 | return functionName.includes(searchLower) || functionSlug.includes(searchLower) |
| 48 | }) |
| 49 | : functions |
| 50 | |
| 51 | // Limit results for performance |
| 52 | return filtered.slice(0, 20).map((func) => { |
| 53 | const displayName = func.name || func.slug || 'Untitled Function' |
| 54 | const description = func.version ? `Version ${func.version}` : undefined |
| 55 | |
| 56 | return { |
| 57 | id: String(func.id), |
| 58 | name: displayName, |
| 59 | description, |
| 60 | } |
| 61 | }) |
| 62 | }, [functions, trimmedQuery]) |
| 63 | |
| 64 | const totalFunctions = functions?.length ?? 0 |
| 65 | |
| 66 | const renderFooter = () => ( |
| 67 | <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"> |
| 68 | <div className="flex items-center gap-x-2"> |
| 69 | {isLoadingFunctions ? ( |
| 70 | <span className="flex items-center gap-2"> |
| 71 | <Loader2 size={14} className="animate-spin" /> Loading... |
| 72 | </span> |
| 73 | ) : ( |
| 74 | <span> |
| 75 | Total: {totalFunctions.toLocaleString()} function{totalFunctions !== 1 ? 's' : ''} |
| 76 | </span> |
| 77 | )} |
| 78 | </div> |
| 79 | </div> |
| 80 | ) |
| 81 | |
| 82 | if (isLoadingFunctions) { |
| 83 | return ( |
| 84 | <div className="relative h-full flex flex-col"> |
| 85 | <div className="flex-1 min-h-0 overflow-hidden"> |
| 86 | <SkeletonResults /> |
| 87 | </div> |
| 88 | {renderFooter()} |
| 89 | </div> |
| 90 | ) |
| 91 | } |
| 92 | |
| 93 | if (isErrorFunctions) { |
| 94 | return ( |
| 95 | <div className="relative h-full flex flex-col"> |
| 96 | <div className="flex-1 min-h-0 overflow-hidden"> |
| 97 | <div className="h-full flex flex-col items-center justify-center py-12 px-4 gap-4 text-center text-foreground-lighter"> |
| 98 | <EdgeFunctions className="h-6 w-6" strokeWidth={1.5} /> |
| 99 | <p className="text-sm">Failed to load edge functions</p> |
| 100 | </div> |
| 101 | </div> |
| 102 | {renderFooter()} |
| 103 | </div> |
| 104 | ) |
| 105 | } |
| 106 | |
| 107 | if (functionResults.length === 0) { |
| 108 | return ( |
| 109 | <div className="relative h-full flex flex-col"> |
| 110 | <div className="flex-1 min-h-0 overflow-hidden"> |
| 111 | <EmptyState icon={EdgeFunctions} label="Edge Functions" query={query} /> |
| 112 | </div> |
| 113 | {renderFooter()} |
| 114 | </div> |
| 115 | ) |
| 116 | } |
| 117 | |
| 118 | return ( |
| 119 | <div className="relative h-full flex flex-col"> |
| 120 | <div className="flex-1 min-h-0 overflow-hidden"> |
| 121 | <ResultsList |
| 122 | results={functionResults} |
| 123 | icon={EdgeFunctions} |
| 124 | getRoute={(result) => { |
| 125 | const func = functions?.find((f) => String(f.id) === result.id) |
| 126 | if (!func || !projectRef) return `/project/${projectRef}/functions` as `/${string}` |
| 127 | |
| 128 | return `/project/${projectRef}/functions/${func.slug}` as `/${string}` |
| 129 | }} |
| 130 | className="pb-9" |
| 131 | /> |
| 132 | </div> |
| 133 | {renderFooter()} |
| 134 | </div> |
| 135 | ) |
| 136 | } |