feat: allow change email
This commit is contained in:
parent
7233a58ca7
commit
97bacc99f1
|
|
@ -0,0 +1,123 @@
|
|||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@follow/components/ui/form/index.js"
|
||||
import { Input } from "@follow/components/ui/input/Input.js"
|
||||
import { changeEmail, sendVerificationEmail } from "@follow/shared/auth"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
import { z } from "zod"
|
||||
|
||||
import { setWhoami, useWhoami } from "~/atoms/user"
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email(),
|
||||
})
|
||||
|
||||
export function EmailManagement() {
|
||||
const { t } = useTranslation("settings")
|
||||
const user = useWhoami()
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
email: user?.email || "",
|
||||
},
|
||||
})
|
||||
|
||||
const updateEmailMutation = useMutation({
|
||||
mutationFn: async (values: z.infer<typeof formSchema>) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
const res = await changeEmail({ newEmail: values.email })
|
||||
if (res.error) {
|
||||
throw new Error(res.error.message)
|
||||
}
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
if (user?.emailVerified) {
|
||||
toast.success(t("profile.email.changed_verification_sent"))
|
||||
} else {
|
||||
if (user) {
|
||||
setWhoami({
|
||||
...user,
|
||||
email: variables.email,
|
||||
})
|
||||
}
|
||||
form.reset({ email: variables.email })
|
||||
toast.success(t("profile.email.changed"))
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message)
|
||||
},
|
||||
})
|
||||
|
||||
const verifyEmailMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!user?.email) return
|
||||
return sendVerificationEmail({
|
||||
email: user.email,
|
||||
})
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success(t("profile.email.verification_sent"))
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit((values) => {
|
||||
updateEmailMutation.mutate(values)
|
||||
})}
|
||||
className="space-y-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("profile.email.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Input {...field} />
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="space-x-2 text-right">
|
||||
{!user?.emailVerified && !form.formState.isDirty && (
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
isLoading={verifyEmailMutation.isPending}
|
||||
onClick={() => {
|
||||
verifyEmailMutation.mutate()
|
||||
}}
|
||||
>
|
||||
{t("profile.email.send_verification")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={updateEmailMutation.isPending}
|
||||
disabled={updateEmailMutation.isPending || !form.formState.isDirty}
|
||||
>
|
||||
{t("profile.email.change")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
|
@ -10,8 +10,7 @@ import {
|
|||
FormMessage,
|
||||
} from "@follow/components/ui/form/index.jsx"
|
||||
import { Input } from "@follow/components/ui/input/index.js"
|
||||
import { Label } from "@follow/components/ui/label/index.js"
|
||||
import { sendVerificationEmail, updateUser } from "@follow/shared/auth"
|
||||
import { updateUser } from "@follow/shared/auth"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
|
|
@ -21,7 +20,6 @@ import { toast } from "sonner"
|
|||
import { z } from "zod"
|
||||
|
||||
import { setWhoami, useWhoami } from "~/atoms/user"
|
||||
import { CopyButton } from "~/components/ui/code-highlighter"
|
||||
import { toastFetchError } from "~/lib/error-parser"
|
||||
|
||||
const formSchema = z.object({
|
||||
|
|
@ -69,18 +67,6 @@ export const ProfileSettingForm = ({
|
|||
},
|
||||
})
|
||||
|
||||
const verifyEmailMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!user?.email) return
|
||||
return sendVerificationEmail({
|
||||
email: user.email,
|
||||
})
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success(t("profile.email.verification_sent"))
|
||||
},
|
||||
})
|
||||
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
updateMutation.mutate(values)
|
||||
}
|
||||
|
|
@ -88,34 +74,6 @@ export const ProfileSettingForm = ({
|
|||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className={cn("mt-4 space-y-4", className)}>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("profile.email.label")}</Label>
|
||||
<p className="group flex gap-2 text-sm text-muted-foreground">
|
||||
{user?.email}
|
||||
|
||||
{user?.email && (
|
||||
<CopyButton
|
||||
value={user.email}
|
||||
className="size-5 p-1 opacity-0 duration-300 group-hover:opacity-100"
|
||||
/>
|
||||
)}
|
||||
<span className={cn(user?.emailVerified ? "text-green-500" : "text-red-500")}>
|
||||
{user?.emailVerified ? t("profile.email.verified") : t("profile.email.unverified")}
|
||||
</span>
|
||||
</p>
|
||||
{!user?.emailVerified && (
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
isLoading={verifyEmailMutation.isPending}
|
||||
onClick={() => {
|
||||
verifyEmailMutation.mutate()
|
||||
}}
|
||||
>
|
||||
{t("profile.email.send_verification")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="handle"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { AccountManagement } from "~/modules/profile/account-management"
|
||||
import { EmailManagement } from "~/modules/profile/email-management"
|
||||
import { ProfileSettingForm } from "~/modules/profile/profile-setting-form"
|
||||
import { UpdatePasswordForm } from "~/modules/profile/update-password-form"
|
||||
import { SettingsTitle } from "~/modules/settings/title"
|
||||
|
|
@ -16,6 +17,7 @@ export function Component() {
|
|||
return (
|
||||
<>
|
||||
<SettingsTitle />
|
||||
<EmailManagement />
|
||||
<ProfileSettingForm />
|
||||
<AccountManagement />
|
||||
<UpdatePasswordForm />
|
||||
|
|
|
|||
|
|
@ -268,6 +268,9 @@
|
|||
"profile.change_password.label": "Change Password",
|
||||
"profile.confirm_password.label": "Confirm Password",
|
||||
"profile.current_password.label": "Current Password",
|
||||
"profile.email.change": "Change Email",
|
||||
"profile.email.changed": "Email changed.",
|
||||
"profile.email.changed_verification_sent": "Email to verify the new email has been sent.",
|
||||
"profile.email.label": "Email",
|
||||
"profile.email.send_verification": "Send Verification Email",
|
||||
"profile.email.unverified": "Unverified",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ const authClient = createAuthClient({
|
|||
|
||||
// @keep-sorted
|
||||
export const {
|
||||
changeEmail,
|
||||
changePassword,
|
||||
createSession,
|
||||
forgetPassword,
|
||||
|
|
|
|||
|
|
@ -9628,6 +9628,15 @@ declare const auth: {
|
|||
type: "string";
|
||||
};
|
||||
};
|
||||
changeEmail: {
|
||||
enabled: true;
|
||||
sendChangeEmailVerification: ({ newEmail, url }: {
|
||||
user: better_auth.User;
|
||||
newEmail: string;
|
||||
url: string;
|
||||
token: string;
|
||||
}) => Promise<void>;
|
||||
};
|
||||
};
|
||||
account: {
|
||||
accountLinking: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue