From 4a432999691bb221b079f2329758c28c3bf6af9f Mon Sep 17 00:00:00 2001 From: Thanh Quy - wolf <524H0124@student.tdtu.edu.vn> Date: Mon, 20 Apr 2026 14:42:19 +0700 Subject: [PATCH] fix: enhance OTP sending and error handling in registration flow --- app/(main)/register/page.tsx | 84 +++++++++++++++++++----- components/organisms/forms/LoginForm.tsx | 52 ++++++++++++--- 2 files changed, 110 insertions(+), 26 deletions(-) diff --git a/app/(main)/register/page.tsx b/app/(main)/register/page.tsx index e981c80..1cdff6f 100644 --- a/app/(main)/register/page.tsx +++ b/app/(main)/register/page.tsx @@ -6,7 +6,7 @@ import { SHOP_INFO } from "@/lib/constants"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { FormEvent, useState } from "react"; +import { FormEvent, useEffect, useState } from "react"; // Static OTP for demo (in production, this would be sent via SMS) const DEMO_OTP = "123456"; @@ -20,6 +20,34 @@ export default function RegisterPage() { const [otp, setOtp] = useState(""); const [errors, setErrors] = useState({ phone: "", otp: "" }); const [isLoading, setIsLoading] = useState(false); + const [otpSending, setOtpSending] = useState(false); + const [otpSendError, setOtpSendError] = useState(""); + + const sendOtp = async () => { + setOtpSending(true); + setOtpSendError(""); + try { + const res = await fetch("/api/sms_otp", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ phone }), + }); + if (!res.ok) { + setOtpSendError("Không thể gửi OTP, vui lòng thử lại"); + } + } catch { + setOtpSendError("Không thể kết nối, vui lòng thử lại"); + } finally { + setOtpSending(false); + } + }; + + useEffect(() => { + if (step === "otp") { + sendOtp(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [step]); // Validate Vietnamese phone number const validatePhone = (phoneNumber: string): boolean => { @@ -58,11 +86,12 @@ export default function RegisterPage() { if (res.ok) { setStep("otp"); } else { - const data = await res.json().catch(() => ({})); - const msg = - data?.error === "ExistedUser" - ? "Số điện thoại đã được đăng ký" - : "Có lỗi xảy ra, vui lòng thử lại"; + const errorCode = (await res.text().catch(() => "")).trim(); + const phoneErrorMap: Record = { + ExistedUser: "Số điện thoại đã được đăng ký", + InvalidPhoneNumber: "Số điện thoại không hợp lệ", + }; + const msg = phoneErrorMap[errorCode] ?? "Đã có lỗi xảy ra, vui lòng thử lại"; setErrors({ phone: msg, otp: "" }); } } catch { @@ -96,11 +125,11 @@ export default function RegisterPage() { localStorage.setItem("coffee-shop-user", JSON.stringify(userData)); router.push("/"); } else { - const data = await res.json().catch(() => ({})); + const errorCode = (await res.text().catch(() => "")).trim(); const msg = - data?.error === "InvalidOTP" - ? "Mã OTP không đúng" - : "Có lỗi xảy ra, vui lòng thử lại"; + errorCode === "InvalidOTP" + ? "Mã OTP không đúng hoặc đã hết hạn" + : "Đã có lỗi xảy ra, vui lòng thử lại"; setErrors({ phone: "", otp: msg }); } } catch { @@ -240,7 +269,16 @@ export default function RegisterPage() {

- Mã OTP đã được gửi đến số {phone} + {otpSending ? ( + <> + + Đang gửi OTP đến {phone}... + + ) : ( + <> + Mã OTP đã được gửi đến số {phone} + + )}

Demo OTP:{" "} @@ -249,6 +287,12 @@ export default function RegisterPage() {

+ {otpSendError && ( +

+ + {otpSendError} +

+ )} {/* OTP Input */}
@@ -290,7 +334,7 @@ export default function RegisterPage() { type="submit" style="login" size="lg" - disabled={isLoading} + disabled={isLoading || otpSending} > {isLoading ? ( <> @@ -314,14 +358,22 @@ export default function RegisterPage() {
- {/* Resend OTP (disabled in demo) */} + {/* Resend OTP */}
diff --git a/components/organisms/forms/LoginForm.tsx b/components/organisms/forms/LoginForm.tsx index 3d9c404..f0f0ee6 100644 --- a/components/organisms/forms/LoginForm.tsx +++ b/components/organisms/forms/LoginForm.tsx @@ -6,12 +6,18 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { SubmitEvent, useState } from "react"; +const LOGIN_ERROR_MAP: Record = { + InvalidPhoneNumber: { field: "username", msg: "Số điện thoại không hợp lệ" }, + InvalidPassword: { field: "password", msg: "Mật khẩu không đúng" }, +}; + export default function LoginForm() { const router = useRouter(); - const { login } = useAuth(); + const { setUser } = useAuth(); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); const [errors, setErrors] = useState({ username: "", password: "", @@ -44,16 +50,34 @@ export default function LoginForm() { if (!validate()) return; - const success = await login(username, password); + setIsLoading(true); + setErrors({ username: "", password: "", general: "" }); - if (success) { - router.push("/"); - } else { - setErrors({ - username: "", - password: "", - general: "Tên đăng nhập hoặc mật khẩu không đúng", + try { + const res = await fetch("/api/customer/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ phone: username, password }), }); + + if (res.ok) { + const userData = await res.json(); + setUser(userData); + localStorage.setItem("coffee-shop-user", JSON.stringify(userData)); + router.push("/"); + } else { + const errorCode = (await res.text().catch(() => "")).trim(); + const mapped = LOGIN_ERROR_MAP[errorCode]; + if (mapped) { + setErrors({ username: "", password: "", general: "", [mapped.field]: mapped.msg }); + } else { + setErrors({ username: "", password: "", general: "Đã có lỗi xảy ra, vui lòng thử lại" }); + } + } + } catch { + setErrors({ username: "", password: "", general: "Không thể kết nối, vui lòng thử lại" }); + } finally { + setIsLoading(false); } }; @@ -107,8 +131,16 @@ export default function LoginForm() { type="submit" style="login" size="lg" + disabled={isLoading} > - Đăng nhập + {isLoading ? ( + <> + + Đang xử lý... + + ) : ( + "Đăng nhập" + )} {/* Register Button */}