FunctionsEmptyState.tsx403 lines · main
1// @ts-nocheck
2import { useParams } from 'common'
3import { Code, Play, Terminal } from 'lucide-react'
4import Link from 'next/link'
5import { useRouter } from 'next/router'
6import { parseAsString, useQueryState } from 'nuqs'
7import { useMemo } from 'react'
8import {
9 AiIconAnimation,
10 Button,
11 Card,
12 CardContent,
13 CardHeader,
14 CardTitle,
15 cn,
16 Dialog,
17 DialogContent,
18 DialogDescription,
19 DialogHeader,
20 DialogSection,
21 DialogTitle,
22 DialogTrigger,
23 Separator,
24} from 'ui'
25import { CodeBlock } from 'ui-patterns/CodeBlock'
26
27import { EDGE_FUNCTION_TEMPLATES } from './Functions.templates'
28import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
29import { ScaffoldSectionTitle } from '@/components/layouts/Scaffold'
30import { DocsButton } from '@/components/ui/DocsButton'
31import { ResourceItem } from '@/components/ui/Resource/ResourceItem'
32import { ResourceList } from '@/components/ui/Resource/ResourceList'
33import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
34import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
35import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
36import { DOCS_URL } from '@/lib/constants'
37import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
38import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
39
40export const FunctionsEmptyState = () => {
41 const { ref } = useParams()
42 const router = useRouter()
43 const aiSnap = useAiAssistantStateSnapshot()
44 const { openSidebar } = useSidebarManagerSnapshot()
45
46 const { mutate: sendEvent } = useSendEventMutation()
47 const { data: org } = useSelectedOrganizationQuery()
48 const [, setCreateMethod] = useQueryState('create', parseAsString)
49
50 const showStripeExample = useIsFeatureEnabled('edge_functions:show_stripe_example')
51 const templates = useMemo(() => {
52 if (showStripeExample) {
53 return EDGE_FUNCTION_TEMPLATES
54 }
55
56 // Filter out Stripe template
57 return EDGE_FUNCTION_TEMPLATES.filter((template) => template.value !== 'stripe-webhook')
58 }, [showStripeExample])
59
60 return (
61 <>
62 <Card>
63 <CardHeader>
64 <CardTitle>Deploy your first edge function</CardTitle>
65 </CardHeader>
66 <CardContent className="p-0 grid grid-cols-[repeat(auto-fit,minmax(240px,1fr))] divide-y md:divide-y-0 md:divide-x divide-default items-stretch">
67 {/* Editor Option */}
68 <div className="p-8">
69 <div className="flex items-center gap-2">
70 <Code strokeWidth={1.5} size={20} />
71 <h4 className="text-base text-foreground">Via Editor</h4>
72 </div>
73 <p className="text-sm text-foreground-light mb-4 mt-1">
74 Create and edit functions directly in the browser. Download to local at any time.
75 </p>
76 <Button
77 type="default"
78 onClick={() => {
79 router.push(`/project/${ref}/functions/new`)
80 sendEvent({
81 action: 'edge_function_via_editor_button_clicked',
82 properties: { origin: 'no_functions_block' },
83 groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
84 })
85 }}
86 >
87 Open Editor
88 </Button>
89 </div>
90
91 {/* AI Assistant Option */}
92 <div className="p-8">
93 <div className="flex items-center gap-2">
94 <AiIconAnimation size={20} />
95 <h4 className="text-base text-foreground">AI Assistant</h4>
96 </div>
97 <p className="text-sm text-foreground-light mb-4 mt-1">
98 Let our AI assistant help you create functions. Perfect for kickstarting a function.
99 </p>
100 <Button
101 type="default"
102 onClick={() => {
103 openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
104 aiSnap.newChat({
105 name: 'Create new edge function',
106 initialInput: 'Create a new edge function that ...',
107 suggestions: {
108 title:
109 'I can help you create a new edge function. Here are a few example prompts to get you started:',
110 prompts: [
111 {
112 label: 'Stripe Payments',
113 description:
114 'Create a new edge function that processes payments with Stripe',
115 },
116 {
117 label: 'Email with Resend',
118 description: 'Create a new edge function that sends emails with Resend',
119 },
120 {
121 label: 'PDF Generator',
122 description:
123 'Create a new edge function that generates PDFs from HTML templates',
124 },
125 ],
126 },
127 })
128 sendEvent({
129 action: 'edge_function_ai_assistant_button_clicked',
130 properties: { origin: 'no_functions_block' },
131 groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
132 })
133 }}
134 >
135 Open Assistant
136 </Button>
137 </div>
138
139 {/* CLI Option */}
140 <div className="p-8">
141 <div className="flex items-center gap-2">
142 <Terminal strokeWidth={1.5} size={20} />
143 <h4 className="text-base text-foreground">Via CLI</h4>
144 </div>
145 <p className="text-sm text-foreground-light mb-4 mt-1">
146 Create and deploy functions using the Briven CLI. Ideal for local development and
147 version control.
148 </p>
149
150 <Button
151 type="default"
152 onClick={() => {
153 setCreateMethod('cli')
154 sendEvent({
155 action: 'edge_function_via_cli_button_clicked',
156 properties: { origin: 'no_functions_block' },
157 groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
158 })
159 }}
160 >
161 View CLI Instructions
162 </Button>
163 </div>
164 </CardContent>
165 </Card>
166 <ScaffoldSectionTitle className="text-xl mb-4 mt-12">
167 Start with a template
168 </ScaffoldSectionTitle>
169 <ResourceList>
170 {templates.map((template) => (
171 <ResourceItem
172 key={template.name}
173 media={<Code strokeWidth={1.5} size={16} className="translate-y-[-9px]" />}
174 onClick={() => {
175 sendEvent({
176 action: 'edge_function_template_clicked',
177 properties: { templateName: template.name, origin: 'functions_page' },
178 groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
179 })
180 }}
181 >
182 <Link href={`/project/${ref}/functions/new?template=${template.value}`}>
183 <p>{template.name}</p>
184 <p className="text-sm text-foreground-lighter">{template.description}</p>
185 </Link>
186 </ResourceItem>
187 ))}
188 </ResourceList>
189 </>
190 )
191}
192
193export const FunctionsInstructionsLocal = () => {
194 const showStripeExample = useIsFeatureEnabled('edge_functions:show_stripe_example')
195 const templates = useMemo(() => {
196 if (showStripeExample) {
197 return EDGE_FUNCTION_TEMPLATES
198 }
199
200 // Filter out Stripe template
201 return EDGE_FUNCTION_TEMPLATES.filter((template) => template.value !== 'stripe-webhook')
202 }, [showStripeExample])
203
204 return (
205 <>
206 <div className="flex flex-col gap-y-4">
207 <Card>
208 <CardHeader>
209 <CardTitle>Developing Edge Functions with the CLI</CardTitle>
210 </CardHeader>
211 <CardContent
212 className={cn(
213 'p-0 flex flex-col',
214 '2xl:grid 2xl:grid-cols-[repeat(auto-fit,minmax(240px,1fr))] 2xl:divide-y-0 2xl:divide-x',
215 'divide-y divide-default items-stretch'
216 )}
217 >
218 <div className="p-8">
219 <div className="flex items-center gap-2">
220 <Code size={20} />
221 <h4 className="text-base text-foreground">Create an Edge Function</h4>
222 </div>
223 <p className="text-sm text-foreground-light mt-1 mb-4 prose [&>code]:text-xs text-sm max-w-full">
224 Create a new edge function called <code>hello-world</code> in your project via the
225 Briven CLI.
226 </p>
227 <div className="mb-4">
228 <CodeBlock
229 language="bash"
230 hideLineNumbers={true}
231 className={cn(
232 'px-3.5 max-w-full prose dark:prose-dark [&>code]:m-0 2xl:min-h-28'
233 )}
234 value="briven functions new hello-world"
235 />
236 </div>
237 <DocsButton
238 href={`${DOCS_URL}/guides/functions/local-quickstart#create-an-edge-function`}
239 />
240 </div>
241
242 <div className="p-8">
243 <div className="flex items-center gap-2">
244 <Play size={20} />
245 <h4 className="text-base text-foreground">Run Edge Functions</h4>
246 </div>
247 <p className="text-sm text-foreground-light mt-1 mb-4 prose [&>code]:text-xs text-sm max-w-full">
248 You can run your Edge Function locally using <code>briven functions serve</code>.
249 </p>
250 <div className="mb-4">
251 <CodeBlock
252 language="bash"
253 hideLineNumbers={true}
254 className={cn(
255 'px-3.5 max-w-full prose dark:prose-dark [&>code]:m-0 2xl:min-h-28'
256 )}
257 value={`
258briven start # start the briven stack
259briven functions serve # start the Functions watcher`.trim()}
260 />
261 </div>
262 <DocsButton
263 href={`${DOCS_URL}/guides/functions/local-quickstart#running-edge-functions-locally`}
264 />
265 </div>
266
267 <div className="p-8">
268 <div className="flex items-center gap-2">
269 <Terminal strokeWidth={1.5} size={20} />
270 <h4 className="text-base text-foreground">Invoke Edge Functions</h4>
271 </div>
272 <p className="text-sm text-foreground-light mt-1 mb-4">
273 While serving your local Edge Functions, you can invoke it using cURL or one of the
274 client libraries.
275 </p>
276 <div className="mb-4">
277 <CodeBlock
278 language="bash"
279 hideLineNumbers={true}
280 className={cn(
281 'px-3.5 max-w-full prose dark:prose-dark [&>code]:m-0 2xl:min-h-28'
282 )}
283 value={`
284curl --request POST 'http://localhost:54321/functions/v1/hello-world' \\
285 --header 'Authorization: Bearer BRIVEN_ANON_KEY' \\
286 --header 'Content-Type: application/json' \\
287 --data '{ "name":"Functions" }'`.trim()}
288 />
289 </div>
290 <DocsButton
291 href={`${DOCS_URL}/guides/functions/local-quickstart#invoking-edge-functions-locally`}
292 />
293 </div>
294 </CardContent>
295 </Card>
296
297 <Card>
298 <CardHeader>
299 <CardTitle>Self-hosted Briven</CardTitle>
300 </CardHeader>
301 <CardContent className="p-0 grid grid-cols-[repeat(auto-fit,minmax(240px,1fr))] divide-y md:divide-y-0 md:divide-x divide-default items-stretch">
302 <div className="p-8">
303 <p className="text-sm text-foreground-light mt-1 mb-4">
304 Edge Functions are available in self-hosted Briven via Briven Edge Runtime.
305 Unlike the platform, functions must be added manually — place each function at{' '}
306 <code className="text-code-inline">
307 volumes/functions/&lt;function-name&gt;/index.ts
308 </code>{' '}
309 and restart the <code className="text-code-inline">functions</code> service to pick
310 up changes. See the guide to learn more about configuration, secrets, and routing.
311 </p>
312 <DocsButton href={`${DOCS_URL}/guides/self-hosting/self-hosted-functions`} />
313 </div>
314 </CardContent>
315 </Card>
316
317 <ScaffoldSectionTitle className="text-xl mt-12">Explore our templates</ScaffoldSectionTitle>
318 <ResourceList>
319 {templates.map((template) => (
320 <Dialog key={template.name}>
321 <DialogTrigger asChild>
322 <ResourceItem
323 key={template.name}
324 media={<Code strokeWidth={1.5} size={16} className="translate-y-[-9px]" />}
325 >
326 <div>
327 <p>{template.name}</p>
328 <p className="text-sm text-foreground-lighter">{template.description}</p>
329 </div>
330 </ResourceItem>
331 </DialogTrigger>
332 <DialogContent size="large">
333 <DialogHeader>
334 <DialogTitle>{template.name}</DialogTitle>
335 <DialogDescription>{template.description}</DialogDescription>
336 </DialogHeader>
337 <Separator />
338 <DialogSection className="p-0!">
339 <CodeBlock
340 language="ts"
341 hideLineNumbers={true}
342 className={cn(
343 'border-0 rounded-none px-3.5 max-w-full prose dark:prose-dark [&>code]:m-0 max-h-[420px]'
344 )}
345 value={template.content}
346 />
347 </DialogSection>
348 </DialogContent>
349 </Dialog>
350 ))}
351 </ResourceList>
352 </div>
353 </>
354 )
355}
356
357export const FunctionsSecretsEmptyStateLocal = () => {
358 return (
359 <>
360 <Card>
361 <CardHeader>
362 <CardTitle>Local development & CLI</CardTitle>
363 </CardHeader>
364 <CardContent>
365 <div className="text-sm text-foreground-light mb-4">
366 <p className="mb-2">Secrets can be loaded in two ways:</p>
367 <ul className="list-disc pl-6 space-y-1">
368 <li>
369 Place a <code className="text-code-inline">.env</code> file at{' '}
370 <code className="text-code-inline">briven/functions/.env</code> — picked up
371 automatically on <code className="text-code-inline">briven start</code>.
372 </li>
373 <li>
374 Pass <code className="text-code-inline">--env-file</code> to{' '}
375 <code className="text-code-inline">briven functions serve</code>, e.g.{' '}
376 <code className="text-code-inline">
377 briven functions serve --env-file ./path/to/.env-file
378 </code>
379 </li>
380 </ul>
381 </div>
382 <DocsButton href={`${DOCS_URL}/guides/functions/secrets#using-the-cli`} />
383 </CardContent>
384 </Card>
385
386 <Card>
387 <CardHeader>
388 <CardTitle>Self-Hosted Briven</CardTitle>
389 </CardHeader>
390 <CardContent>
391 <p className="text-sm text-foreground-light mb-4">
392 Configure secrets in your <code className="text-code-inline">.env</code> file and{' '}
393 <code className="text-code-inline">docker-compose.yml</code> under the{' '}
394 <code className="text-code-inline">functions</code> service.
395 </p>
396 <DocsButton
397 href={`${DOCS_URL}/guides/self-hosting/self-hosted-functions#custom-environment-variables`}
398 />
399 </CardContent>
400 </Card>
401 </>
402 )
403}