| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- import type { PGTrigger } from '@supabase/pg-meta'
- import { PermissionAction } from '@supabase/shared-types/out/constants'
- import { useParams } from 'common'
- import Image from 'next/legacy/image'
- import { useEffect } from 'react'
- import { UseFormReturn } from 'react-hook-form'
- import {
- Checkbox,
- FormControl,
- FormField,
- Input,
- Label,
- RadioGroupStacked,
- RadioGroupStackedItem,
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
- SidePanel,
- useWatch,
- } from 'ui'
- import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
- import { isEdgeFunction } from './EditHookPanel'
- import { WebhookFormValues } from './EditHookPanel.constants'
- import { AVAILABLE_WEBHOOK_TYPES, HOOK_EVENTS } from './Hooks.constants'
- import { HTTPHeaders } from './HTTPHeaders'
- import { HTTPParameters } from './HTTPParameters'
- import { HTTPRequestConfig } from './HTTPRequestConfig'
- import {
- FormSection,
- FormSectionContent,
- FormSectionLabel,
- } from '@/components/ui/Forms/FormSection'
- import { useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
- import { useEdgeFunctionsQuery } from '@/data/edge-functions/edge-functions-query'
- import { useTableNamesQuery } from '@/data/tables/table-names-query'
- import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
- import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- import { uuidv4 } from '@/lib/helpers'
- export interface FormContentsProps {
- form: UseFormReturn<WebhookFormValues>
- selectedHook?: PGTrigger
- }
- export const FormContents = ({ form, selectedHook }: FormContentsProps) => {
- const { ref } = useParams()
- const { data: project } = useSelectedProjectQuery()
- const restUrl = project?.restUrl
- const restUrlTld = restUrl ? new URL(restUrl).hostname.split('.').pop() : 'co'
- const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: keys = [] } = useAPIKeysQuery(
- { projectRef: ref, reveal: true },
- { enabled: canReadAPIKeys }
- )
- const { data: functions = [], isSuccess: isSuccessEdgeFunctions } = useEdgeFunctionsQuery({
- projectRef: ref,
- })
- const legacyServiceRole = keys.find((x) => x.name === 'service_role')?.api_key ?? '[YOUR API KEY]'
- const httpUrl = useWatch({ control: form.control, name: 'http_url' })
- const httpHeaders = useWatch({ control: form.control, name: 'httpHeaders' })
- const { data: tables = [] } = useTableNamesQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
- // Handle auth header auto-add for edge functions
- useEffect(() => {
- if (!isSuccessEdgeFunctions) return
- const isEdgeFunctionSelected = isEdgeFunction({ ref, restUrlTld, url: httpUrl })
- if (httpUrl && isEdgeFunctionSelected) {
- const fnSlug = httpUrl.split('/').at(-1)
- const fn = functions.find((x) => x.slug === fnSlug)
- const authorizationHeader = httpHeaders.find((x) => x.name === 'Authorization')
- const edgeFunctionAuthHeaderVal = `Bearer ${legacyServiceRole}`
- if (fn?.verify_jwt && authorizationHeader == null) {
- const newAuthHeader = {
- id: uuidv4(),
- name: 'Authorization',
- value: edgeFunctionAuthHeaderVal,
- }
- form.setValue('httpHeaders', [...httpHeaders, newAuthHeader])
- } else if (fn?.verify_jwt && authorizationHeader?.value !== edgeFunctionAuthHeaderVal) {
- const updatedHttpHeaders = httpHeaders.map((x) => {
- if (x.name === 'Authorization') return { ...x, value: edgeFunctionAuthHeaderVal }
- else return x
- })
- form.setValue('httpHeaders', updatedHttpHeaders)
- }
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [httpUrl, isSuccessEdgeFunctions])
- return (
- <div>
- <FormSection header={<FormSectionLabel className="lg:col-span-4!">General</FormSectionLabel>}>
- <FormSectionContent loading={false} className="lg:col-span-8!">
- <FormField
- control={form.control}
- name="name"
- render={({ field }) => (
- <FormItemLayout label="Name" layout="vertical" className="gap-1">
- <FormControl>
- <Input {...field} placeholder="my_webhook" />
- </FormControl>
- <p className="mt-2 text-xs text-foreground-lighter">
- Do not use spaces/whitespaces
- </p>
- </FormItemLayout>
- )}
- />
- </FormSectionContent>
- </FormSection>
- <SidePanel.Separator />
- <FormSection
- header={
- <FormSectionLabel
- className="lg:col-span-4!"
- description={
- <p className="text-sm text-foreground-light">
- Select which table and events will trigger your webhook
- </p>
- }
- >
- Conditions to fire webhook
- </FormSectionLabel>
- }
- >
- <FormSectionContent loading={false} className="lg:col-span-8!">
- <FormField
- control={form.control}
- name="table_id"
- render={({ field }) => (
- <FormItemLayout
- label="Table"
- layout="vertical"
- className="gap-1"
- description="This is the table the trigger will watch for changes. You can only select 1 table for a trigger."
- >
- <Select value={field.value} onValueChange={field.onChange}>
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="Select a table" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- {tables.map((table) => (
- <SelectItem key={table.id} value={table.id.toString()}>
- <div className="flex items-center space-x-2">
- <span className="text-foreground-light">{table.schema}</span>
- <span className="text-foreground">{table.name}</span>
- </div>
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </FormItemLayout>
- )}
- />
- <FormField
- control={form.control}
- name="events"
- render={({ field }) => (
- <FormItemLayout
- label="Events"
- layout="vertical"
- className="gap-1"
- description="These are the events that are watched by the webhook, only the events selected above will fire the webhook on the table you've selected."
- >
- <div className="space-y-3">
- {HOOK_EVENTS.map((event) => (
- <div key={event.value} className="flex items-start space-x-3">
- <Checkbox
- id={`event-${event.value}`}
- checked={field.value.includes(event.value)}
- onCheckedChange={(checked) => {
- if (checked) {
- field.onChange([...field.value, event.value])
- } else {
- field.onChange(field.value.filter((v) => v !== event.value))
- }
- }}
- />
- <div className="grid gap-1.5 leading-none">
- <Label
- htmlFor={`event-${event.value}`}
- className="text-sm font-normal cursor-pointer"
- >
- {event.label}
- </Label>
- <p className="text-xs text-foreground-lighter">{event.description}</p>
- </div>
- </div>
- ))}
- </div>
- </FormItemLayout>
- )}
- />
- </FormSectionContent>
- </FormSection>
- <SidePanel.Separator />
- <FormSection
- header={
- <FormSectionLabel className="lg:col-span-4!">Webhook configuration</FormSectionLabel>
- }
- >
- <FormSectionContent loading={false} className="lg:col-span-8!">
- <FormField
- control={form.control}
- name="function_type"
- render={({ field }) => (
- <FormItemLayout label="Type of webhook" layout="vertical" className="gap-1">
- <FormControl>
- <RadioGroupStacked
- value={field.value}
- onValueChange={(functionType) => {
- if (functionType === 'http_request') {
- if (selectedHook !== undefined) {
- const [url] = selectedHook.function_args
- form.setValue('http_url', url, { shouldDirty: false })
- } else {
- form.setValue('http_url', '', { shouldDirty: false })
- }
- } else if (functionType === 'briven_function') {
- // Default to first edge function in the list
- const fnSlug = functions[0]?.slug
- const defaultFunctionUrl = `https://${ref}.briven.${restUrlTld}/functions/v1/${fnSlug}`
- const currentUrl = form.getValues('http_url')
- if (!isEdgeFunction({ ref, restUrlTld, url: currentUrl })) {
- form.setValue('http_url', defaultFunctionUrl, { shouldDirty: false })
- }
- }
- field.onChange(functionType)
- }}
- >
- {AVAILABLE_WEBHOOK_TYPES.map((webhook) => (
- <RadioGroupStackedItem
- key={webhook.value}
- id={webhook.value}
- value={webhook.value}
- label=""
- showIndicator={false}
- >
- <div className="flex items-center space-x-5">
- <Image
- alt={webhook.label}
- src={webhook.icon}
- layout="fixed"
- width="32"
- height="32"
- />
- <div className="flex-col space-y-0">
- <div className="flex space-x-2">
- <p className="text-foreground">{webhook.label}</p>
- </div>
- <p className="text-foreground-light">{webhook.description}</p>
- </div>
- </div>
- </RadioGroupStackedItem>
- ))}
- </RadioGroupStacked>
- </FormControl>
- </FormItemLayout>
- )}
- />
- </FormSectionContent>
- </FormSection>
- <SidePanel.Separator />
- <HTTPRequestConfig form={form} />
- <SidePanel.Separator />
- <HTTPHeaders form={form} />
- <SidePanel.Separator />
- <HTTPParameters form={form} />
- </div>
- )
- }
|