Files
supabase/apps/studio/components/interfaces/Integrations/CronJobs/EdgeFunctionSection.tsx
T
Joshen Lim 09a1f431bc Mass update for all lucide icon imports to use the component with Icon suffix (#51659)
## Context

This is largely just a mechanical change here:
Opting to use lucide icons with the `Icon` suffix across all icon
imports within `studio`
- so that it’s clearer which components are icons + reduces the
possibility of name collision with our own components
- e.g <User /> vs <UserIcon />

Also added a eslint rule for this:
<img width="1069" height="167" alt="image"
src="https://github.com/user-attachments/assets/7f36f443-5ca2-4bf6-8d22-86e774513bfc"
/>

And updated `AGENTS.md` as such:
```
Icons come from `lucide-react`, always imported with the `Icon` suffix (`import { CheckIcon, Trash2Icon } from 'lucide-react'`) — never the bare names (`Check`) or `Check as CheckIcon` aliases (lint-enforced).
```
2026-10-14 12:56:55 +02:00

208 lines
7.2 KiB
TypeScript

import { useParams } from 'common/hooks'
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'
import Link from 'next/link'
import { useEffect, useId, useMemo, useState } from 'react'
import { UseFormReturn } from 'react-hook-form'
import {
Button,
cn,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Popover,
PopoverContent,
PopoverTrigger,
ScrollArea,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
SheetSection,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateCronJobForm } from './CreateCronJobSheet/CreateCronJobSheet.constants'
import { useEdgeFunctionsQuery } from '@/data/edge-functions/edge-functions-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { buildDatabaseEdgeFunctionUrl } from '@/lib/api/edgeFunctions'
interface HTTPRequestFieldsProps {
form: UseFormReturn<CreateCronJobForm>
}
export const EdgeFunctionSection = ({ form }: HTTPRequestFieldsProps) => {
const { ref } = useParams()
const { data: selectedProject } = useSelectedProjectQuery()
const {
data: functions,
isSuccess,
isPending: isLoading,
} = useEdgeFunctionsQuery({ projectRef: ref })
const [open, setOpen] = useState(false)
const listboxId = useId()
const edgeFunctions = useMemo(
() =>
functions?.map((fn) => ({
...fn,
url: buildDatabaseEdgeFunctionUrl(
fn.slug,
selectedProject?.ref || '',
selectedProject?.restUrl
),
})) ?? [],
[functions, selectedProject]
)
// Only set a default value if the field is empty
useEffect(() => {
if (isSuccess && edgeFunctions.length > 0 && !form.getValues('values.edgeFunctionName')) {
const fn = edgeFunctions[0]
form.setValue('values.edgeFunctionName', fn.url)
}
}, [edgeFunctions, form, isSuccess, selectedProject?.ref, selectedProject?.restUrl])
return (
<SheetSection className="flex flex-col gap-6">
<FormField
control={form.control}
name="values.method"
render={({ field }) => (
<FormItem>
<FormLabel>Method</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a method for the API call" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="GET">GET</SelectItem>
<SelectItem value="POST">POST</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
{edgeFunctions.length === 0 ? (
<div className="space-y-1">
<p className="text-sm text-foreground-light">Select which edge function to trigger</p>
{isLoading ? (
<Button className="justify-start" block size="small" loading>
Loading edge functions...
</Button>
) : (
<div className="px-4 py-4 border rounded-sm bg-surface-300 border-strong flex items-center justify-between space-x-4">
<p className="text-sm">No edge functions created yet</p>
<Button variant="primary" asChild>
<Link href={`/project/${ref}/functions`}>Create an edge function</Link>
</Button>
</div>
)}
</div>
) : edgeFunctions.length > 0 ? (
<FormField
control={form.control}
name="values.edgeFunctionName"
render={({ field }) => {
const selectedFunction = edgeFunctions.find((fn) => fn.url === field.value)
return (
<FormItem>
<FormLabel>Edge Function</FormLabel>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl>
<Button
role="combobox"
aria-expanded={open}
aria-controls={listboxId}
className={cn(
'w-full justify-between',
!field.value && 'text-muted-foreground'
)}
size="small"
iconRight={
<ChevronsUpDownIcon
className="ml-2 h-4 w-4 shrink-0 opacity-50"
strokeWidth={1}
/>
}
>
{selectedFunction
? selectedFunction.name
: 'Select which edge function to trigger'}
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent id={listboxId} className="p-0" sameWidthAsTrigger>
<Command>
<CommandInput placeholder="Search edge functions..." />
<CommandList>
<CommandEmpty>No edge function found.</CommandEmpty>
<CommandGroup>
<ScrollArea className={edgeFunctions.length > 7 ? 'h-[210px]' : ''}>
{edgeFunctions.map((fn) => {
return (
<CommandItem
value={fn.name}
key={fn.id}
onSelect={() => {
field.onChange(fn.url === field.value ? '' : fn.url)
setOpen(false)
}}
>
<CheckIcon
className={cn(
'mr-2 h-4 w-4',
fn.url === field.value ? 'opacity-100' : 'opacity-0'
)}
/>
{fn.name}
</CommandItem>
)
})}
</ScrollArea>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)
}}
/>
) : null}
<FormField
control={form.control}
name="values.timeoutMs"
render={({ field: { ref, ...rest } }) => (
<FormItemLayout label="Timeout" layout="vertical" className="gap-1">
<InputGroup>
<InputGroupInput {...rest} type="number" placeholder="1000" />
<InputGroupAddon align="inline-end">
<InputGroupText> ms</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormItemLayout>
)}
/>
</SheetSection>
)
}