zgl преди 3 дни
родител
ревизия
945bd5055e

+ 102 - 96
src/app/(checkout)/checkout/_components/CheckoutAddress/CheckoutAssress.tsx

@@ -4,11 +4,12 @@ import {type Ref, useState, useEffect, useCallback, useImperativeHandle, useMemo
 import { useRouter } from 'next/navigation';
 import { useForm, FormProvider } from "react-hook-form";
 import { useCustomToast } from "@/utils/hooks/useToast";
-import {useCheckoutAddress} from "@/utils/hooks/useCheckoutAddress"
+import {useCheckoutAddress, addressIsSame} from "@/utils/hooks/useCheckoutAddress"
 import {useLoginModal} from "@/providers/LoginModalProvider";
 import { useGetCustomerAddress } from "@utils/hooks/useGetCustomerAddress";
 import { EMAIL_REGEX } from "@utils/constants";
 import type { 
+    BillAddressFormData,
     ShipAddressFormData,
     FullAddressFormData,
     CreateCheckoutAddressVariables,
@@ -41,50 +42,44 @@ export interface RefCheckoutAddressHandle {
 /**
  * 地址表单默认值
  */
-function getBillingAddressFormData(billingAddress:CartAddress) {
-    const billingCountry = billingAddress.country || 'US';
-    const billingPhoneNumber =  normalizePhoneForForm(billingAddress.phone, billingCountry) || "";
-    const res = {
-        "billingAddressId": billingAddress.id,
-        "billingEmail": billingAddress.email,
-        "billingFirstName": billingAddress.firstName,
-        "billingLastName": billingAddress.lastName,
+function getBillingAddressFormData(billingAddress:CartAddress | null) {
+    let res:Omit<BillAddressFormData,'billingSameAsShipping'> = {
+        "billingAddressId": "",
+        "billingEmail": '',
+        "billingFirstName": "",
+        "billingLastName": "",
         "billingCompanyName": "",
-        "billingAddress": billingAddress.address,
-        "billingCountry": billingCountry,
-        "billingState": billingAddress.state,
-        "billingCity": billingAddress.city,
-        "billingPostcode": billingAddress.postcode,
-        "billingPhoneNumber": billingPhoneNumber,
-    };
-
-    return res;
-}
-function getShippingAddressFormData(shippingAddress:CartAddress) {
-    const shippingCountry = shippingAddress.country || 'US';
-    const shippingPhoneNumber =  normalizePhoneForForm(shippingAddress.phone, shippingCountry) || "";
-    const res = {
-        "shippingAddressId": shippingAddress.id,
-        "shippingEmail": shippingAddress.email,
-        "shippingFirstName": shippingAddress.firstName,
-        "shippingLastName": shippingAddress.lastName,
-        "shippingCompanyName": "",
-        "shippingAddress": shippingAddress.address,
-        "shippingCountry": shippingCountry,
-        "shippingState": shippingAddress.state,
-        "shippingCity": shippingAddress.city,
-        "shippingPostcode": shippingAddress.postcode,
-        "shippingPhoneNumber": shippingPhoneNumber,
+        "billingAddress": "",
+        "billingCountry": "US",
+        "billingState": "",
+        "billingCity": "",
+        "billingPostcode": "",
+        "billingPhoneNumber": "",
     };
+    if(billingAddress) {
+        const billingCountry = billingAddress.country || 'US';
+        const billingPhoneNumber =  normalizePhoneForForm(billingAddress.phone, billingCountry) || "";
+        res = {
+            "billingAddressId": billingAddress.id,
+            "billingEmail": billingAddress.email,
+            "billingFirstName": billingAddress.firstName,
+            "billingLastName": billingAddress.lastName,
+            "billingCompanyName": "",
+            "billingAddress": billingAddress.address,
+            "billingCountry": billingCountry,
+            "billingState": billingAddress.state,
+            "billingCity": billingAddress.city,
+            "billingPostcode": billingAddress.postcode,
+            "billingPhoneNumber": billingPhoneNumber,
+        };
+    }
 
     return res;
 }
-function getAddressFormDataFromCart(cartDetail:CartDetail, loginEmail?: string) {
-    const shippingAddress = cartDetail.shippingAddress;
-    const billingAddress = cartDetail.billingAddress;
-    const defaultValues = {
+function getShippingAddressFormData(shippingAddress:CartAddress | null) {
+    let res: ShipAddressFormData = {
         "shippingAddressId": "",
-        "shippingEmail": loginEmail ?? '',
+        "shippingEmail": "",
         "shippingFirstName": "",
         "shippingLastName": "",
         "shippingCompanyName": "",
@@ -93,62 +88,46 @@ function getAddressFormDataFromCart(cartDetail:CartDetail, loginEmail?: string)
         "shippingState": "",
         "shippingCity": "",
         "shippingPostcode": "",
-        "shippingPhoneNumber": "",
-        "billingAddressId": "",
-        "billingEmail": loginEmail ?? '',
-        "billingFirstName": "",
-        "billingLastName": "",
-        "billingCompanyName": "",
-        "billingAddress": "",
-        "billingCountry": "US",
-        "billingState": "",
-        "billingCity": "",
-        "billingPostcode": "",
-        "billingPhoneNumber": "",
-        "billingSameAsShipping": true
+        "shippingPhoneNumber": ""
+    };
+    if(shippingAddress) {
+        const shippingCountry = shippingAddress.country || 'US';
+        const shippingPhoneNumber =  normalizePhoneForForm(shippingAddress.phone, shippingCountry) || "";
+        res = {
+            "shippingAddressId": shippingAddress.id,
+            "shippingEmail": shippingAddress.email,
+            "shippingFirstName": shippingAddress.firstName,
+            "shippingLastName": shippingAddress.lastName,
+            "shippingCompanyName": "",
+            "shippingAddress": shippingAddress.address,
+            "shippingCountry": shippingCountry,
+            "shippingState": shippingAddress.state,
+            "shippingCity": shippingAddress.city,
+            "shippingPostcode": shippingAddress.postcode,
+            "shippingPhoneNumber": shippingPhoneNumber,
+        };
     }
-    if(billingAddress) {
-        const bs = getBillingAddressFormData(billingAddress);
-        defaultValues.billingAddressId = bs.billingAddressId;
-        defaultValues.billingEmail = bs.billingEmail;
-        defaultValues.billingFirstName = bs.billingFirstName;
-        defaultValues.billingLastName = bs.billingLastName;
-        defaultValues.billingCompanyName = bs.billingCompanyName;
-        defaultValues.billingAddress = bs.billingAddress;
-        defaultValues.billingCountry = bs.billingCountry;
-        defaultValues.billingState = bs.billingState;
-        defaultValues.billingCity = bs.billingCity;
-        defaultValues.billingPostcode = bs.billingPostcode;
-        defaultValues.billingPhoneNumber = bs.billingPhoneNumber;
-        // defaultValues.billingSameAsShipping = true;
+
+    return res;
+}
+function getAddressFormDataFromCart(cartDetail:CartDetail, loginEmail?: string) {
+    const shippingAddress = cartDetail.shippingAddress;
+    const billingAddress = cartDetail.billingAddress;
+
+    const bs = getBillingAddressFormData(billingAddress);
+    const ss = getShippingAddressFormData(shippingAddress);
+    if(loginEmail && !bs.billingEmail) {
+        bs.billingEmail = loginEmail;
     }
-    if(shippingAddress) {
-        const ss = getShippingAddressFormData(shippingAddress);
-        defaultValues.shippingAddressId = ss.shippingAddressId;
-        defaultValues.shippingEmail = ss.shippingEmail;
-        defaultValues.shippingFirstName = ss.shippingFirstName;
-        defaultValues.shippingLastName = ss.shippingLastName;
-        defaultValues.shippingCompanyName = ss.shippingCompanyName;
-        defaultValues.shippingAddress = ss.shippingAddress;
-        defaultValues.shippingCountry = ss.shippingCountry;
-        defaultValues.shippingState = ss.shippingState;
-        defaultValues.shippingCity = ss.shippingCity;
-        defaultValues.shippingPostcode = ss.shippingPostcode;
-        defaultValues.shippingPhoneNumber = ss.shippingPhoneNumber;
+    if(loginEmail && !ss.shippingEmail) {
+        ss.shippingEmail = loginEmail;
     }
-    if( defaultValues.billingEmail === defaultValues.shippingEmail &&
-        defaultValues.billingFirstName === defaultValues.shippingFirstName &&
-        defaultValues.billingLastName === defaultValues.shippingLastName &&
-        defaultValues.billingCompanyName === defaultValues.shippingCompanyName &&
-        defaultValues.billingAddress === defaultValues.shippingAddress &&
-        defaultValues.billingCountry === defaultValues.shippingCountry &&
-        defaultValues.billingState === defaultValues.shippingState &&
-        defaultValues.billingCity === defaultValues.shippingCity &&
-        defaultValues.billingPostcode === defaultValues.shippingPostcode &&
-        defaultValues.billingPhoneNumber === defaultValues.shippingPhoneNumber
-    ) {
-        defaultValues.billingSameAsShipping = true;
+    const defaultValues: FullAddressFormData = {
+        ...ss,
+        ...bs,
+        "billingSameAsShipping": addressIsSame(bs,ss)
     }
+    
     return defaultValues;
 }
 /**
@@ -305,11 +284,38 @@ export function CheckoutAddress({
                     return;
                 }
 
-                addressForm.resetField('shippingAddressId',{
-                    defaultValue: createAddressData.shippingAddressId
-                });
-                addressForm.resetField('billingAddressId',{
-                    defaultValue: createAddressData.billingAddressId
+                const shipAddressAfterSave: ShipAddressFormData = {
+                    shippingAddressId: createAddressData.shippingAddressId,
+                    shippingEmail: createAddressData.shippingEmail,
+                    shippingFirstName: createAddressData.shippingFirstName,
+                    shippingLastName: createAddressData.shippingLastName,
+                    shippingCompanyName: createAddressData.shippingCompanyName || '',
+                    shippingAddress: createAddressData.shippingAddress,
+                    shippingCountry: createAddressData.shippingCountry,
+                    shippingState: createAddressData.shippingState,
+                    shippingCity: createAddressData.shippingCity,
+                    shippingPostcode: createAddressData.shippingPostcode,
+                    shippingPhoneNumber: createAddressData.shippingPhoneNumber,
+                };
+                const billAddressAfterSave: Omit<BillAddressFormData,'billingSameAsShipping'> = {
+                    billingAddressId: createAddressData.billingAddressId,
+                    billingFirstName: createAddressData.billingFirstName,
+                    billingLastName: createAddressData.billingLastName,
+                    billingEmail: createAddressData.billingEmail,
+                    billingCompanyName: createAddressData.billingCompanyName || '',
+                    billingAddress: createAddressData.billingAddress,
+                    billingCity: createAddressData.billingCity,
+                    billingState: createAddressData.billingState,
+                    billingCountry: createAddressData.billingCountry,
+                    billingPostcode: createAddressData.billingPostcode,
+                    billingPhoneNumber: createAddressData.billingPhoneNumber
+                };
+                // 重置表单,更新默认值
+                addressForm.reset({
+                    ...shipAddressAfterSave,
+                    ...billAddressAfterSave,
+                    billingSameAsShipping: addressIsSame(billAddressAfterSave,shipAddressAfterSave)
+
                 });
 
         

+ 5 - 2
src/app/(public)/category/[slug]/[id]/_components/ProductListing.tsx

@@ -13,6 +13,7 @@ import { useRouter, usePathname } from "next/navigation";
 import {  useApolloClient } from "@apollo/client/react";
 import type{ CategoryProductsResult,PageInfo,CategoryProductNode,CategoryAttrFilterItem } from "@components/catalog/type";
 import {CATEGORY_PRODUCTS} from "@/graphql";
+import {transformFiltersForApi} from "@utils/helper";
 interface QueryState {
   // search: string;
 
@@ -65,6 +66,8 @@ export default function ProductListing({
 
   const fetchProducts = async (currentQuery: QueryState) => {
     try {
+      // 转换
+    const finalFilterObj = transformFiltersForApi(currentQuery.filters);
       const res = await apolloClient.query<CategoryProductsResult>({
         query: CATEGORY_PRODUCTS,
         variables: 
@@ -80,14 +83,14 @@ export default function ProductListing({
         
           sortKey: currentQuery.sortKey,
           reverse: currentQuery.reverse,
-          filter:JSON.stringify( currentQuery.filters),
+          filter:JSON.stringify(finalFilterObj),
 
           first: 15,
 
           after: null,
         },
       });
-      console.log("res----------------------------------:",res,currentQuery);
+      console.log("res----------------------------------:",res,currentQuery,finalFilterObj);
       
       // const res = await fetch("/api/products", {
       //   method: "POST",

+ 14 - 4
src/app/(public)/category/[slug]/[id]/page.tsx

@@ -3,6 +3,7 @@ import { notFound } from "next/navigation";
 // import { isArray } from "@/utils/type-guards";
 // import FilterList from "@components/theme/filters/FilterList";
 // import Pagination from "@components/catalog/Pagination";
+import Image from "next/image";
 import ProductListing from "./_components/ProductListing";
 import type{
   // ProductsResponse,
@@ -33,6 +34,7 @@ import {
   // extractNumericId,
   // findCategoryBySlug,
   newBuildProductFilters,
+  transformFiltersForApi,
 } from "@utils/helper";
 import { serverGraphqlFetch } from "@/utils/bagisto/index";
 import type {
@@ -175,8 +177,8 @@ export default async function CategoryPage({
   if (numericId) {
     filterObject.category_id = numericId;
   }
-
-  const filterInput = JSON.stringify(filterObject);
+  const finalFilterObj =transformFiltersForApi(filterObject);
+  const filterInput = JSON.stringify(finalFilterObj);
   // 默认获取产品数据接口:
   const { data: categoryProductDatas } = await serverGraphqlFetch<
     CategoryProductsResult,
@@ -306,10 +308,18 @@ export default async function CategoryPage({
               name: translation?.name ?? "",
             }}
           /> */}
-          <CategoryDesc
+           <div className="flex justify-center">
+              <Image
+               src={categoryProductDatas? categoryProductDatas.categoryProducts.banner: ''}
+               alt={'categoryBanner'}
+               width={358}
+               height={176}
+              />
+          </div>
+           <CategoryDesc
             title={translation?.name ?? ""}
             description={
-              "Alipearl Hair provides many style best quality but cheap lace wigs,including lace front wigs, lace closure wigs, full lace human hair wigs, HD transparent lace wigs, 13x4 frontal wigs, 13x6 lace front wigs, 360 lace wigs, all textures available straight, body wave, deep wave, water wave, curly hair wigs, all lengths from 8 inch to 40 inch, different hair colors natural black, brown, blonde, highlight, ginger, burgundy etc."
+              categoryProductDatas? categoryProductDatas.categoryProducts.description: ''
             } //translation?.description ?? ""}
           />
         </Suspense>

+ 5 - 8
src/app/(public)/customer/account/_components/AccountVipPopup.tsx

@@ -3,7 +3,7 @@
 // 轮播逻辑
 import { Swiper, SwiperSlide } from "swiper/react";
 import { Navigation, Scrollbar } from "swiper/modules";
-import { Swiper as SwiperType } from "swiper/types";
+import type { Swiper as SwiperType } from "swiper/types";
 import { useEffect, useState } from "react";
 import Image from "next/image";
 import "swiper/css";
@@ -121,17 +121,16 @@ export default function AccountVipPopup({
   visible: boolean;
   onClose: () => void;
 }) {
-  // swiper实例
-  const [_swiperRef, setSwiperRef] = useState<SwiperType | null>(null);
+
   // 当前slide下标
   const [activeIndex, setActiveIndex] = useState(0);
-  const [_loading, setLoading] = useState(true);
+
   const [VipGrothData, setVipGrothData] = useState<any>(null);
   // 如果不显示,直接 return null
   useEffect(() => {
     const fetchGrothValue = async () => {
       try {
-        setLoading(true);
+
         const res = await clientFetch("/api/customer/growth-value");
         if (res.success) {
           setVipGrothData(res.data);
@@ -139,8 +138,6 @@ export default function AccountVipPopup({
         }
       } catch (error) {
         console.error("获取vip成长值失败:", error);
-      } finally {
-        setLoading(false);
       }
     };
     fetchGrothValue();
@@ -261,7 +258,7 @@ export default function AccountVipPopup({
                         nextEl: ".btn-next", // 右箭头class
                         disabledClass: "opacity-20", // 滑到首尾自动加这个类,Tailwind直接用
                       }}
-                      onSwiper={(swiper) => setSwiperRef(swiper)}
+
                       onSlideChange={handleSlideChange}
                       spaceBetween={0}
                       className="swiper-container-horizontal"

+ 2 - 2
src/app/(public)/paymentresult/_components/CancelOrFailure.tsx

@@ -68,7 +68,7 @@ export default function CancelOrFailure({
                         {isGuest ?
                             "payment cancelled! Your order has been cancelled."
                         :
-                            "Payment cancelled! You can continue to pay."
+                            orderStatus !=='canceled' ? "Payment cancelled! You can continue to pay." : "Order has been cancelled!"
                         }
                         
                     </p>
@@ -84,7 +84,7 @@ export default function CancelOrFailure({
                 We’ll get started on your order very soon!<br/>
                 You’ll receive an order confirmation email with details of your order in an hour, please be sure to check your email and confirm your order, that is very important for us to process your order.
             </p> */}
-            {isGuest ?
+            {isGuest || orderStatus ==='canceled' ?
                 <Link href="/" className="mt-6 flex justify-center items-center h-12 border-1 rounded-3xl text-ly-14">
                     Continue Shopping
                 </Link>

Файловите разлики са ограничени, защото са твърде много
+ 6 - 2
src/app/(public)/paymentresult/_components/Faqs.tsx


+ 2 - 21
src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx

@@ -5,35 +5,16 @@ import { redirect, RedirectType } from 'next/navigation';
 import Link from "next/link";
 import Image from "next/image";
 import type {FetchGraphqlResult} from "@/types/graphqlFetch/type";
-import type { OrderDetailsData,ProductItemAdditional } from "@/types/customer/type";
+import type { OrderDetailsData } from "@/types/customer/type";
 import {deleteGuestCookieAction} from "@/actions";
 import { useConfig } from "@utils/hooks/useConfig";
-import {getAddressFromOrderDetailAddressList} from "@/utils/orderDetailTools";
+import {getAddressFromOrderDetailAddressList,getProductAdditionalInfo} from "@/utils/orderDetailTools";
 import Faqs from "./Faqs";
 
 /**
  * 订单详情里的金额的货币符号不会根据用户选择的货币改变 ,而是固定为下单时使用的货币
  */
 
-function getProductAdditionalInfo(productItem: ProductItemAdditional) { 
-    const attributeKeys = Object.keys(productItem.attributes);
-    let res = '';
-    attributeKeys.forEach(key => {
-      const attribute = productItem.attributes[key];
-      if (attribute) {
-        // option_label value_label
-
-        if(res) {
-            res = res + ', ' + attribute.option_label + ': ' + attribute.value_label;
-        } else {
-            res = attribute.option_label + ': ' + attribute.value_label;
-        }
-      }
-    });
-    return res;
-}
-
-
 export default function OrderDetailWrapper({
     orderDetailPromise,
     isGuest

+ 3 - 0
src/components/catalog/type.ts

@@ -176,6 +176,7 @@ export interface CategoryAttrFilterItem  {
         adminName: string;
         sortOrder: number;
         swatchValue: string | null;
+        productCount:number;
       };
     }>;
   };
@@ -210,6 +211,8 @@ export type CategoryProductNode = {
 export type CategoryProductsResult = {
   categoryProducts: {
     totalCount: number;
+    description: string;
+    banner: string;
     pageInfo:{
       endCursor:string,
       hasNextPage: boolean

+ 0 - 14
src/components/customer/OpenAuth.tsx

@@ -1,14 +0,0 @@
-import { UserIcon } from "@heroicons/react/24/outline";
-import clsx from "clsx";
-
-export default function OpenAuth({ className }: { className?: string }) {
-  return (
-    <>
-      <div className="relative flex items-center justify-center rounded-md border-0 lg:border border-solid border-neutral-200 dark:border-neutral-700 lg:h-11 lg:w-11">
-        <UserIcon className={clsx("h-5 w-5  ", className)} />
-      </div>
-    </>
-
-  );
-}
-

+ 39 - 1
src/components/customer/SettingModal.tsx

@@ -1,6 +1,13 @@
 "use client";
 import Link from "next/link";
 import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { signOut } from "next-auth/react";
+import { logoutAction } from "@/actions";
+import { useCustomToast } from '@/utils/hooks/useToast';
+import { useAppDispatch } from "@/store/hooks";
+import { clearCart } from "@/store/slices/cart-slice";
+import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import HelpModal from "../../app/(public)/customer/account/_components/HelpModal";
 export default function SettingModal({
   visible,
@@ -9,7 +16,38 @@ export default function SettingModal({
   visible: boolean;
   onClose: () => void;
 }) {
+  const router = useRouter();
+  const dispatch = useAppDispatch();
+  const { showToast } = useCustomToast();
   const [showSetting, setShowSetting] = useState(false);
+  const logOutHandler = async () => {
+    try {
+      overlayLoading.start();
+      const res = await logoutAction();
+
+      if (!res.success) {
+        showToast(res.message, "danger");
+        return;
+      }
+
+      await signOut({
+        callbackUrl: "/customer/login",
+        redirect: false,
+      });
+
+      dispatch(clearCart());
+      showToast("You are logged out successfully!", "success");
+      overlayLoading.stop();
+      setTimeout(() => {
+        router.push("/customer/login");
+        router.refresh();
+      }, 100);
+    } catch (err: unknown) {
+      overlayLoading.stop();
+      const message = err instanceof Error ? err.message : "Logout failed";
+      showToast(message, "danger");
+    }
+  };
   // 如果不显示,直接 return null
   if (!visible) return null;
   
@@ -132,7 +170,7 @@ export default function SettingModal({
 
               {/* Log Out */}
               <div className="py-4  absolute left-1/2 -translate-x-1/2 bottom-7 ">
-                <button className="text-black font-medium  w-full underline text-center">
+                <button onClick={logOutHandler} className="text-black font-medium  w-full underline text-center" type="button">
                   Log Out
                 </button>
               </div>

+ 0 - 265
src/components/customer/credentials/CredentialModal.tsx

@@ -1,265 +0,0 @@
-"use client";
-
-import { useDisclosure } from "@heroui/use-disclosure";
-import { AnimatePresence, motion } from "framer-motion";
-import clsx from "clsx";
-import { useSession, 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 LoadingDots from "@components/common/icons/LoadingDots";
-import { logoutAction } from "@/actions";
-import { useAppDispatch } from "@/store/hooks";
-import { clearCart } from "@/store/slices/cart-slice";
-
-
-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();
-
-
-  useBodyScrollLock(finalIsOpen );
-
-
-  const {
-    handleSubmit,
-    formState: { isSubmitting },
-  } = useForm();
-
-  const {data: session} = useSession();
-
-  const onSubmit = async () => {
-    try {
-  
-      const res = await logoutAction();
-
-      if (!res.success) {
-        showToast(res.message, "danger");
-        return;
-      }
-
-      await signOut({
-        callbackUrl: "/customer/login",
-        redirect: false,
-      });
-
-      dispatch(clearCart());
-      showToast("You are logged out successfully!", "success");
-      setTimeout(() => {
-        router.push("/customer/login");
-        router.refresh();
-      }, 100);
-    } 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>
-    </>
-  );
-}

+ 0 - 21
src/components/customer/credentials/index.tsx

@@ -1,21 +0,0 @@
-import CredentialModal from "./CredentialModal";
-
-export default function UserAccount({
-  children,
-  className,
-  onOpen,
-  onClose,
-  isOpen,
-}: {
-  children?: React.ReactNode;
-  className?: string;
-  onOpen?: () => void;
-  onClose?: () => void;
-  isOpen?: boolean;
-}) {
-  return (
-    <CredentialModal className={className} onOpen={onOpen} onClose={onClose} isOpen={isOpen}>
-      {children}
-    </CredentialModal>
-  );
-}

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

@@ -191,6 +191,7 @@ export default function MobileMenu({ menu, isOpen, onClose,isGuest }: MobileMenu
           
                  <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
@@ -201,6 +202,7 @@ export default function MobileMenu({ menu, isOpen, onClose,isGuest }: MobileMenu
                          height={37}
                        />
                    </div>
+                    }
                  
                    {subMenuList.length === 0 ? (
                      <div className="text-gray-500 text-xl">No sub categories</div>

+ 2 - 2
src/components/layout/navbar/NavbarSearchIcon.tsx

@@ -21,11 +21,11 @@ export default function NavbarSearchIcon() {
             cy="8.75"
             r="8"
             stroke="rgba(0, 0, 0, 1)"
-            stroke-width="1.5"
+            strokeWidth="1.5"
           ></circle>
           <path
             stroke="rgba(0, 0, 0, 1)"
-            stroke-width="1.5"
+            strokeWidth="1.5"
             d="M14.25 15.25L17.25 18.25"
           ></path>
         </svg>

+ 36 - 38
src/components/theme/filters/NewMobileFilter.tsx

@@ -71,29 +71,29 @@ export default function MobileFilter({
     console.log("【抽屉是否打开isOpen】", isOpen);
   }, [isOpen, filters]);
 
-  /**
-   * 单选
-   */
-  const toggleOption = (code: string, id: string) => {
-    setTempFilters((prev) => {
-      const current = prev[code];
-
-      if (current === id) {
-        const next = { ...prev };
-
-        delete next[code];
-        // 打印点击后最新的临时筛选
-        console.log("【点击选项后临时筛选tempFilters】", next);
-        return next;
-      }
-
-      return {
-        ...prev,
+ const toggleOption = (code: string, id: string) => {
+  setTempFilters((prev) => {
+    const currentStr = prev[code] ?? "";
+    const selectedSet = new Set(currentStr ? currentStr.split(",") : []);
+    // debugger
+    if (selectedSet.has(id)) {
+      // 已选中 → 删除
+      selectedSet.delete(id);
+    } else {
+      // 未选中 → 添加
+      selectedSet.add(id);
+    }
 
-        [code]: id,
-      };
-    });
-  };
+    const next = { ...prev };
+    if (selectedSet.size === 0) {
+      delete next[code];
+    } else {
+      next[code] = Array.from(selectedSet).join(",");
+    }
+    console.log("【点击选项后临时筛选tempFilters】", next);
+    return next;
+  });
+};
 
   const toggleGroupExpand = (code: string) => {
     setExpandedGroups((prev) => ({
@@ -175,39 +175,37 @@ export default function MobileFilter({
                           <FilterPrice
                             priceAttr={attr.node}
                             initialRange={{
-                              minPrice: tempFilters.minPrice
-                                ? Number(tempFilters.minPrice)
+                              minPrice: tempFilters.price_from
+                                ? Number(tempFilters.price_from)
                                 : undefined,
-                              maxPrice: tempFilters.maxPrice
-                                ? Number(tempFilters.maxPrice)
+                              maxPrice: tempFilters.price_to
+                                ? Number(tempFilters.price_to)
                                 : undefined,
                             }}
                             onChangeComplete={(range) => {
                               setTempFilters((prev) => ({
                                 ...prev,
-                                minPrice: String(range.minPrice),
-                                maxPrice: String(range.maxPrice),
+                                price_from: String(range.minPrice),
+                                price_to: String(range.maxPrice),
                               }));
                             }}
                           />
                         ) : (
-                          (attr.node.options.edges || []).map((opt: any) => {
-                            const checked =
-                              tempFilters[attr.node.code] == opt.node._id;
-                            return (
+                         (attr.node.options.edges || []).map((opt: any) => {
+                            const selectedStr = tempFilters[attr.node.code] ?? "";
+                            const idStr = String(opt.node._id);
+                            const checked = selectedStr.split(",").includes(idStr);
+                            return opt.node.productCount > 0 && (
                               <label
                                 key={opt.node._id}
-                                className="flex gap-3 text-lg"
+                                className="flex gap-3 text-lg items-center"
                               >
                                 <input
                                   type="checkbox"
                                   checked={checked}
-                                  onChange={() =>
-                                    toggleOption(attr.node.code, opt.node._id)
-                                  }
+                                  onChange={() => toggleOption(attr.node.code, String(opt.node._id))}
                                 />
-
-                                <span>{opt.node.adminName}</span>
+                                <span className="wrap-break-word">{opt.node.adminName}</span>
                               </label>
                             );
                           })

+ 1 - 0
src/graphql/catalog/queries/CategoryAttributeFilters.ts

@@ -25,6 +25,7 @@ export const GET_CATEGORY_ATTR_FILTERS = gql`
                 adminName
                 sortOrder
                 swatchValue
+                productCount
               }
             }
           }

+ 2 - 0
src/graphql/catalog/queries/GetCategoryProducts.ts

@@ -4,6 +4,8 @@ export const CATEGORY_PRODUCTS = gql`
 query CategoryProducts($slug: String!, $filter: String, $sortKey: String, $reverse: Boolean, $first: Int, $after: String) {
   categoryProducts(slug: $slug, filter: $filter, sortKey: $sortKey, reverse: $reverse, first: $first, after: $after) {
     totalCount
+    description
+    banner
     pageInfo {
      endCursor     
      hasNextPage   

+ 0 - 27
src/types/checkout/type.ts

@@ -461,33 +461,6 @@ export interface State {
 }
 
 
-export interface ShipAddressFormData { 
-    shippingAddressId: string | number,
-    shippingEmail: string,
-    shippingFirstName: string,
-    shippingLastName: string,
-    shippingCompanyName: string,
-    shippingAddress: string,
-    shippingCountry: string,
-    shippingState: string,
-    shippingCity: string,
-    shippingPostcode: string,
-    shippingPhoneNumber: string,
-}
-export interface BillAddressFormData { 
-    billingAddressId: string | number,
-    billingEmail: string,
-    billingFirstName: string,
-    billingLastName: string,
-    billingCompanyName: string,
-    billingAddress: string,
-    billingCountry: string,
-    billingState: string,
-    billingCity: string,
-    billingPostcode: string,
-    billingPhoneNumber: string,
-}
-
 export interface CreatePaymentReplay{
     id: number;
     success: boolean;

+ 33 - 6
src/utils/helper.ts

@@ -300,7 +300,6 @@ export function buildProductFilters(params: {
 export function newBuildProductFilters(params: {
   [key: string]: string | string[] | undefined;
 }) {
-  // ==========黑名单:这些字段不会筛选==========
   const EXCLUDE_KEYS = new Set([
     "q",
     "sort",
@@ -309,13 +308,13 @@ export function newBuildProductFilters(params: {
     "after",
     "page",
   ]);
+  const PRICE_KEYS = new Set(["price_from", "price_to"]);
 
   const extractId = (value: string) => {
     if (/^\d+$/.test(value)) return value;
     const match = value.match(/\/(\d+)$/);
     return match ? match[1] : null;
   };
-
   const parseParamIds = (raw: string | string[] | undefined) => {
     let list: string[] = [];
     if (typeof raw === "string") {
@@ -328,13 +327,20 @@ export function newBuildProductFilters(params: {
       .filter((id): id is string => Boolean(id));
   };
 
-  const filterObject: Record<string, string> = {};
+  const filterObject: Record<string, string > = {};
 
-  // 遍历全部传入参数
   for (const [key, value] of Object.entries(params)) {
-    // 黑名单字段直接跳过
     if (EXCLUDE_KEYS.has(key)) continue;
 
+    // ✅ 价格字段单独处理,不执行ID提取
+    if (PRICE_KEYS.has(key)) {
+      if (typeof value === "string" && value) {
+        filterObject[key] = value;
+      }
+      continue;
+    }
+
+    // 普通属性筛选,沿用原来的ID提取逻辑
     const ids = parseParamIds(value);
     if (ids.length > 0) {
       filterObject[key] = ids.join(",");
@@ -362,4 +368,25 @@ export function getUuId() {
         localStorage.setItem(key, id);
     }
     return id;
-};
+};
+// 筛选字段要多个match {\"wig_color\":{\"match\":\"33\"},\"price_from\":10,\"price_to\":212}
+ export function transformFiltersForApi(raw: Record<string, any>) {
+   // filter传参需要调整
+  const result: Record<string, any> = {};
+
+  for (const key in raw) {
+    const val = raw[key];
+    // 价格字段特殊处理:转数字,直接赋值,不包match
+    if (key === "price_from" || key === "price_to") {
+      // 空/undefined 跳过
+      if (val === undefined || val === null || val === "") continue;
+      result[key] = Number(val);
+    } else {
+      // 其他所有筛选属性:套上 {match:xxx}
+      result[key] = {
+        match:String(val),
+      };
+    }
+   }
+   return result;
+  }

+ 1 - 1
src/utils/hooks/useCheckoutAddress.ts

@@ -12,7 +12,7 @@ import {normalizePhoneForForm} from "@/utils/phoneNumberTools";
 /**
  * 对比shipping address与billing address的数据是否完全相同
  */
-export function addressIsSame(billAddress: BillAddressFormData | null, shipAddress: ShipAddressFormData | null) {
+export function addressIsSame(billAddress: Omit<BillAddressFormData,'billingSameAsShipping'> | null, shipAddress: ShipAddressFormData | null) {
     if(billAddress === null && shipAddress === null) {
         return true;
     } else if(billAddress !== null && shipAddress !== null) {

+ 20 - 1
src/utils/orderDetailTools.ts

@@ -1,4 +1,4 @@
-import type { AddressListInOrderDetails,OrderAddressType } from "@/types/customer/type";
+import type { AddressListInOrderDetails,OrderAddressType,ProductItemAdditional } from "@/types/customer/type";
 import type { CartAddress } from "@/types/cart/type";
 
 export function getAddressFromOrderDetailAddressList(addressList: AddressListInOrderDetails, addressType: OrderAddressType) {
@@ -25,6 +25,25 @@ export function getAddressFromOrderDetailAddressList(addressList: AddressListInO
     return res;
 
 }
+
+export function getProductAdditionalInfo(productItem: ProductItemAdditional) { 
+    const attributeKeys = Object.keys(productItem.attributes);
+    let res = '';
+    attributeKeys.forEach(key => {
+      const attribute = productItem.attributes[key];
+      if (attribute) {
+        // option_label value_label
+
+        if(res) {
+            res = res + ', ' + attribute.option_label + ': ' + attribute.value_label;
+        } else {
+            res = attribute.option_label + ': ' + attribute.value_label;
+        }
+      }
+    });
+    return res;
+}
+
 export function getProductFinalPriceInOrder() {}
 // export function getProductAdditinalInOrder(itemAdditional: string) {
 //     const additinal = JSON.parse(itemAdditional);