Skip to content

Commit 81fcb64

Browse files
authored
feat(ssr): enhance email validation and reCAPTCHA handling
feat(ssr): enhance email validation and reCAPTCHA handling
1 parent 099dee9 commit 81fcb64

File tree

4 files changed

+63
-13
lines changed

4 files changed

+63
-13
lines changed

apps/ssr/client/@types/default-resource.ts

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import common_zhCN from "@locales/common/zh-CN.json"
2121
import common_zhHK from "@locales/common/zh-HK.json"
2222
import common_zhTW from "@locales/common/zh-TW.json"
2323
import external_en from "@locales/external/en.json"
24+
import external_zhCN from "@locales/external/zh-CN.json"
2425

2526
import type { ns, SSRSupportedLanguages } from "./constants"
2627

@@ -38,6 +39,7 @@ export const defaultResources = {
3839
},
3940
"zh-CN": {
4041
common: common_zhCN,
42+
external: external_zhCN,
4143
},
4244
"zh-HK": {
4345
common: common_zhHK,

apps/ssr/client/pages/(login)/forget-password.tsx

+56-13
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,65 @@ import { Input } from "@follow/components/ui/input/index.js"
1919
import { env } from "@follow/shared/env"
2020
import { zodResolver } from "@hookform/resolvers/zod"
2121
import { useMutation } from "@tanstack/react-query"
22-
import { useRef } from "react"
22+
import { useEffect, useRef } from "react"
2323
import ReCAPTCHA from "react-google-recaptcha"
2424
import { useForm } from "react-hook-form"
2525
import { useTranslation } from "react-i18next"
2626
import { useNavigate } from "react-router"
2727
import { toast } from "sonner"
2828
import { z } from "zod"
2929

30-
const forgetPasswordFormSchema = z.object({
31-
email: z.string().email(),
32-
})
30+
function closeRecaptcha(
31+
recaptchaRef: React.RefObject<ReCAPTCHA>,
32+
mutation?: { reset: () => void },
33+
) {
34+
const handleClick = (e: MouseEvent) => {
35+
const recaptchaIframeSelector =
36+
'iframe[src*="recaptcha/api2"], iframe[src*="www.recaptcha.net"], iframe[src*="google.com/recaptcha"]'
37+
const recaptchaChallengeIframe = document.querySelector(recaptchaIframeSelector)
38+
39+
if (
40+
e.target instanceof Element &&
41+
(!recaptchaChallengeIframe || !recaptchaChallengeIframe.contains(e.target)) &&
42+
!e.target.closest(".g-recaptcha") &&
43+
recaptchaChallengeIframe
44+
) {
45+
recaptchaRef.current?.reset()
46+
mutation?.reset()
47+
}
48+
}
49+
50+
document.addEventListener("click", handleClick)
51+
return () => document.removeEventListener("click", handleClick)
52+
}
53+
54+
const createEmailSchema = (t: any) =>
55+
z.object({
56+
email: z
57+
.string()
58+
.min(1, t("login.forget_password.email_required"))
59+
.email(t("login.forget_password.email_invalid")),
60+
})
3361

3462
export function Component() {
3563
const { t } = useTranslation()
36-
const form = useForm<z.infer<typeof forgetPasswordFormSchema>>({
37-
resolver: zodResolver(forgetPasswordFormSchema),
64+
const navigate = useNavigate()
65+
const recaptchaRef = useRef<ReCAPTCHA>(null)
66+
67+
const EmailSchema = createEmailSchema(t)
68+
69+
const form = useForm<z.infer<typeof EmailSchema>>({
70+
resolver: zodResolver(EmailSchema),
3871
defaultValues: {
3972
email: "",
4073
},
74+
mode: "onChange",
75+
delayError: 500,
4176
})
4277

43-
const recaptchaRef = useRef<ReCAPTCHA>(null)
44-
4578
const { isValid } = form.formState
4679
const updateMutation = useMutation({
47-
mutationFn: async (values: z.infer<typeof forgetPasswordFormSchema>) => {
80+
mutationFn: async (values: z.infer<typeof EmailSchema>) => {
4881
const token = await recaptchaRef.current?.executeAsync()
4982
const res = await forgetPassword(
5083
{
@@ -62,19 +95,25 @@ export function Component() {
6295
}
6396
},
6497
onError: (error) => {
98+
recaptchaRef.current?.reset()
6599
toast.error(error.message)
66100
},
67101
onSuccess: () => {
68102
toast.success(t("login.forget_password.success"))
69103
},
104+
onSettled: () => {
105+
recaptchaRef.current?.reset()
106+
},
70107
})
71108

72-
function onSubmit(values: z.infer<typeof forgetPasswordFormSchema>) {
109+
useEffect(() => {
110+
return closeRecaptcha(recaptchaRef, updateMutation)
111+
}, [updateMutation])
112+
113+
function onSubmit(values: z.infer<typeof EmailSchema>) {
73114
updateMutation.mutate(values)
74115
}
75116

76-
const navigate = useNavigate()
77-
78117
return (
79118
<div className="flex h-full items-center justify-center">
80119
<Card className="w-[350px] max-w-full">
@@ -116,7 +155,11 @@ export function Component() {
116155
size="invisible"
117156
/>
118157
<div className="text-right">
119-
<Button disabled={!isValid} type="submit" isLoading={updateMutation.isPending}>
158+
<Button
159+
disabled={!isValid || updateMutation.isPending}
160+
type="submit"
161+
isLoading={updateMutation.isPending}
162+
>
120163
{t("login.submit")}
121164
</Button>
122165
</div>

locales/external/en.json

+3
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@
3939
"login.continueWith": "Continue with {{provider}}",
4040
"login.email": "Email",
4141
"login.forget_password.description": "Enter the email address associated with your account and we’ll send you an email about how to reset your password.",
42+
"login.forget_password.email_invalid": "Invalid email",
43+
"login.forget_password.email_required": "Email required",
4244
"login.forget_password.label": "Forget Password",
4345
"login.forget_password.note": "Forgot your password?",
46+
"login.forget_password.recaptcha_required": "Please complete the reCAPTCHA verification",
4447
"login.forget_password.success": "Email has been sent successfully",
4548
"login.logInTo": "Sign in to",
4649
"login.logInWithEmail": "Sign in with email",

locales/external/zh-CN.json

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
"login.continueWith": "使用 {{provider}} 登入",
4040
"login.email": "邮件地址",
4141
"login.forget_password.description": "请输入与你的帐户关联的邮件地址,我们将向你发送一封关于如何重置密码的邮件。",
42+
"login.forget_password.email_invalid": "无效的邮箱地址",
43+
"login.forget_password.email_required": "请输入邮箱",
4244
"login.forget_password.label": "忘记密码",
4345
"login.forget_password.note": "忘记了密码?",
4446
"login.forget_password.success": "邮件已成功发送。",

0 commit comments

Comments
 (0)