OAuthSecrets.tsx95 lines · main
1import { PermissionAction } from '@supabase/shared-types/out/constants'
2import { useParams } from 'common'
3import { useState } from 'react'
4import { Alert, AlertTitle, InfoIcon } from 'ui'
5
6import { SecretRow } from './SecretRow'
7import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
8import { InlineLink } from '@/components/ui/InlineLink'
9import { useClientSecretCreateMutation } from '@/data/oauth-secrets/client-secret-create-mutation'
10import { CreatedSecret, useClientSecretsQuery } from '@/data/oauth-secrets/client-secrets-query'
11import { OAuthApp } from '@/data/oauth/oauth-apps-query'
12import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
13import { DOCS_URL } from '@/lib/constants'
14
15interface Props {
16 selectedApp?: OAuthApp
17}
18
19export const OAuthSecrets = ({ selectedApp }: Props) => {
20 const { slug } = useParams()
21 const [createdSecret, setCreatedSecret] = useState<CreatedSecret>()
22 const { can: canManageSecrets } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'oauth_apps')
23
24 const { id: appId } = selectedApp ?? {}
25
26 const { data } = useClientSecretsQuery({ slug, appId })
27 const secrets = data?.client_secrets ?? []
28
29 const { mutate: createSecret, isPending: isCreatingSecret } = useClientSecretCreateMutation({
30 onSuccess: (data) => setCreatedSecret(data),
31 })
32
33 const handleCreateSecret = () => {
34 if (!slug) return console.error('Slug is required')
35 if (!appId) return console.error('App ID is required')
36 createSecret({ slug, appId })
37 }
38
39 return (
40 <div className="flex flex-col gap-4">
41 <div className="flex items-center justify-between">
42 <div className="flex flex-col">
43 <span className="text-sm text-foreground">Client secrets</span>
44 <span className="text-sm text-foreground-light">
45 For handling callbacks in the OAuth 2.0 flow. Learn more{' '}
46 <InlineLink
47 href={`${DOCS_URL}/guides/integrations/build-a-briven-integration#handling-the-callback`}
48 >
49 here
50 </InlineLink>
51 .
52 </span>
53 </div>
54
55 {canManageSecrets && (
56 <ButtonTooltip
57 type="default"
58 disabled={!appId || secrets.length >= 5}
59 onClick={handleCreateSecret}
60 loading={isCreatingSecret}
61 tooltip={{
62 content: {
63 side: 'bottom',
64 text: secrets.length >= 5 ? 'You can only have up to 5 client secrets' : undefined,
65 },
66 }}
67 >
68 Generate new secret
69 </ButtonTooltip>
70 )}
71 </div>
72
73 {createdSecret && (
74 <Alert variant="default">
75 <InfoIcon />
76 <AlertTitle>
77 Make sure to copy your new client secret now. You won't be able to see it again.
78 </AlertTitle>
79 </Alert>
80 )}
81
82 <div className="-space-y-px">
83 {secrets
84 .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
85 .map((secret) => {
86 const isNew = createdSecret?.id === secret.id
87 const _secret = isNew
88 ? { ...secret, client_secret: createdSecret.client_secret }
89 : secret
90 return <SecretRow key={secret.id} secret={_secret} appId={appId} />
91 })}
92 </div>
93 </div>
94 )
95}