HooksList.tsx107 lines · main
1import { PermissionAction } from '@supabase/shared-types/out/constants'
2import { includes, map as lodashMap, uniqBy } from 'lodash'
3import { Search } from 'lucide-react'
4import { parseAsBoolean, useQueryState } from 'nuqs'
5import { useState } from 'react'
6import { InputGroup, InputGroupAddon, InputGroupInput } from 'ui'
7import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
8
9import { HooksListEmpty } from './HooksListEmpty'
10import { SchemaTable } from './SchemaTable'
11import AlertError from '@/components/ui/AlertError'
12import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
13import { DocsButton } from '@/components/ui/DocsButton'
14import { NoSearchResults } from '@/components/ui/NoSearchResults'
15import { useDatabaseHooksQuery } from '@/data/database-triggers/database-triggers-query'
16import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
17import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
18import { DOCS_URL } from '@/lib/constants'
19
20export const HooksList = () => {
21 const { data: project } = useSelectedProjectQuery()
22
23 const [, setShowCreateHookForm] = useQueryState('new', parseAsBoolean.withDefault(false))
24
25 const {
26 data: hooks = [],
27 isPending: isLoading,
28 isSuccess,
29 isError,
30 error,
31 } = useDatabaseHooksQuery({
32 projectRef: project?.ref,
33 connectionString: project?.connectionString,
34 })
35 const [filterString, setFilterString] = useState<string>('')
36
37 const filteredHooks = hooks.filter((x) =>
38 includes(x.name.toLowerCase(), filterString.toLowerCase())
39 )
40 const filteredHookSchemas = lodashMap(uniqBy(filteredHooks, 'schema'), 'schema')
41
42 const { can: canCreateWebhooks, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
43 PermissionAction.TENANT_SQL_ADMIN_WRITE,
44 'triggers'
45 )
46
47 return (
48 <div className="w-full space-y-4">
49 <div className="flex items-center justify-between">
50 <InputGroup className="w-52">
51 <InputGroupInput
52 size="tiny"
53 placeholder="Search for a webhook"
54 value={filterString}
55 onChange={(e) => setFilterString(e.target.value)}
56 />
57 <InputGroupAddon>
58 <Search />
59 </InputGroupAddon>
60 </InputGroup>
61 <div className="flex items-center gap-x-2">
62 <DocsButton href={`${DOCS_URL}/guides/database/webhooks`} />
63 <ButtonTooltip
64 onClick={() => setShowCreateHookForm(true)}
65 disabled={!isPermissionsLoaded || !canCreateWebhooks}
66 tooltip={{
67 content: {
68 side: 'bottom',
69 text:
70 isPermissionsLoaded && !canCreateWebhooks
71 ? 'You need additional permissions to create webhooks'
72 : undefined,
73 },
74 }}
75 >
76 Create a new hook
77 </ButtonTooltip>
78 </div>
79 </div>
80
81 {isLoading && (
82 <div className="py-4">
83 <GenericSkeletonLoader />
84 </div>
85 )}
86
87 {isError && <AlertError error={error} subject="Failed to retrieve database webhooks" />}
88
89 {isSuccess &&
90 (hooks.length <= 0 ? (
91 <HooksListEmpty />
92 ) : (
93 <>
94 {filteredHooks.length <= 0 && (
95 <NoSearchResults
96 searchString={filterString}
97 onResetFilter={() => setFilterString('')}
98 />
99 )}
100 {filteredHookSchemas.map((schema) => (
101 <SchemaTable key={schema} filterString={filterString} schema={schema} />
102 ))}
103 </>
104 ))}
105 </div>
106 )
107}