FunctionsEmptyState.tsx 16 KB

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