TerminalInstructions.tsx153 lines · main
1import { PermissionAction } from '@supabase/shared-types/out/constants'
2import { useParams } from 'common'
3import { ExternalLink, Maximize2, Minimize2, Terminal } from 'lucide-react'
4import { useRouter } from 'next/router'
5import { ComponentPropsWithoutRef, ElementRef, forwardRef, useState } from 'react'
6import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger } from 'ui'
7
8import type { Commands } from './Functions.types'
9import CommandRender from '@/components/interfaces/Functions/CommandRender'
10import { DocsButton } from '@/components/ui/DocsButton'
11import { useAccessTokensQuery } from '@/data/access-tokens/access-tokens-query'
12import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
13import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
14import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
15import { DOCS_URL } from '@/lib/constants'
16
17interface TerminalInstructionsProps extends ComponentPropsWithoutRef<typeof Collapsible> {
18 closable?: boolean
19 removeBorder?: boolean
20}
21
22export const TerminalInstructions = forwardRef<
23 ElementRef<typeof Collapsible>,
24 TerminalInstructionsProps
25>(({ closable = false, removeBorder = false, ...props }, ref) => {
26 const router = useRouter()
27 const { ref: projectRef } = useParams()
28 const [showInstructions, setShowInstructions] = useState(!closable)
29
30 const { data: tokens } = useAccessTokensQuery()
31 const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
32 const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
33
34 const { data: endpoint } = useProjectApiUrl({ projectRef })
35 const functionsEndpoint = `${endpoint}/functions/v1`
36
37 const { anonKey, publishableKey } = getKeys(apiKeys)
38 const apiKey = publishableKey?.api_key ?? anonKey?.api_key ?? '[YOUR ANON KEY]'
39
40 // get the .co or .net TLD from the restUrl
41 const restUrl = `https://${endpoint}`
42 const restUrlTld = !!endpoint ? new URL(restUrl).hostname.split('.').pop() : 'co'
43
44 const commands: Commands[] = [
45 {
46 command: 'briven functions new hello-world',
47 description: ' creates a function stub at ./functions/hello-world/index.ts',
48 jsx: () => {
49 return (
50 <>
51 <span className="text-brand-600">briven</span> functions new hello-world
52 </>
53 )
54 },
55 comment: 'Create a function',
56 },
57 {
58 command: `briven functions deploy hello-world --project-ref ${projectRef}`,
59 description: 'Deploys function at ./functions/hello-world/index.ts',
60 jsx: () => {
61 return (
62 <>
63 <span className="text-brand-600">briven</span> functions deploy hello-world
64 --project-ref {projectRef}
65 </>
66 )
67 },
68 comment: 'Deploy your function',
69 },
70 {
71 command: `curl -L -X POST 'https://${projectRef}.briven.${restUrlTld}/functions/v1/hello-world' -H 'Authorization: Bearer ${apiKey}'${anonKey?.type === 'publishable' ? ` -H 'apikey: ${apiKey}'` : ''} --data '{"name":"Functions"}'`,
72 description: 'Invokes the hello-world function',
73 jsx: () => {
74 return (
75 <>
76 <span className="text-brand-600">curl</span> -L -X POST '{functionsEndpoint}
77 /hello-world' -H 'Authorization: Bearer [YOUR ANON KEY]'
78 {anonKey?.type === 'publishable' ? " -H 'apikey: [YOUR ANON KEY]' " : ''}
79 {`--data '{"name":"Functions"}'`}
80 </>
81 )
82 },
83 comment: 'Invoke your function',
84 },
85 ]
86
87 return (
88 <Collapsible
89 ref={ref}
90 open={showInstructions}
91 className="w-full"
92 onOpenChange={() => setShowInstructions(!showInstructions)}
93 {...props}
94 >
95 <CollapsibleTrigger className="flex w-full justify-between" disabled={!closable}>
96 <div className="flex items-center gap-x-3">
97 <div className="flex items-center justify-center w-8 h-8 p-2 border rounded-sm bg-alternative">
98 <Terminal strokeWidth={2} />
99 </div>
100 <h4>Create your first Edge Function via the CLI</h4>
101 </div>
102 {closable && (
103 <div className="cursor-pointer" onClick={() => setShowInstructions(!showInstructions)}>
104 {showInstructions ? (
105 <Minimize2 size={16} strokeWidth={1.5} />
106 ) : (
107 <Maximize2 size={16} strokeWidth={1.5} />
108 )}
109 </div>
110 )}
111 </CollapsibleTrigger>
112 <CollapsibleContent className="w-full transition-all data-closed:animate-collapsible-up data-open:animate-collapsible-down">
113 <CommandRender commands={commands} className="my-4" />
114 {tokens && tokens.length === 0 ? (
115 <div className="py-4 space-y-3 border-t">
116 <div>
117 <p className="text-sm text-foreground">You may need to create an access token</p>
118 <p className="text-sm text-foreground-light">
119 You can create a secure access token in your account section
120 </p>
121 </div>
122 <Button type="default" onClick={() => router.push('/account/tokens')}>
123 Access tokens
124 </Button>
125 </div>
126 ) : (
127 <div className="py-4 space-y-3 border-t">
128 <div>
129 <h3 className="text-base text-foreground">Need help?</h3>
130 <p className="text-sm text-foreground-light">
131 Read the documentation, or browse some sample code.
132 </p>
133 </div>
134 <div className="flex gap-2">
135 <DocsButton href={`${DOCS_URL}/guides/functions`} />
136 <Button asChild type="default" icon={<ExternalLink />}>
137 <a
138 target="_blank"
139 rel="noreferrer"
140 href="https://github.com/briven/briven/tree/master/examples/edge-functions/briven/functions"
141 >
142 Examples
143 </a>
144 </Button>
145 </div>
146 </div>
147 )}
148 </CollapsibleContent>
149 </Collapsible>
150 )
151})
152
153TerminalInstructions.displayName = 'TerminalInstructions'