aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/admin/CreateInviteDialog.tsx
blob: 84f5c60f7e9cf25df0cd68b93121fbb38599e7b3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
"use client";

import { useState } from "react";
import { ActionButton } from "@/components/ui/action-button";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { toast } from "@/components/ui/use-toast";
import { api } from "@/lib/trpc";
import { zodResolver } from "@hookform/resolvers/zod";
import { TRPCClientError } from "@trpc/client";
import { useForm } from "react-hook-form";
import { z } from "zod";

const createInviteSchema = z.object({
  email: z.string().email("Please enter a valid email address"),
});

interface CreateInviteDialogProps {
  children: React.ReactNode;
}

export default function CreateInviteDialog({
  children,
}: CreateInviteDialogProps) {
  const [open, setOpen] = useState(false);
  const [errorMessage, setErrorMessage] = useState("");

  const form = useForm<z.infer<typeof createInviteSchema>>({
    resolver: zodResolver(createInviteSchema),
    defaultValues: {
      email: "",
    },
  });

  const invalidateInvitesList = api.useUtils().invites.list.invalidate;
  const createInviteMutation = api.invites.create.useMutation({
    onSuccess: () => {
      toast({
        description: "Invite sent successfully",
      });
      invalidateInvitesList();
      setOpen(false);
      form.reset();
      setErrorMessage("");
    },
    onError: (e) => {
      if (e instanceof TRPCClientError) {
        setErrorMessage(e.message);
      } else {
        setErrorMessage("Failed to send invite");
      }
    },
  });

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger asChild>{children}</DialogTrigger>
      <DialogContent className="sm:max-w-md">
        <DialogHeader>
          <DialogTitle>Send User Invitation</DialogTitle>
          <DialogDescription>
            Send an invitation to a new user to join Karakeep. They&apos;ll
            receive an email with instructions to create their account and will
            be assigned the &quot;user&quot; role.
          </DialogDescription>
        </DialogHeader>

        <Form {...form}>
          <form
            onSubmit={form.handleSubmit(async (value) => {
              setErrorMessage("");
              await createInviteMutation.mutateAsync(value);
            })}
            className="space-y-4"
          >
            {errorMessage && (
              <p className="text-sm text-destructive">{errorMessage}</p>
            )}

            <FormField
              control={form.control}
              name="email"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Email Address</FormLabel>
                  <FormControl>
                    <Input
                      type="email"
                      placeholder="user@example.com"
                      {...field}
                    />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />

            <div className="flex justify-end space-x-2">
              <ActionButton
                type="button"
                variant="outline"
                loading={false}
                onClick={() => setOpen(false)}
              >
                Cancel
              </ActionButton>
              <ActionButton
                type="submit"
                loading={createInviteMutation.isPending}
              >
                Send Invitation
              </ActionButton>
            </div>
          </form>
        </Form>
      </DialogContent>
    </Dialog>
  );
}