| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- "use client";
- import clsx from "clsx";
- import { getSession, signIn } from "next-auth/react";
- import Image from "next/image";
- import Link from "next/link";
- import { useRouter } from "next/navigation";
- import { SubmitHandler, useForm } from "react-hook-form";
- import { Button } from "@components/common/button/Button";
- import { EMAIL_REGEX, SIGNIN_IMG } from "@/utils/constants";
- import InputText from "@components/common/form/Input";
- import { useCustomToast } from "@/utils/hooks/useToast";
- import { useMergeCart } from "@utils/hooks/useMergeCart";
- import {
- setCookie,
- getGuestCartToken,
- getGuestCartId,
- deleteGuestCookie
- } from "@/utils/cookie-tools";
- import { useAppSelector } from "@/store/hooks";
- import { useCartDetail } from "@utils/hooks/useCartDetail";
- import { IS_GUEST } from "@/utils/constants";
- type LoginFormInputs = {
- username: string;
- password: string;
- };
- export default function LoginForm() {
- const router = useRouter();
- const { showToast } = useCustomToast();
- const { getCartDetail } = useCartDetail()
- const { mergeCart } = useMergeCart();
- const cart = useAppSelector((state) => state.cartDetail.cart);
- const {
- register,
- handleSubmit,
- formState: { errors, isSubmitting },
- } = useForm<LoginFormInputs>({
- mode: "onSubmit",
- reValidateMode: "onChange",
- });
- const onSubmit: SubmitHandler<LoginFormInputs> = async (data) => {
- try {
- // First, handle cart merging before sign in
- const guestCartId = getGuestCartId();
- const guestCartToken = getGuestCartToken();
- /**
- * @todo 使用 signInAuth 重写
- * result: {error: null,ok: true,status: 200,url: "http://localhost:3001/"}
- */
- const result = await signIn("credentials", {
- redirect: false,
- ...data,
- callbackUrl: "/", // The callbackUrl specifies to which URL the user will be redirected after signing in. Defaults to the page URL the sign-in is initiated from.
- });
- console.log('result --- ',result);
- if (!result?.ok) {
- showToast(result?.error || "Invalid login credentials.", "warning");
- return;
- }
- showToast("Welcome! Successfully logged in.", "success");
- const session = await getSession();
- const userToken: string | undefined = session?.user?.accessToken;
- if (!userToken) {
- console.warn("No API token available in session after login");
- console.error('userToken is required');
- }
- // Only merge cart if user had a guest cart before login
- if (userToken && guestCartId && guestCartToken) {
- try {
- if(cart) {
- await mergeCart({ variables: { token: userToken, cartId: parseInt(guestCartId, 10) } });
- }
-
- } catch (err) {
- console.error("mergeCart failed:", err);
- }
- // 登录成功后不应该保存用户的token
- setCookie(IS_GUEST, "false");
- deleteGuestCookie();
- await getCartDetail();
- } else if (userToken) {
- // User logged in without a guest cart, just set the token
- setCookie(IS_GUEST, "false");
- deleteGuestCookie();
- }
- setTimeout(() => {
- router.push("/");
- router.refresh();
- }, 100);
- } catch (error) {
- console.error(error);
- showToast("Something went wrong. Please try again.", "danger");
- }
- };
- return (
- <div className="flex w-full items-center max-w-screen-2xl mx-auto px-4 xss:px-7.5 justify-between gap-4 lg:my-16 xl:my-28">
- <div className="flex w-full max-w-[583px] flex-col gap-y-4 lg:gap-y-12">
- <div className="font-outfit">
- <h2 className="py-1 text-2xl font-semibold sm:text-4xl">
- Sign in to your account
- </h2>
- <p className="mt-2 text-base md:text-lg font-normal text-black/60 dark:text-neutral-400">
- If you have an account, sign in with your email address.
- </p>
- </div>
- <form
- noValidate
- className="flex flex-col gap-y-4 lg:gap-y-12"
- onSubmit={handleSubmit(onSubmit)}
- >
- <div className="flex flex-col gap-y-2.5 lg:gap-4">
- <InputText
- {...register("username", {
- required: "Email is required",
- pattern: {
- value: EMAIL_REGEX,
- message: "Please enter a valid email.",
- },
- })}
- errorMsg={
- errors.username?.message ? [errors.username.message] : undefined
- }
- label="Enter Your Email Address"
- labelPlacement="outside"
- name="username"
- placeholder="Enter your email address"
- rounded="md"
- size="lg"
- typeName="email"
- />
- <InputText
- {...register("password", {
- required: "Password is required",
- minLength: {
- value: 2,
- message: "Be at least 2 characters long",
- },
- validate: (value) => {
- if (!/[0-2]/.test(value))
- return "Contain at least one number.";
- return true;
- },
- })}
- errorMsg={
- errors.password?.message ? [errors.password.message] : undefined
- }
- label="Enter Password"
- labelPlacement="outside"
- name="password"
- placeholder="Enter your password"
- rounded="md"
- size="lg"
- typeName="password"
- />
- <Link
- className="text-end text-sm font-medium text-blue-600 underline hover:text-blue-500 underline"
- href="/customer/forget-password"
- aria-label="Go to forgot password page"
- >
- Forgot your password ?
- </Link>
- </div>
- <div className="flex flex-col gap-2 lg:gap-y-3">
- <Button
- className="cursor-pointer"
- disabled={isSubmitting}
- loading={isSubmitting}
- title="Sign In"
- type="submit"
- />
- <span className="mx-auto font-outfit sm:mx-0">
- New customer?{" "}
- <Link
- className="font-medium text-blue-600 hover:text-blue-500 underline"
- href="/customer/register"
- aria-label="Go to create account page"
- >
- Create your account
- </Link>
- </span>
- </div>
- </form>
- </div>
- <div className="relative hidden aspect-[0.9] max-h-[692px] w-full max-w-[790px] sm:block md:aspect-[1.14]">
- <Image
- fill
- priority
- alt="Sign In Image"
- className={clsx(
- "relative h-full w-full object-fill",
- "transition duration-300 ease-in-out group-hover:scale-105"
- )}
- sizes={"(min-width: 768px) 66vw, 100vw"}
- src={SIGNIN_IMG}
- />
- </div>
- </div>
- );
- }
|