import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import dayjs from 'dayjs' import { ExternalLink } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' import { useEffect, useMemo, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import z from 'zod' import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' import AlertError from 'components/ui/AlertError' import { getAPIKeys, useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' import { useEdgeFunctionDeleteMutation } from 'data/edge-functions/edge-functions-delete-mutation' import { useEdgeFunctionUpdateMutation } from 'data/edge-functions/edge-functions-update-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Button, Card, CardContent, CardFooter, CardHeader, CardTitle, cn, CodeBlock, CriticalIcon, Form_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, Input, Input_Shadcn_, Switch, Tabs_Shadcn_ as Tabs, TabsContent_Shadcn_ as TabsContent, TabsList_Shadcn_ as TabsList, TabsTrigger_Shadcn_ as TabsTrigger, } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import CommandRender from '../CommandRender' import { INVOCATION_TABS } from './EdgeFunctionDetails.constants' import { generateCLICommands } from './EdgeFunctionDetails.utils' const FormSchema = z.object({ name: z.string().min(0, 'Name is required'), verify_jwt: z.boolean(), }) export const EdgeFunctionDetails = () => { const router = useRouter() const { ref: projectRef, functionSlug } = useParams() const [showDeleteModal, setShowDeleteModal] = useState(false) const canUpdateEdgeFunction = useCheckPermissions(PermissionAction.FUNCTIONS_WRITE, '*') const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { data: customDomainData } = useCustomDomainsQuery({ projectRef }) const { data: selectedFunction, error, isLoading, isError, isSuccess, } = useEdgeFunctionQuery({ projectRef, slug: functionSlug, }) const { mutate: updateEdgeFunction, isLoading: isUpdating } = useEdgeFunctionUpdateMutation() const { mutate: deleteEdgeFunction, isLoading: isDeleting } = useEdgeFunctionDeleteMutation({ onSuccess: () => { toast.success(`Successfully deleted "${selectedFunction?.name}"`) router.push(`/project/${projectRef}/functions`) }, }) const form = useForm({ resolver: zodResolver(FormSchema), defaultValues: { name: '', verify_jwt: false }, }) const { anonKey } = getAPIKeys(settings) const apiKey = anonKey?.api_key ?? '[YOUR ANON KEY]' const protocol = settings?.app_config?.protocol ?? 'https' const endpoint = settings?.app_config?.endpoint ?? '' const functionUrl = customDomainData?.customDomain?.status === 'active' ? `https://${customDomainData.customDomain.hostname}/functions/v1/${selectedFunction?.slug}` : `${protocol}://${endpoint}/functions/v1/${selectedFunction?.slug}` const hasImportMap = useMemo( () => selectedFunction?.import_map || selectedFunction?.import_map_path, [selectedFunction] ) const { managementCommands } = generateCLICommands({ selectedFunction, functionUrl, anonKey: apiKey, }) const onUpdateFunction: SubmitHandler> = async (values: any) => { if (!projectRef) return console.error('Project ref is required') if (selectedFunction === undefined) return console.error('No edge function selected') updateEdgeFunction( { projectRef, slug: selectedFunction.slug, payload: values, }, { onSuccess: () => { toast.success(`Successfully updated edge function`) }, } ) } const onConfirmDelete = async () => { if (!projectRef) return console.error('Project ref is required') if (selectedFunction === undefined) return console.error('No edge function selected') deleteEdgeFunction({ projectRef, slug: selectedFunction.slug }) } useEffect(() => { if (selectedFunction) { form.reset({ name: selectedFunction.name, verify_jwt: selectedFunction.verify_jwt, }) } }, [selectedFunction]) return (
Function Configuration
( )} /> ( )} /> {form.formState.isDirty && ( )}
Invoke function {INVOCATION_TABS.map((tab) => ( {tab.label} ))} {INVOCATION_TABS.map((tab) => (
))}
Develop locally
( <> supabase functions download{' '} {selectedFunction?.slug} ), comment: '1. Download the function', }, ]} />
Delete function Once your function is deleted, it can no longer be restored Make sure you have made a backup if you want to restore your edge function setShowDeleteModal(false)} onConfirm={onConfirmDelete} alert={{ base: { variant: 'destructive' }, title: 'This action cannot be undone', description: 'Ensure that you have made a backup if you want to restore your edge function', }} />
Details {isLoading && } {isError && ( )} {isSuccess && (
Slug
{selectedFunction?.slug}
Endpoint URL
Region
All functions are deployed globally
Created at
{dayjs(selectedFunction?.created_at ?? 0).format('dddd, MMMM D, YYYY h:mm A')}
Last updated at
{dayjs(selectedFunction?.updated_at ?? 0).format('dddd, MMMM D, YYYY h:mm A')}
Deployments
{selectedFunction?.version ?? 0}
Import Maps

Import maps are{' '} {hasImportMap ? 'used' : 'not used'} {' '} for this function

Import maps allow the use of bare specifiers in functions instead of explicit import URLs

)}
) }