Sfoglia il codice sorgente

货币切换组件优化;结账页地址板块优化;LyDropDown组件优化

fogwind 2 giorni fa
parent
commit
24432eb22b

File diff suppressed because it is too large
+ 77 - 22
src/app/(checkout)/checkout/_components/CheckoutAddress/BillingAddressCheckout.tsx


File diff suppressed because it is too large
+ 111 - 0
src/app/(checkout)/checkout/_components/CheckoutAddress/MyAddressListTrigger.tsx


File diff suppressed because it is too large
+ 99 - 20
src/app/(checkout)/checkout/_components/CheckoutAddress/ShippingAddressCheckout.tsx


File diff suppressed because it is too large
+ 40 - 66
src/components/common/CurrencySwitch/CurrencySwitch.tsx


+ 0 - 154
src/components/home/HomeImageBanner.tsx

@@ -1,154 +0,0 @@
-"use client";
-
-import { useRouter } from 'next/navigation';
-import { Swiper, SwiperSlide } from 'swiper/react';
-import {CurrencySwitch} from "@/components/common/CurrencySwitch/CurrencySwitch";
-import makeClient from "@/lib/ApolloClientBrowser";
-import { GET_CART_ITEM,GET_FILTER_ATTRIBUTES,CREATE_APPLY_COUPON, GET_COUNTRY_STATES} from "@/graphql";
-import { useMutation, useLazyQuery, useQuery } from "@apollo/client/react";
-import {handleApolloBusinessError} from "@/lib/ApolloErrorHandler";
-import { useCustomToast } from "@/utils/hooks/useToast";
-// interface ImageCarouselProps {
-//     options: {
-//         images: {
-//             image: string;
-//             link: string;
-//             title?: string;
-//         }[];
-//     };
-// }
-export default function HomeImageBanner() {
-  const router = useRouter();
-  const { showToast } = useCustomToast();
-  const apolloClient = makeClient();
-  const res = useQuery(GET_COUNTRY_STATES, {
-          variables: {
-              countryId: 1000
-          }
-      });
-      console.log('query state --- res', res);
-  const ttt = async () => {
-    try {
-      const res = await apolloClient.mutate({
-        mutation: GET_CART_ITEM,
-        fetchPolicy: 'network-only', // 按需调整
-      });
-      console.log('----------',res);
-    } catch(error) {
-      console.log('ttt----------',error);
-      handleApolloBusinessError(error,(err) => {
-          showToast(err.message, "danger");
-      });
-    }  
-  }
-  // 76|nlq9qGztR7qmSS72Kal56NCjhRRVrLCeVOdZdLLYa2c769e9
-const [getContent] = useLazyQuery(GET_FILTER_ATTRIBUTES,{
-        fetchPolicy: 'network-only'
-    });
-
-  const testLazyQuery = async () => { 
-      try {
-            const result = await getContent();
-            console.log('lazy query res---', result);
-      } catch(err) {
-          console.log('lazy query ---- ', err);
-      }
-  }
-
-    // useMutation
-    
-
-
-    const [mutateAsync] = useMutation(
-        GET_CART_ITEM,
-        {
-          onCompleted: (res) => {
-            console.log('useMutation onCompleted res ----- 0',res);
-          },
-    
-          onError: (err) => {
-            console.log('useMutation onCompleted err ----- 0',err);
-          },
-        },
-      );
-
-
-    const testMutation = async () => { 
-        
-            const res = await mutateAsync();
-        console.log('testMutation ---',res)
-        
-    };
-    const testFilter= () => {
-            return apolloClient.query({
-                query: GET_FILTER_ATTRIBUTES,
-                fetchPolicy: "no-cache",
-                context: {
-                    fetchOptions: {
-                        cache: 'no-store',
-                    },
-            
-                },
-                // variables: {
-                //     orderId: Number(orderid)
-                // }
-            }).then((res) => {
-                
-                console.log('testFilter res ---- ',res);
-            
-            
-            }).catch((err) => {
-                console.error('testFilter error ---- ',err);
-                handleApolloBusinessError(err,(error) => {
-          showToast(error.message, "danger");
-      });
-            });
-        };
-    
-        const testApplyCoupon = async () => { 
-            try {
-                await apolloClient.mutate({
-                    mutation: CREATE_APPLY_COUPON,
-                    variables: {
-                        couponCode: 'BIRTHDAY20'
-                    },
-                    fetchPolicy: 'network-only', // 按需调整
-                });
-            } catch (err) {
-                console.log('testApplyCoupon error ---- ',err);
-            }
-        };
-
-
-        const toggleLoginModal = () => {
-          // openLoginModal();
-          router.refresh();
-        };
-  
-  return (
-    <>
-    <p>
-      <button onClick={ttt}>get cart detail</button>
-    </p>
-
-    <p><button onClick={testLazyQuery}>testLazyQuery</button></p>
-    <p><button onClick={testMutation}>testMutation</button></p>
-    <p><button onClick={testFilter}>testFilter</button></p>
-    <p><button onClick={testApplyCoupon}>testApplyCoupon</button></p>
-    <Swiper
-      spaceBetween={50}
-      slidesPerView={3}
-      onSlideChange={() => console.log('slide change')}
-      onSwiper={(swiper) => console.log(swiper)}
-    >
-      <SwiperSlide>Slide 1</SwiperSlide>
-      <SwiperSlide>Slide 2</SwiperSlide>
-      <SwiperSlide>Slide 3</SwiperSlide>
-      <SwiperSlide>Slide 4</SwiperSlide>
-    </Swiper>
-    <CurrencySwitch/>
-
-    <p><button onClick={() => toggleLoginModal()}>show Login Modal</button></p>
-    </>
-  )
-}

