Просмотр исходного кода

feat: implement user invite dialog

satnaing 1 год назад
Родитель
Сommit
9f11bcbc0d

+ 153 - 0
src/features/users/components/users-invite-dialog.tsx

@@ -0,0 +1,153 @@
+import { z } from 'zod'
+import { useForm } from 'react-hook-form'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { IconMailPlus, IconSend } from '@tabler/icons-react'
+import { toast } from '@/hooks/use-toast'
+import { Button } from '@/components/ui/button'
+import {
+  Dialog,
+  DialogClose,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+} from '@/components/ui/dialog'
+import {
+  Form,
+  FormControl,
+  FormField,
+  FormItem,
+  FormLabel,
+  FormMessage,
+} from '@/components/ui/form'
+import { Input } from '@/components/ui/input'
+import { Textarea } from '@/components/ui/textarea'
+import { SelectDropdown } from '@/components/select-dropdown'
+import { userTypes } from '../data/data'
+
+const formSchema = z.object({
+  email: z
+    .string()
+    .min(1, { message: 'Email is required.' })
+    .email({ message: 'Email is invalid.' }),
+  role: z.string().min(1, { message: 'Role is required.' }),
+  desc: z.string().optional(),
+})
+type UserInviteForm = z.infer<typeof formSchema>
+
+interface Props {
+  open: boolean
+  onOpenChange: (open: boolean) => void
+}
+
+export function UsersInviteDialog({ open, onOpenChange }: Props) {
+  const form = useForm<UserInviteForm>({
+    resolver: zodResolver(formSchema),
+    defaultValues: { email: '', role: '', desc: '' },
+  })
+
+  const onSubmit = (values: UserInviteForm) => {
+    form.reset()
+    toast({
+      title: 'You submitted the following values:',
+      description: (
+        <pre className='mt-2 w-[340px] rounded-md bg-slate-950 p-4'>
+          <code className='text-white'>{JSON.stringify(values, null, 2)}</code>
+        </pre>
+      ),
+    })
+    onOpenChange(false)
+  }
+
+  return (
+    <Dialog
+      open={open}
+      onOpenChange={(state) => {
+        form.reset()
+        onOpenChange(state)
+      }}
+    >
+      <DialogContent className='sm:max-w-md'>
+        <DialogHeader>
+          <DialogTitle className='flex items-center gap-2'>
+            <IconMailPlus /> Invite User
+          </DialogTitle>
+          <DialogDescription>
+            Invite new user to join your team by sending them an email
+            invitation. Assign a role to define their access level.
+          </DialogDescription>
+        </DialogHeader>
+        <Form {...form}>
+          <form
+            id='user-invite-form'
+            onSubmit={form.handleSubmit(onSubmit)}
+            className='space-y-4'
+          >
+            <FormField
+              control={form.control}
+              name='email'
+              render={({ field }) => (
+                <FormItem>
+                  <FormLabel>Email</FormLabel>
+                  <FormControl>
+                    <Input
+                      type='email'
+                      placeholder='eg: john.doe@gmail.com'
+                      {...field}
+                    />
+                  </FormControl>
+                  <FormMessage />
+                </FormItem>
+              )}
+            />
+            <FormField
+              control={form.control}
+              name='role'
+              render={({ field }) => (
+                <FormItem className='space-y-1'>
+                  <FormLabel>Role</FormLabel>
+                  <SelectDropdown
+                    defaultValue={field.value}
+                    onValueChange={field.onChange}
+                    placeholder='Select a role'
+                    items={userTypes.map(({ label, value }) => ({
+                      label,
+                      value,
+                    }))}
+                  />
+                  <FormMessage />
+                </FormItem>
+              )}
+            />
+            <FormField
+              control={form.control}
+              name='desc'
+              render={({ field }) => (
+                <FormItem className=''>
+                  <FormLabel>Description (optional)</FormLabel>
+                  <FormControl>
+                    <Textarea
+                      className='resize-none'
+                      placeholder='Add a personal note to your invitation (optional)'
+                      {...field}
+                    />
+                  </FormControl>
+                  <FormMessage />
+                </FormItem>
+              )}
+            />
+          </form>
+        </Form>
+        <DialogFooter>
+          <DialogClose asChild>
+            <Button variant='outline'>Cancel</Button>
+          </DialogClose>
+          <Button type='submit' form='user-invite-form'>
+            Invite <IconSend />
+          </Button>
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
+  )
+}

+ 1 - 1
src/features/users/context/users-context.tsx

@@ -1,7 +1,7 @@
 import React from 'react'
 import { User } from '../data/schema'
 
-export type UsersDialogType = 'add' | 'edit' | 'delete'
+export type UsersDialogType = 'invite' | 'add' | 'edit' | 'delete'
 
 interface UsersContextType {
   open: UsersDialogType | null

+ 12 - 1
src/features/users/index.tsx

@@ -10,6 +10,7 @@ import { ThemeSwitch } from '@/components/theme-switch'
 import { UsersActionDialog } from './components/users-action-dialog'
 import { columns } from './components/users-columns'
 import { UsersDeleteDialog } from './components/users-delete-dialog'
+import { UsersInviteDialog } from './components/users-invite-dialog'
 import { UsersTable } from './components/users-table'
 import UsersContextProvider, {
   type UsersDialogType,
@@ -45,7 +46,11 @@ export default function Users() {
             </p>
           </div>
           <div className='flex gap-2'>
-            <Button variant='outline' className='space-x-1'>
+            <Button
+              variant='outline'
+              className='space-x-1'
+              onClick={() => setOpen('invite')}
+            >
               <span>Invite User</span> <IconMailPlus />
             </Button>
             <Button className='space-x-1' onClick={() => setOpen('add')}>
@@ -64,6 +69,12 @@ export default function Users() {
         onOpenChange={() => setOpen('add')}
       />
 
+      <UsersInviteDialog
+        key='user-invite'
+        open={open === 'invite'}
+        onOpenChange={() => setOpen('invite')}
+      />
+
       {currentRow && (
         <>
           <UsersActionDialog