| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- "use client";
- import { useDisclosure } from "@heroui/use-disclosure";
- import { AnimatePresence, motion } from "framer-motion";
- import clsx from "clsx";
- import { signOut } from "next-auth/react";
- import Link from "next/link";
- import { Avatar } from "@heroui/avatar";
- import { useForm } from "react-hook-form";
- import { usePathname, useRouter } from "next/navigation";
- import { useCustomToast } from '@/utils/hooks/useToast';
- import { useBodyScrollLock } from "@utils/hooks/useBodyScrollLock";
- import OpenAuth from "../OpenAuth";
- import { isObject } from '@/utils/type-guards';
- import { useGuestCartToken } from "@utils/hooks/useGuestCartToken";
- import LoadingDots from "@components/common/icons/LoadingDots";
- import { logoutAction } from "@utils/actions";
- import { useAppDispatch, useAppSelector } from "@/store/hooks";
- import { clearUser } from "@/store/slices/user-slice";
- import { clearCart } from "@/store/slices/cart-slice";
- import { EMAIL, removeFromLocalStorage } from "@/store/local-storage";
- export default function CredentialModal({
- children,
- className,
- onOpen,
- onClose,
- isOpen,
- }: {
- children?: React.ReactNode;
- className?: string;
- onOpen?: () => void;
- onClose?: () => void;
- isOpen?: boolean;
- }) {
- const {
- isOpen: internalIsOpen,
- onOpen: internalOnOpen,
- onClose: internalOnClose,
- onOpenChange: _internalOnOpenChange,
- } = useDisclosure();
- const isControlled = isOpen !== undefined;
- const finalIsOpen = isControlled ? isOpen : internalIsOpen;
- const finalOnOpen = isControlled ? onOpen : internalOnOpen;
- const finalOnClose = isControlled ? onClose : internalOnClose;
- const pathname = usePathname();
- const router = useRouter();
- const dispatch = useAppDispatch();
- const { showToast } = useCustomToast();
- const { resetGuestToken } = useGuestCartToken();
- useBodyScrollLock(finalIsOpen );
- const {
- handleSubmit,
- formState: { isSubmitting },
- } = useForm();
- const { user } = useAppSelector((state) => state.user);
- const session = { user };
- const onSubmit = async () => {
- try {
- const res = await logoutAction();
- if (!res.success) {
- showToast(res.message, "danger");
- }
- await signOut({
- callbackUrl: "/customer/login",
- redirect: false,
- });
- await resetGuestToken();
- dispatch(clearUser());
- dispatch(clearCart());
- showToast("You are logged out successfully!", "success");
- setTimeout(() => {
- router.push("/customer/login");
- router.refresh();
- }, 100);
- removeFromLocalStorage(EMAIL)
- } catch (err: unknown) {
- const message = err instanceof Error ? err.message : "Logout failed";
- showToast(message, "danger");
- }
- };
- const innerContent = (_onClose?: () => void) => (
- <div className={clsx("flex w-full flex-col rounded-md py-4", {
- "gap-y-6": !!session?.user,
- "gap-y-10": !session?.user,
- })}>
- {isObject(session?.user) ? (
- <>
- <header>
- <div className={clsx("flex flex-col gap-3", "items-center justify-center")}>
- <div className={clsx("flex gap-3", "flex-col items-center")}>
- <Avatar
- isBordered
- showFallback
- color="default"
- icon={<OpenAuth className={clsx("h-12 w-12")} />}
- size={"lg"}
- className={clsx( "h-24 w-24 text-large")}
- />
- <div className={clsx("flex flex-col justify-center", "items-center gap-1")}>
- <h4 className={clsx("leading-none dark:text-white", "text-xl font-bold text-black")}>
- {session?.user?.name}
- </h4>
- <h5 className={clsx("tracking-tight dark:text-white", "text-sm text-gray-500")}>
- {session?.user?.email}
- </h5>
- </div>
- </div>
- <p className={clsx("text-default-500 dark:text-white", "text-center mt-2")}>
- Manage Cart, Orders
- <span aria-label="confetti" className="px-2" role="img">
- 🎉
- </span>
- </p>
- </div>
- </header>
- <footer>
- <form onSubmit={handleSubmit(onSubmit)} className={clsx("flex justify-center")}>
- <button
- className={clsx(
- "rounded-full bg-gray-800 px-5 py-2.5 text-sm font-medium text-white hover:bg-gray-900 focus:outline-none focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700",
- isSubmitting ? " cursor-not-allowed" : " cursor-pointer",
- "w-40 min-w-[150px] mt-2"
- )}
- type="submit"
- >
- <div className="mx-1">
- {isSubmitting ? (
- <div className="flex items-center justify-center">
- <p>Loading</p>
- <LoadingDots className="bg-white" />
- </div>
- ) : (
- <p> Log Out</p>
- )}
- </div>
- </button>
- </form>
- </footer>
- </>
- ) : (
- <>
- <header className="text-center">
- <div className="flex flex-col gap-y-2">
- <h4 className="font-bold leading-none text-black dark:text-white text-3xl">
- Welcome Guest
- </h4>
- <p className="text-default-500 dark:text-neutral-400 text-lg">
- Manage Cart, Orders
- <span aria-label="confetti" className="px-2" role="img">
- 🎉
- </span>
- </p>
- </div>
- </header>
- <footer className="flex gap-4">
- <Link className="w-full" href="/customer/login" onClick={finalOnClose} aria-label="Go to sign in page">
- <button
- className={clsx(
- "w-full rounded-full bg-blue-600 px-5 py-3 text-center text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-4 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800",
- pathname === "/customer/login"
- ? " cursor-not-allowed"
- : " cursor-pointer"
- )}
- disabled={pathname === "/customer/login"}
- type="button"
- >
- Sign In
- </button>
- </Link>
- <Link className="w-full" href="/customer/register" onClick={finalOnClose} aria-label="Go to create account page">
- <button
- className={clsx(
- "w-full rounded-full bg-[#1e293b] px-5 py-3 text-sm font-medium text-white hover:bg-gray-900 focus:outline-none focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700",
- pathname === "/customer/register"
- ? " cursor-not-allowed"
- : " cursor-pointer"
- )}
- disabled={pathname === "/customer/register"}
- type="button"
- >
- Sign Up
- </button>
- </Link>
- </footer>
- </>
- )}
- </div>
- );
- return (
- <>
- <button
- type="button"
- aria-label="Open account"
- className={clsx(className, "cursor-pointer bg-transparent")}
- onClick={finalOnOpen}
- >
- {children ? children : <OpenAuth />}
- </button>
- <AnimatePresence>
- {finalIsOpen && (
- <>
- <motion.div
- initial={{ opacity: 0 }}
- animate={{ opacity: 1 }}
- exit={{ opacity: 0 }}
- onClick={finalOnClose}
- className="fixed inset-0 z-40 bg-transparent"
- style={{ top: "68px", bottom: "64px" }}
- />
- <motion.div
- initial={{ x: "100%" }}
- animate={{ x: 0 }}
- exit={{ x: "100%" }}
- transition={{ type: "spring", damping: 30, stiffness: 300, mass: 0.8 }}
- className="fixed right-0 z-50 flex flex-col border-l border-neutral-200 bg-white dark:border-neutral-800 dark:bg-black lg:hidden"
- style={{
- top: "68px",
- bottom: "64px",
- width: "100%",
- maxWidth: "448px",
- height: "calc(var(--visual-viewport-height) - 132px)",
- }}
- >
- <div className="flex flex-col gap-1 border-b border-neutral-100 p-4 dark:border-neutral-800">
- <div className="flex items-center justify-between">
- <p className="text-xl font-semibold dark:text-white">Account</p>
- <button
- aria-label="Close account"
- className="rounded-full p-1 hover:bg-neutral-100 "
- onClick={finalOnClose}
- type="button"
- >
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon" className="h-6 transition-all ease-in-out hover:scale-110"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12"></path></svg>
- </button>
- </div>
- </div>
- <div className="flex flex-1 flex-col justify-center px-4 py-0 drawer-scrollbar-hidden">
- {innerContent(finalOnClose)}
- </div>
- <div className="p-4" />
- </motion.div>
- </>
- )}
- </AnimatePresence>
- </>
- );
- }
|