+ 0 - 289
src/components/layout/navbar/MobileMenu.tsx

@@ -1,289 +0,0 @@
-"use client";
-
-import { AnimatePresence, motion } from "framer-motion";
-// import Link from "next/link";
-
-// import { MobileSearchBar } from "./MobileSearch";
-import { useBodyScrollLock } from "@utils/hooks/useBodyScrollLock";
-import{useState} from "react";
-import {useLoginModal} from "@/providers/LoginModalProvider";
-import { useCustomToast } from "@/utils/hooks/useToast";
-import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
-import { useRouter } from 'next/navigation';
-import Image from "next/image";
-import type { TreefrontMenu } from "@/types/theme/menus";
-import {CurrencySwitch} from "@/components/common/CurrencySwitch/CurrencySwitch";
-interface MobileMenuProps {
-  menu: TreefrontMenu[];
-  isOpen: boolean;
-  onClose: () => void; // 接收关闭函数
-  isGuest:boolean;
-}
-export default function MobileMenu({ menu, isOpen, onClose,isGuest }: MobileMenuProps) {
-   const {openLoginModal} = useLoginModal();
-   const { showToast } = useCustomToast();
-   const router = useRouter();
-  // 当前选中一级菜单ID
-  const [activeMainId, setActiveMainId] = useState(menu[0]._id);
-  // 存储右侧二级菜单展开状态集合 key:二级id  value:是否展开boolean
-  const [expandedSubIds, setExpandedSubIds] = useState<Record<string, boolean>>({});
-
-  // 获取选中的一级菜单
-  const activeMainMenu = menu.find((item) => item._id === activeMainId);
-  // 当前一级下面全部二级菜单列表
-  const subMenuList = activeMainMenu?.children?.edges?.map((e) => e.node) ?? [];
-
-  // 切换右侧二级菜单展开/收起
-  const toggleSubExpand = (subId: number) => {
-    setExpandedSubIds((prev) => ({
-      ...prev,
-      [subId]: !prev[subId],
-    }));
-  };
-  useBodyScrollLock(isOpen);
-  const guestUserClick = () => {
-          openLoginModal({
-              onFinalResult: async (res) => {
-                  if(res.mergeCartSuccess) { // 购物车合并成功
-                      // if(!res.cartAfterMerge?.isVip && res.cartAfterMerge?.vipPlusAmount === 0) {
-                      //     await switchVipPlus(true);
-                      // }
-                      return
-                  } else { // 购物车合并失败
-                      // 三种 情况: 登录成功; 注册成功;
-                      if(res.loginSuccess) {
-                          // 登录成功 需要刷新页面
-                          showToast('You have logged in successfully. But something wrong','danger');
-                          await confirmDialog({
-                              title: "Warning",
-                              content: "You have logged in successfully. But something wrong. You must refresh the page.",
-                              noCancel: true,
-                          });
-                          window.location.reload();
-                      }
-                      if(res.registerSuccess && !res.loginSuccess) {
-                          // 注册成功,去登录
-                          await confirmDialog({
-                              title: "Warning",
-                              content: "You have registered successfully. But something wrong. You have to login.",
-                              noCancel: true,
-                          });
-                          router.replace('/customer/login');
-                      }
-                  }
-              }
-          });
-      };
-
-  return (
-    <>
-      <AnimatePresence>
-        {/* {isOpen && ( */}
-          <>
-            <motion.div
-              // initial={{ opacity: 0 }}
-              // animate={{ opacity: 1 }}
-              initial={false}
-              animate={{
-                opacity: isOpen ? 1 : 0,
-                pointerEvents: isOpen ? "auto" : "none"
-              }}
-              // exit={{ opacity: 0 }}
-              onClick={onClose}
-              className="fixed inset-0 z-40 bg-transparent lg:hidden"
-              style={{ top: "68px", bottom: "64px" }}
-            />
-
-            <motion.div
-              // initial={{ x: "-100%" }}
-              // animate={{ x: 0 }}
-              // exit={{ x: "-100%" }}
-              initial={false}
-              animate={{
-                x: isOpen ? 0 : "-100%"
-              }}
-              transition={{ type: "spring", damping: 25, stiffness: 200 }}
-              className="fixed left-0 z-50 w-full max-w-[448px] border-r border-neutral-200 bg-white dark:border-neutral-800 dark:bg-black lg:hidden drawer-scrollbar-hidden"
-              style={{
-                top: "0px",
-                bottom: "0px",
-                width: "100%",
-                maxWidth: "448px",
-                height: "calc(var(--visual-viewport-height) - 132px)",
-              }}
-            >
-              {/* <div className="h-full overflow-y-auto px-4 py-4 drawer-scrollbar-hidden">
-                <MobileSearchBar onClose={onClose} />
-
-                <h1 className="mt-4 px-2 text-2xl font-semibold text-black dark:text-white">
-                  Category
-                </h1>
-
-                <ul className="mt-2 flex w-full flex-col drawer-scrollbar-hidden">
-                  {menu.map((item: any) => (
-                    <li
-                      key={item.id + item.name}
-                      className="p-2 text-xl text-black dark:text-white"
-                    >
-                      <Link
-                        href={item.slug ? `/category/${item.slug}/${item._id}` : "/search"}
-                        aria-label={`${item?.name}`}
-                        onClick={onClose}
-                      >
-                        {item.name}{item._id} 4444
-                      </Link>
-                    </li>
-                  ))}
-                </ul>
-              </div> */}
-             <div className="h-screen flex flex-col overflow-hidden bg-white">
-             
-               <div className="flex justify-between items-center px-4 h-13.5 border-b border-[#E1E1E1FF]">
-                 <h2 className="text-base font-medium">VIP Rewards</h2>
-                 <div className="flex gap-3 items-center">
-                  {
-                    isGuest ? (
-                         <button onClick={guestUserClick} className="bg-emerald-700 text-white w-25.75 h-9 flex items-center justify-center  rounded-full text-xs font-normal">
-                            Register/Login
-                          </button>
-                    ) : null
-                  }
-                   <svg
-                     width="24"
-                     height="24"
-                     viewBox="0 0 24 24"
-                     fill="none"
-                     stroke="currentColor"
-                     strokeWidth="2"
-                    onClick={onClose}
-                   >
-                     <path d="M18 6L6 18M6 6l12 12" />
-                   </svg>
-                 </div>
-               </div>
-                       
-               <div className="flex flex-1 overflow-hidden">
-               
-                 <aside className="w-23 border-r border-gray-200 overflow-y-auto">
-                   {menu.map((mainItem) => (
-                     <div
-                       key={mainItem._id}
-                       onClick={() => setActiveMainId(mainItem._id)}
-                       className={`py-3 pl-4 pr-3   text-xs cursor-pointer border-r-2 border-b border-[#E1E1E1FF]
-                         ${
-                           activeMainId === mainItem._id
-                             ? "border-r-emerald-700 text-emerald-700 font-bold"
-                             : " text-gray-700 font-normal"
-                         }
-                       `}
-                     >
-                       {mainItem.name} {mainItem.showIcon&&(<Image
-                                     className=" block"
-                                     src="/image/theme/image_flame.webp"
-                                     alt={"userphoto"}
-                                     width={16}
-                                     height={16}
-                                   />)}
-                     </div>
-                   ))}
-                 </aside>
-                 
-          
-                 <main className="flex-1 overflow-y-auto p-3 pr-4">
-                   {/*顶部横幅占位 */}
-                   {activeMainMenu?.wapImage && 
-                   <div className="w-full  bg-gray-100 mb-4 flex items-end">
-                     {/* <span className="text-3xl text-white pl-6 pb-4">Shop By Feature</span> */}
-                      <Image
-                         src={activeMainMenu?.wapImage||''}
-                         alt={activeMainMenu?.name||''}
-                         className="object-cover"
-                         width={269}
-                         height={37}
-                       />
-                   </div>
-                   }
-                 
-                   {subMenuList.length === 0 ? (
-                     <div className="text-gray-500 text-xl">No sub categories</div>
-                   ) : (
-                     subMenuList.map((subItem) => {
-                       const isOpen = !!expandedSubIds[subItem._id];
-                      
-                       const cardEdges = subItem?.children?.edges ?? [];
-                     
-                       return (
-                         <div key={subItem._id} className="mb-6">
-                          
-                           <div
-                             onClick={() => toggleSubExpand(subItem._id)}
-                             className="flex justify-between items-center cursor-pointer py-2  px-3 h-10 bg-[#F9F9F9FF]"
-                           >
-                           <h3 className="text-2xl font-medium">{subItem.name}</h3>
-                            <svg xmlns="http://www.w3.org/2000/svg"  width="24" height="24" viewBox="0 0 24 24" fill="none" 
-                            className={`transition-transform duration-300 ${isOpen ? "rotate-0" : "rotate-180"}`}
-                            >
-                             <path    stroke="rgba(0, 0, 0, 1)" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="square"  d="M18 15L12 9L6 15"></path>
-                            </svg>
-                           </div>
-                       
-                           {/*二级展开之后渲染三级卡片网格 */}
-                           {isOpen && cardEdges.length > 0 && (
-                             <div className="grid grid-cols-3 gap-2 pt-3">
-                               {cardEdges.map((edge) => {
-                                 const cardItem = edge.node;
-                                 return (
-                                   <div
-                                     key={cardItem._id}
-                                     className="flex flex-col items-center cursor-pointer"
-                                   >
-                                     <div className="w-full relative  bg-gray-200 mb-1 overflow-hidden">
-                                     
-                                         <Image
-                                           src={cardItem.image||''}
-                                           alt={cardItem.name||''}
-                                           width={84}
-                                           height={112}
-                                           className="object-cover"
-                                         />
-                                         {
-                                          cardItem.showIcon&&(
-                                             <Image
-                                                className=" block absolute top-0 left-0"
-                                                src="/image/theme/image_hot.webp"
-                                                alt={"userphoto"}
-                                                width={30}
-                                                height={14}
-                                              />
-                                          )
-                                         }
-                                     </div>
-                                     <span className="text-xs font-normal text-center">{cardItem.name}</span>
-                                   </div>
-                                 );
-                               })}
-                             </div>
-                           )}
-                           {isOpen && cardEdges.length === 0 && (
-                             <div className="py-3 text-gray-500">No feature items</div>
-                           )}
-                         </div>
-                       );
-                     })
-                   )}
-                 </main>
-               </div>
-                 
-               {/* =========底部固定货币选择栏 ========= */}
-               <div className="h-12 border-t border-gray-200 flex items-center px-4 gap-4">
-                  <CurrencySwitch/>
-               </div>
-             </div>
-
-            </motion.div>
-          </>
-        {/* )} */}
-      </AnimatePresence>
-    </>
-  );
-}

