mirror of
https://github.com/supabase/supabase.git
synced 2026-05-09 10:19:50 -04:00
0facd341a6
## Problem We used to have a `_Shadcn_` suffix for all the shadcn form components because we also had `formik` form components. This is not needed anymore. ## Solution - Remove the suffix - Update all usages
190 lines
6.6 KiB
TypeScript
190 lines
6.6 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod'
|
|
import { SubmitHandler, useForm } from 'react-hook-form'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
Input_Shadcn_,
|
|
SidePanel,
|
|
Switch,
|
|
} from 'ui'
|
|
import z from 'zod'
|
|
|
|
import { ROLE_PERMISSIONS } from './Roles.constants'
|
|
import { FormActions } from '@/components/ui/Forms/FormActions'
|
|
import { useDatabaseRoleCreateMutation } from '@/data/database-roles/database-role-create-mutation'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
|
|
interface CreateRolePanelProps {
|
|
visible: boolean
|
|
onClose: () => void
|
|
}
|
|
|
|
const FormSchema = z.object({
|
|
name: z.string().trim().min(1, 'You must provide a name').default(''),
|
|
isSuperuser: z.boolean().default(false),
|
|
canLogin: z.boolean().default(false),
|
|
canCreateRole: z.boolean().default(false),
|
|
canCreateDb: z.boolean().default(false),
|
|
isReplicationRole: z.boolean().default(false),
|
|
canBypassRls: z.boolean().default(false),
|
|
})
|
|
|
|
const initialValues = {
|
|
name: '',
|
|
isSuperuser: false,
|
|
canLogin: false,
|
|
canCreateRole: false,
|
|
canCreateDb: false,
|
|
isReplicationRole: false,
|
|
canBypassRls: false,
|
|
}
|
|
|
|
export const CreateRolePanel = ({ visible, onClose }: CreateRolePanelProps) => {
|
|
const formId = 'create-new-role'
|
|
|
|
const { data: project } = useSelectedProjectQuery()
|
|
|
|
const form = useForm<z.infer<typeof FormSchema>>({
|
|
resolver: zodResolver(FormSchema),
|
|
})
|
|
|
|
const { mutate: createDatabaseRole, isPending: isCreating } = useDatabaseRoleCreateMutation({
|
|
onSuccess: (_, vars) => {
|
|
toast.success(`Successfully created new role: ${vars.payload.name}`)
|
|
handleClose()
|
|
},
|
|
})
|
|
|
|
const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (values) => {
|
|
if (!project) return console.error('Project is required')
|
|
createDatabaseRole({
|
|
projectRef: project.ref,
|
|
connectionString: project.connectionString,
|
|
payload: values,
|
|
})
|
|
}
|
|
|
|
const handleClose = () => {
|
|
onClose()
|
|
form.reset(initialValues)
|
|
}
|
|
|
|
return (
|
|
<SidePanel
|
|
size="large"
|
|
visible={visible}
|
|
header="Create a new role"
|
|
className="mr-0 transform transition-all duration-300 ease-in-out"
|
|
loading={false}
|
|
onCancel={handleClose}
|
|
customFooter={
|
|
<div className="flex w-full justify-end space-x-3 border-t border-default px-3 py-4">
|
|
<FormActions
|
|
form={formId}
|
|
isSubmitting={isCreating}
|
|
hasChanges={form.formState.isDirty}
|
|
handleReset={handleClose}
|
|
/>
|
|
</div>
|
|
}
|
|
>
|
|
<Form {...form}>
|
|
<form
|
|
id={formId}
|
|
className="grid gap-6 w-full px-8 py-8"
|
|
onSubmit={form.handleSubmit(onSubmit)}
|
|
>
|
|
<FormField
|
|
control={form.control}
|
|
name="name"
|
|
render={({ field }) => (
|
|
<FormItem className="grid gap-2 md:grid md:grid-cols-12 space-y-0">
|
|
<FormLabel className="flex flex-col space-y-2 col-span-4 text-sm justify-center text-foreground-light">
|
|
Name
|
|
</FormLabel>
|
|
<FormControl className="col-span-8">
|
|
<Input_Shadcn_ {...field} className="w-full" />
|
|
</FormControl>
|
|
<FormMessage className="col-start-5 col-span-8" />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<div className="grid gap-2 mt-4 md:grid md:grid-cols-12">
|
|
<div className="col-span-4">
|
|
<FormLabel className="flex flex-col space-y-2 col-span-4 text-sm justify-center text-foreground-light">
|
|
Role privileges
|
|
</FormLabel>
|
|
</div>
|
|
<div className="col-span-8 grid gap-4">
|
|
{(Object.keys(ROLE_PERMISSIONS) as (keyof typeof ROLE_PERMISSIONS)[])
|
|
.filter((permissionKey) => ROLE_PERMISSIONS[permissionKey].grant_by_dashboard)
|
|
.map((permissionKey) => {
|
|
const permission = ROLE_PERMISSIONS[permissionKey]
|
|
|
|
return (
|
|
<FormField
|
|
key={permissionKey}
|
|
control={form.control}
|
|
name={permissionKey}
|
|
render={({ field }) => (
|
|
<FormItem className="grid gap-2 md:grid md:grid-cols-12 space-y-0">
|
|
<FormControl className="col-span-8 flex items-center gap-4">
|
|
<div className="w-full text-sm">
|
|
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
|
<FormLabel>{permission.description}</FormLabel>
|
|
</div>
|
|
</FormControl>
|
|
<FormMessage className="col-start-5 col-span-8" />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
)
|
|
})}
|
|
|
|
<SidePanel.Separator />
|
|
|
|
<div className="grid gap-4">
|
|
<p className="text-sm">These privileges cannot be granted via the Dashboard:</p>
|
|
{(Object.keys(ROLE_PERMISSIONS) as (keyof typeof ROLE_PERMISSIONS)[])
|
|
.filter((permissionKey) => !ROLE_PERMISSIONS[permissionKey].grant_by_dashboard)
|
|
.map((permissionKey) => {
|
|
const permission = ROLE_PERMISSIONS[permissionKey]
|
|
|
|
return (
|
|
<FormField
|
|
key={permissionKey}
|
|
control={form.control}
|
|
name={permissionKey}
|
|
render={({ field }) => (
|
|
<FormItem className="space-y-0 opacity-70">
|
|
<FormControl className="flex items-center gap-4">
|
|
<div className="w-full text-sm">
|
|
<Switch
|
|
checked={field.value}
|
|
onCheckedChange={field.onChange}
|
|
disabled
|
|
aria-readonly
|
|
/>
|
|
<FormLabel>{permission.description}</FormLabel>
|
|
</div>
|
|
</FormControl>
|
|
<FormMessage className="col-start-5 col-span-8" />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</Form>
|
|
</SidePanel>
|
|
)
|
|
}
|