ChangeEmailAddress.tsx126 lines · main
1import HCaptcha from '@hcaptcha/react-hcaptcha'
2import { zodResolver } from '@hookform/resolvers/zod'
3import { useRef, useState } from 'react'
4import { SubmitHandler, useForm } from 'react-hook-form'
5import { toast } from 'sonner'
6import { Button, DialogFooter, DialogSection, Form, FormControl, FormField, Input } from 'ui'
7import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
8import * as z from 'zod'
9
10import { InlineLink } from '@/components/ui/InlineLink'
11import { useEmailUpdateMutation } from '@/data/profile/profile-update-email-mutation'
12
13export const GitHubChangeEmailAddress = () => {
14 return (
15 <DialogSection className="flex flex-col gap-y-2">
16 <p className="text-sm">
17 Email addresses for GitHub identities should be updated through GitHub
18 </p>
19 <ol className="flex flex-col gap-y-0.5 text-sm ml-4 pl-2 list-decimal text-foreground-light">
20 <li>Log out of Briven</li>
21 <li>
22 Change your Primary Email in{' '}
23 <InlineLink href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/changing-your-primary-email-address">
24 GitHub
25 </InlineLink>{' '}
26 (your primary email)
27 </li>
28 <li>Log out of GitHub</li>
29 <li>Log back into GitHub (with the new, desired email set as primary)</li>
30 <li>Log back into Briven</li>
31 </ol>
32 </DialogSection>
33 )
34}
35
36export const SSOChangeEmailAddress = () => {
37 return (
38 <DialogSection className="flex flex-col gap-y-2">
39 <p className="text-sm">
40 Email addresses for SSO should be updated through your identity provider
41 </p>
42 <ol className="flex flex-col gap-y-0.5 text-sm ml-4 pl-2 list-decimal text-foreground-light">
43 <li>Contact the owner / admin for your team to change your email</li>
44 </ol>
45 </DialogSection>
46 )
47}
48
49export const ChangeEmailAddressForm = ({ onClose }: { onClose: () => void }) => {
50 const captchaRef = useRef<HCaptcha>(null)
51 const [captchaToken, setCaptchaToken] = useState<string | null>(null)
52
53 const FormSchema = z.object({ email: z.string().email() })
54 const form = useForm<z.infer<typeof FormSchema>>({
55 mode: 'onBlur',
56 reValidateMode: 'onBlur',
57 resolver: zodResolver(FormSchema as any),
58 defaultValues: { email: '' },
59 })
60
61 const { mutate: updateEmail, isPending } = useEmailUpdateMutation({
62 onSuccess: (_, vars) => {
63 toast.success(
64 `A confirmation email has been sent to ${vars.email}. Please confirm the change within 10 minutes.`
65 )
66 onClose()
67 },
68 onError: (error) => {
69 toast.error(`Failed to update email: ${error.message}`)
70 setCaptchaToken(null)
71 captchaRef.current?.resetCaptcha()
72 },
73 })
74
75 const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (values) => {
76 let token = captchaToken
77 if (!token) {
78 const captchaResponse = await captchaRef.current?.execute({ async: true })
79 token = captchaResponse?.response ?? null
80 }
81
82 updateEmail({ email: values.email, hcaptchaToken: token ?? null })
83 }
84
85 return (
86 <Form {...form}>
87 <form id="update-email-form" onSubmit={form.handleSubmit(onSubmit)}>
88 <div className="self-center">
89 <HCaptcha
90 ref={captchaRef}
91 sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
92 size="invisible"
93 onVerify={(token) => setCaptchaToken(token)}
94 onExpire={() => setCaptchaToken(null)}
95 />
96 </div>
97
98 <DialogSection>
99 <FormField
100 name="email"
101 control={form.control}
102 render={({ field }) => (
103 <FormItemLayout
104 label="Provide a new email address"
105 description="A confirmation email will be sent to the provided email address"
106 >
107 <FormControl>
108 <Input {...field} placeholder="example@email.com" />
109 </FormControl>
110 </FormItemLayout>
111 )}
112 />
113 </DialogSection>
114
115 <DialogFooter>
116 <Button type="default" disabled={isPending} onClick={onClose}>
117 Cancel
118 </Button>
119 <Button htmlType="submit" loading={isPending} disabled={isPending}>
120 Confirm
121 </Button>
122 </DialogFooter>
123 </form>
124 </Form>
125 )
126}