+ 0 - 28
src/components/layout/navbar/MobileMenuTrigger.tsx

@@ -1,28 +0,0 @@
-'use client';
-
-import { useState } from "react";
-import MobileMenu from "./MobileMenu";
-import type { TreefrontMenu } from "@/types/theme/menus";
-
-export default function MobileMenuTrigger({ menuData,isGuest }: { menuData: TreefrontMenu[],isGuest:boolean }) {
-  const [isOpen, setIsOpen] = useState(false);
-  const toggleMenu = () => setIsOpen((prev) => !prev);
-  const closeMenu = () => setIsOpen(false);
-
-  return (
-    <>
-      {/* 汉堡按钮 */}
-      <button className="flex-none w-6 h-6" onClick={toggleMenu}>
-        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
-          <rect x="0" y="0" width="24" height="24" fill="#FFFFFF" fillOpacity="0" />
-          <path stroke="currentColor" strokeWidth="1.5" d="M4 5L20 5" />
-          <path stroke="currentColor" strokeWidth="1.5" d="M4 12L20 12" />
-          <path stroke="currentColor" strokeWidth="1.5" d="M4 19L20 19" />
-        </svg>
-      </button>
-
-      {/* 抽屉菜单 */}
-      <MobileMenu menu={menuData} isGuest={isGuest} isOpen={isOpen} onClose={closeMenu} />
-    </>
-  );
-}

