SiteUrl.tsx150 lines · main
1import { zodResolver } from '@hookform/resolvers/zod'
2import { PermissionAction } from '@supabase/shared-types/out/constants'
3import { useParams } from 'common'
4import { useEffect, useState } from 'react'
5import { useForm } from 'react-hook-form'
6import { toast } from 'sonner'
7import { Button, Card, CardContent, CardFooter, Form, FormControl, FormField, Input } from 'ui'
8import { GenericSkeletonLoader } from 'ui-patterns'
9import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
10import {
11 PageSection,
12 PageSectionContent,
13 PageSectionMeta,
14 PageSectionSummary,
15 PageSectionTitle,
16} from 'ui-patterns/PageSection'
17import * as z from 'zod'
18
19import AlertError from '@/components/ui/AlertError'
20import { useAuthConfigQuery } from '@/data/auth/auth-config-query'
21import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation'
22import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
23
24const schema = z.object({
25 SITE_URL: z.string().min(1, 'Must have a Site URL'),
26})
27
28const SiteUrl = () => {
29 const { ref: projectRef } = useParams()
30 const {
31 data: authConfig,
32 error: authConfigError,
33 isError,
34 isPending: isLoading,
35 } = useAuthConfigQuery({ projectRef })
36 const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation()
37 const [isUpdatingSiteUrl, setIsUpdatingSiteUrl] = useState(false)
38
39 const { can: canUpdateConfig } = useAsyncCheckPermissions(
40 PermissionAction.UPDATE,
41 'custom_config_gotrue'
42 )
43
44 const siteUrlForm = useForm({
45 resolver: zodResolver(schema as any),
46 defaultValues: {
47 SITE_URL: '',
48 },
49 })
50 const { isDirty } = siteUrlForm.formState
51
52 useEffect(() => {
53 if (authConfig && !isUpdatingSiteUrl) {
54 siteUrlForm.reset({
55 SITE_URL: authConfig.SITE_URL || '',
56 })
57 }
58 }, [authConfig, isUpdatingSiteUrl])
59
60 const onSubmitSiteUrl = (values: any) => {
61 setIsUpdatingSiteUrl(true)
62
63 updateAuthConfig(
64 { projectRef: projectRef!, config: values },
65 {
66 onError: (error) => {
67 toast.error(`Failed to update site URL: ${error?.message}`)
68 setIsUpdatingSiteUrl(false)
69 },
70 onSuccess: () => {
71 toast.success('Successfully updated site URL')
72 setIsUpdatingSiteUrl(false)
73 },
74 }
75 )
76 }
77
78 if (isError) {
79 return (
80 <PageSection>
81 <PageSectionContent>
82 <AlertError error={authConfigError} subject="Failed to retrieve auth configuration" />
83 </PageSectionContent>
84 </PageSection>
85 )
86 }
87
88 if (isLoading) {
89 return (
90 <PageSection>
91 <PageSectionContent>
92 <GenericSkeletonLoader />
93 </PageSectionContent>
94 </PageSection>
95 )
96 }
97
98 return (
99 <PageSection>
100 <PageSectionMeta>
101 <PageSectionSummary>
102 <PageSectionTitle>Site URL</PageSectionTitle>
103 </PageSectionSummary>
104 </PageSectionMeta>
105 <PageSectionContent>
106 <Form {...siteUrlForm}>
107 <form onSubmit={siteUrlForm.handleSubmit(onSubmitSiteUrl)}>
108 <Card>
109 <CardContent>
110 <FormField
111 control={siteUrlForm.control}
112 name="SITE_URL"
113 render={({ field }) => (
114 <FormItemLayout
115 layout="flex-row-reverse"
116 label="Site URL"
117 description="Configure the default redirect URL used when a redirect URL is not specified or doesn't match one from the allow list. This value is also exposed as a template variable in the email templates section. Wildcards cannot be used here."
118 >
119 <FormControl>
120 <Input {...field} disabled={!canUpdateConfig} />
121 </FormControl>
122 </FormItemLayout>
123 )}
124 />
125 </CardContent>
126
127 <CardFooter className="justify-end space-x-2">
128 {isDirty && (
129 <Button type="default" onClick={() => siteUrlForm.reset()}>
130 Cancel
131 </Button>
132 )}
133 <Button
134 type="primary"
135 htmlType="submit"
136 disabled={!canUpdateConfig || isUpdatingSiteUrl || !isDirty}
137 loading={isUpdatingSiteUrl}
138 >
139 Save changes
140 </Button>
141 </CardFooter>
142 </Card>
143 </form>
144 </Form>
145 </PageSectionContent>
146 </PageSection>
147 )
148}
149
150export default SiteUrl