OrganizationDetailsForm.tsx111 lines · main
1import { zodResolver } from '@hookform/resolvers/zod'
2import { PermissionAction } from '@supabase/shared-types/out/constants'
3import { useQueryClient } from '@tanstack/react-query'
4import { useParams } from 'common'
5import { useEffect } from 'react'
6import { useForm } from 'react-hook-form'
7import { toast } from 'sonner'
8import { Card, CardContent, CardFooter, Form, FormControl, FormField, Input } from 'ui'
9import { Input as PasswordInput } from 'ui-patterns/DataInputs/Input'
10import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
11import * as z from 'zod'
12
13import { FormActions } from '@/components/ui/Forms/FormActions'
14import { useOrganizationUpdateMutation } from '@/data/organizations/organization-update-mutation'
15import { invalidateOrganizationsQuery } from '@/data/organizations/organizations-query'
16import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
17import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
18import type { ResponseError } from '@/types'
19
20const OrgDetailsSchema = z.object({
21 name: z.string().min(1, 'Organization name is required'),
22})
23
24export const OrganizationDetailsForm = () => {
25 const { slug } = useParams()
26 const queryClient = useQueryClient()
27 const { data: selectedOrganization } = useSelectedOrganizationQuery()
28
29 const { can: canUpdateOrganization } = useAsyncCheckPermissions(
30 PermissionAction.UPDATE,
31 'organizations'
32 )
33
34 const { mutate: updateOrganization, isPending: isUpdatingDetails } =
35 useOrganizationUpdateMutation()
36
37 const orgDetailsForm = useForm<z.infer<typeof OrgDetailsSchema>>({
38 resolver: zodResolver(OrgDetailsSchema as any),
39 defaultValues: { name: selectedOrganization?.name ?? '' },
40 })
41
42 const onUpdateOrganizationDetails = async (values: z.infer<typeof OrgDetailsSchema>) => {
43 if (!canUpdateOrganization) {
44 return toast.error('You do not have the required permissions to update this organization')
45 }
46 if (!slug) return console.error('Slug is required')
47
48 updateOrganization(
49 { slug, name: values.name },
50 {
51 onSuccess: () => {
52 invalidateOrganizationsQuery(queryClient)
53 toast.success('Successfully updated organization name')
54 },
55 onError: (error: ResponseError) => {
56 toast.error(`Failed to update organization name: ${error.message}`)
57 },
58 }
59 )
60 }
61
62 const permissionsHelperText = !canUpdateOrganization
63 ? "You need additional permissions to manage this organization's settings"
64 : undefined
65
66 useEffect(() => {
67 if (selectedOrganization && !isUpdatingDetails) {
68 orgDetailsForm.reset({ name: selectedOrganization.name ?? '' })
69 }
70 }, [selectedOrganization, orgDetailsForm, isUpdatingDetails])
71
72 return (
73 <Form {...orgDetailsForm}>
74 <form
75 id="org-details-form"
76 onSubmit={orgDetailsForm.handleSubmit(onUpdateOrganizationDetails)}
77 >
78 <Card>
79 <CardContent>
80 <FormField
81 control={orgDetailsForm.control}
82 name="name"
83 render={({ field }) => (
84 <FormItemLayout label="Organization name" layout="flex-row-reverse">
85 <FormControl>
86 <Input {...field} disabled={!canUpdateOrganization || isUpdatingDetails} />
87 </FormControl>
88 </FormItemLayout>
89 )}
90 />
91 </CardContent>
92 <CardContent>
93 <FormItemLayout label="Organization slug" layout="flex-row-reverse">
94 <PasswordInput copy disabled id="slug" value={selectedOrganization?.slug ?? ''} />
95 </FormItemLayout>
96 </CardContent>
97 <CardFooter className="flex justify-end p-4 md:px-8">
98 <FormActions
99 form="org-details-form"
100 isSubmitting={isUpdatingDetails}
101 hasChanges={orgDetailsForm.formState.isDirty}
102 handleReset={() => orgDetailsForm.reset()}
103 helper={permissionsHelperText}
104 disabled={!canUpdateOrganization}
105 />
106 </CardFooter>
107 </Card>
108 </form>
109 </Form>
110 )
111}