+ 3 - 10
src/components/layout/navbar/index.tsx

@@ -2,7 +2,6 @@ import Link from "next/link";
 import { Suspense } from "react";
 import LogoIcon from "@components/common/icons/LogoIcon";
 import { NavbarActions } from "./NavbarActions";
-// import { NavigationSkeleton } from "./NavigationSkeleton";
 import { NavbarErrorBoundary } from "@/components/error/ErrorBoundary";
 import  DesktopNavigation from "./DesktopNavigation";
 import { 
@@ -10,8 +9,6 @@ import {
 import { serverGraphqlFetch } from "@utils/bagisto/index";
 import NavbarSearchIcon from "./NavbarSearchIcon"
 import type { GetTreefrontMenusResult } from "@/types/theme/menus";
-// import { authOptions } from "@utils/auth";
-// import { getServerSession } from "next-auth";
 import { auth } from "@/utils/auth/auth-helper";
 import TopSwiperBanner from "./TopSwiperBanner"
 import type { AnnouncementItem } from "./TopSwiperBanner";
@@ -47,7 +44,7 @@ export default async function Navbar() {
       query: GET_TREEFRONT_MENUS,
       takeAuthorization: false,
     });
-    console.log('GetTreefrontMenusResult --- ',menudata)
+    // console.log('GetTreefrontMenusResult --- ',menudata)
     const menudatas =menudata? menudata.treefrontMenus:[];
 
   return (
@@ -61,13 +58,9 @@ export default async function Navbar() {
             {/* 1. THE STATIC SHELL (Visible Instantly) */}
             <div className="flex items-center max-w-fit gap-5 xl:gap-6">
               {/* 2. STATIC HOLE: Categories (Suspended) */}
-              {/* <Suspense fallback={<NavigationSkeleton />}>
-                <MobileMenuTrigger isGuest={isGuest} menuData={menudatas} />
-              </Suspense> */}
+  
               <div className="flex-1 justify-center flex">
-                {/* <Suspense fallback={<SearchSkeleton />}>
-                  <Search search={false} />
-                </Suspense> */}
+
               <NavbarSearchIcon/>
               </div>
             </div>

+ 43 - 20
src/components/theme/ui/LyDropDown.tsx

@@ -6,38 +6,61 @@ import clsx from "clsx";
 
 export function LyDropDown({
     showDropDown,
+    trigger,
     children,
-    onClose
+    onClose,
+    className,
+    listClassName
 }: {
     showDropDown: boolean;
+    trigger: ReactNode;
     children: ReactNode;
     onClose: () => void;
+    className?: string;
+    listClassName?: string;
 }) {
     const listRef = useRef<HTMLDivElement>(null);
     const rootRef = useRef<HTMLDivElement>(null);
-    const [listPosition, setListPosition] = useState({
+    const triggerRef = useRef<HTMLDivElement>(null);
+    const [listPosition, setListPosition] = useState<React.CSSProperties>({
+        position: 'absolute',
         top: '0',
+        left: '0',
         transform: 'translate3d(0, 0%, 0)', 
     });
 
     useLayoutEffect(() => {
         if (listRef.current && rootRef.current && showDropDown) {
-            const { height } = listRef.current.getBoundingClientRect();
+            const { height, width } = listRef.current.getBoundingClientRect();
             // setListHeight(height);
             console.log('Measured tooltip height: ' + height);
 
-            const triggerRect = rootRef.current.getBoundingClientRect();
-            if(triggerRect.bottom + height > window.innerHeight) { // 显示在上方
-                setListPosition({
-                    top: '0',
-                    transform: 'translate3d(0, -100%, 0)',
-                });
+            // 标准模式下视口宽度和高度
+            const viewPortWidth = document.documentElement.clientWidth;
+            const viewPortHeight = document.documentElement.clientHeight;
+            const rootRect = rootRef.current.getBoundingClientRect();
+            let top = '0';
+            let left = '0';
+            let transform = 'translate3d(0, 0%, 0)';
+            if(rootRect.bottom + height > viewPortHeight) { // 显示在上方
+                top = '0';
+                transform = 'translate3d(0, -100%, 0)';
             } else { // 显示在下方
-                setListPosition({
-                    top: `${triggerRect.height}px`,
-                    transform: `translate3d(0, 0%, 0)`
-                });
+                top = `${rootRect.height}px`;
+                transform = 'translate3d(0, 0%, 0)';
+            }
+            if(rootRect.left + width > viewPortWidth) {
+                left = `-${rootRect.left + width - viewPortWidth}px`;
+            } else {
+                left = '0';
             }
+            setListPosition((pre) => {
+                return Object.assign({...pre},{
+                    top,
+                    transform,
+                    left
+                });
+            });
             
         }
     }, [showDropDown]);
@@ -53,10 +76,9 @@ export function LyDropDown({
 
             const target = event.target as Node;
 
-            if (
-                rootRef.current &&
-                !rootRef.current.contains(target)
-            ) {
+            if (triggerRef?.current?.contains(target)) return;
+
+            if (rootRef.current && !rootRef.current.contains(target)) {
                 onClose();
             }
         }
@@ -84,10 +106,11 @@ export function LyDropDown({
 
     return (
         <>
-            <div className="w-full relative" ref={rootRef}>
+            <div className={clsx("relative", className ? className : "w-full")} ref={rootRef}>
 
-                <div ref={listRef} className={clsx(
-                    "absolute w-full max-h-48 overflow-auto bg-white ly-thin-shadow border border-ly-gray rounded-md z-1",
+                <div className="w-full" ref={triggerRef}>{trigger}</div>
+                <div ref={listRef} className={clsx("absolute max-h-48 overflow-y-auto bg-white ly-thin-shadow border border-ly-gray rounded-md z-1",
+                    listClassName ? listClassName : "w-full",
                     showDropDown  ? "visible pointer-events-auto" : "invisible pointer-events-none"
                 )}
                     onClick={(e) => { e.stopPropagation(); }}