Browse Source

加购弹窗修改;删除无用代码;金额计算优化

fogwind 4 days ago
parent
commit
c2601563c0

+ 6 - 6
src/app/(checkout)/checkout/_components/CheckoutAddress/BillingAddressCheckout.tsx

@@ -343,7 +343,7 @@ export default function BillingAddressCheckout ({
                         Name *
                     </label>
                     <div className="w-full flex justify-between gap-3">
-                        <div className="">
+                        <div className="flex-auto">
                             <InputText
                                 type="text"
                                 placeholder="First name"
@@ -357,7 +357,7 @@ export default function BillingAddressCheckout ({
                                 error={errors.billingFirstName && errors.billingFirstName.message as string}
                             />
                         </div> 
-                        <div className="">
+                        <div className="flex-auto">
                             <InputText
                                 type="text"
                                 placeholder="Last name"
@@ -391,7 +391,7 @@ export default function BillingAddressCheckout ({
                     <label className="text-ly-16 block mb-1 font-semibold">Country and State/Province  *</label>
                     <div className="w-full">
                         <div className="w-full flex justify-between gap-3">
-                            <div className="">
+                            <div className="flex-auto w-1/2">
                                 <Select placeholder="Country/Region"
                                     {...register("billingCountry", {
                                         required: !billingSameAsShipping ? "Country/Region field is required" : false,
@@ -410,7 +410,7 @@ export default function BillingAddressCheckout ({
                                     error={errors.billingCountry && errors.billingCountry.message as string}
                                 /> 
                             </div>
-                            <div className="relative">
+                            <div className="flex-auto relative w-1/2">
                                 {statesLoading && <LoadingSpinner className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />}
                                 {statesOptions.length > 0 ?
                                 (
@@ -437,7 +437,7 @@ export default function BillingAddressCheckout ({
                             
                         </div>
                         <div className="w-full flex justify-between mt-4 gap-3">
-                            <div className="">
+                            <div className="flex-auto">
                                 <InputText
                                     type="text"
                                     placeholder="Zip/Postal Code"
@@ -451,7 +451,7 @@ export default function BillingAddressCheckout ({
                                     error={errors.billingPostcode&& errors.billingPostcode.message as string}
                                 />
                             </div>
-                            <div className="">
+                            <div className="flex-auto">
                                 <InputText
                                     type="text"
                                     placeholder="City"

+ 6 - 6
src/app/(checkout)/checkout/_components/CheckoutAddress/ShippingAddressCheckout.tsx

@@ -261,7 +261,7 @@ export default function ShippingAddressCheckout({
                     Name *
                 </label>
                 <div className="w-full flex justify-between gap-3">
-                    <div className="">
+                    <div className="flex-auto">
                         <InputText
                             type="text"
                             placeholder="First name"
@@ -275,7 +275,7 @@ export default function ShippingAddressCheckout({
                             error={errors.shippingFirstName && errors.shippingFirstName.message as string}
                         />
                     </div> 
-                    <div className="">
+                    <div className="flex-auto">
                         <InputText
                             type="text"
                             placeholder="Last name"
@@ -337,7 +337,7 @@ export default function ShippingAddressCheckout({
                 <label className="text-ly-16 block mb-1 font-semibold">Country and State/Province  *</label>
                 <div className="w-full">
                     <div className="w-full flex justify-between gap-3">
-                        <div className="">
+                        <div className="flex-auto w-1/2">
                             <Select placeholder="Country/Region"
                                 {...register("shippingCountry", {
                                     required: "Country/Region field is required",
@@ -357,7 +357,7 @@ export default function ShippingAddressCheckout({
                                 error={errors.shippingCountry && errors.shippingCountry.message as string}
                             /> 
                         </div>
-                        <div className=" relative">
+                        <div className="flex-auto relative w-1/2">
                             {statesLoading && <LoadingSpinner className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />}
                             {statesOptions.length > 0 ?
                             (
@@ -384,7 +384,7 @@ export default function ShippingAddressCheckout({
                         
                     </div>
                     <div className="w-full flex justify-between mt-4 gap-3">
-                        <div className="">
+                        <div className="flex-auto">
                             <InputText
                                 type="text"
                                 placeholder="Zip/Postal Code"
@@ -398,7 +398,7 @@ export default function ShippingAddressCheckout({
                                 error={errors.shippingPostcode&& errors.shippingPostcode.message as string}
                             />
                         </div>
-                        <div className="">
+                        <div className="flex-auto">
                             <InputText
                                 type="text"
                                 placeholder="City"

+ 1 - 11
src/app/(public)/_components/HomeContent.tsx

@@ -1,7 +1,6 @@
 "use client";
 // import { Swiper, SwiperSlide } from "swiper/react";
 // import { Navigation } from "swiper/modules";
-// import {useState} from "react";
 import JinGangSwiper from "./JinGangSwiper";
 import TopImageSwiper from "./TopImageSwiper"
 import HomeBestSellers from "./HomeBestSellers"
@@ -13,7 +12,7 @@ import RecommendedProducts from "./RecommendedProducts";
 // import CategoryTabSwiper  from "./CategoryTabSwiper ";
 import type {HomeApiData,HomeProduct } from "@/types/home/type"
 import {CustomerService} from "@/components/common/CustomerService/CustomerService";
-// import AddToCartModalNew from "@/components/common/AddToCartModal/AddToCartModalNew";
+
 // 定义组件入参类型
 interface HomeContentProps {
   homeData: HomeApiData;
@@ -125,10 +124,6 @@ export default function HomeContent({ homeData }: HomeContentProps) {
   const recommendedSection = sections.find(sec => sec.name === "Recommended_For_You");
   // const recommended: HomeProduct[] = recommendedSection?.products ?? [];
 
-  // const [isOpen, setIsOpen] = useState(false);
-  // const closeHandler = () => {
-  //   setIsOpen(false);
-  // } 
   return (<>
     <div className="min-h-screen pt-[162px] bg-white font-sans">
       {/* 顶部Banner */}
@@ -157,10 +152,5 @@ export default function HomeContent({ homeData }: HomeContentProps) {
       {/* <CategoryTabSwiper /> */}
     </div>
     <CustomerService />
-    {/* <button onClick={() => setIsOpen(true)}>test</button>
-    <AddToCartModalNew
-        isOpen={isOpen}
-        onClose={closeHandler}
-    /> */}
   </>);
 }

+ 10 - 15
src/app/(public)/product/_components/ProductInformation.tsx

@@ -2,6 +2,7 @@
 
 import { useState, useMemo } from "react";
 import clsx from "clsx";
+import Big from 'big.js';
 import type { ProductOption, ResolvedVariant } from "@/components/catalog/type";
 import { ProductAddToCart } from "@/app/(public)/product/_components/ProductAddToCart";
 import { useCustomToast } from "@utils/hooks/useToast";
@@ -171,21 +172,20 @@ export function ProductInformation({
     }
 
     if (variant.priceIndices && variant.priceIndices.length > 0) {
-      totalLinePrice =
-        Number(variant.priceIndices[0].regular_min_price) * productQty;
-      totalNowPrice = Number(variant.priceIndices[0].min_price) * productQty;
-      const dayPrice = totalNowPrice / 30; // 30天一期,日均成本
-      displayDayPrice = Number(dayPrice.toFixed(2));
+      totalLinePrice = Big(variant.priceIndices[0].regular_min_price).times(productQty).toNumber();
+      totalNowPrice = Big(variant.priceIndices[0].min_price).times(productQty).toNumber();
+      const dayPrice = Big(totalNowPrice).div(30).toFixed(2); // 30天一期,日均成本
+      displayDayPrice = Number(dayPrice);
     }
     if (totalLinePrice !== totalNowPrice) {
-      save = -(totalLinePrice - totalNowPrice);
+      save = Big(totalNowPrice).minus(totalLinePrice).toNumber();
     }
 
     return {
       variant,
-      totalLinePrice:   totalLinePrice || 170,
-      totalNowPrice:   totalNowPrice ||119,
-      save:  save ||  51,
+      totalLinePrice:   totalLinePrice,
+      totalNowPrice:   totalNowPrice,
+      save:  save,
       displayDayPrice: displayDayPrice,
     };
   }, [selected, flexibleVariants, productQty]);
@@ -253,7 +253,7 @@ export function ProductInformation({
     <>
       <div className="box-border w-full ">
         <h3 className="text-ly-14 text-black  leading-ly-22">
-          {name} {isSaleable}
+          {name}
         </h3>
         <ProductText ProductText={shortDescription} />
         <div className="flex items-center mt-2">
@@ -265,11 +265,6 @@ export function ProductInformation({
           {currentVariantInfo.totalLinePrice !==
             currentVariantInfo.totalNowPrice && (
             <>
-              <Price
-                className="text-ly-12 leading-ly-20 text-ly-gray line-through ml-1"
-                amount={String(currentVariantInfo.totalLinePrice)}
-                currencyCode={currencyCode}
-              />
               <Price
                 className="text-ly-12 leading-ly-20 text-ly-gray line-through ml-1"
                 amount={String(currentVariantInfo.totalLinePrice)}

+ 2 - 22
src/app/globals.css

@@ -200,6 +200,7 @@
   /* ---------- Document ---------- */
 
   html {
+    /*--font-outfit-variable 是在根layout.tsx中定义的*/
     font-family:
       var(--font-outfit-variable),
       system-ui,
@@ -354,28 +355,6 @@
    * 所以这里无需 !important。
    * ===================================================== */
 
-  .addtocartmodal-main-swiper-prev-btn,
-  .addtocartmodal-main-swiper-next-btn {
-    @apply bg-white;
-  }
-
-  .addtocartmodal-main-swiper-prev-btn.disable,
-  .addtocartmodal-main-swiper-next-btn.disable {
-    @apply bg-black/50;
-  }
-
-
-  .addtocartmodal-main-swiper-prev-btn svg,
-  .addtocartmodal-main-swiper-next-btn svg {
-    @apply stroke-black;
-  }
-
-  .addtocartmodal-main-swiper-prev-btn.disable svg,
-  .addtocartmodal-main-swiper-next-btn.disable svg {
-    @apply stroke-white;
-  }
-
-
   .addtocartmodal-main-swiper .swiper-pagination-fraction {
     @apply
       absolute
@@ -390,6 +369,7 @@
       rounded-xl
       px-3
       text-ly-14
+      bg-ly-deepgreen/50
       text-white;
   }
 

+ 139 - 83
src/components/common/AddToCartModal/AddToCartModal.tsx

@@ -2,6 +2,10 @@
 
 import { useState, useMemo } from "react";
 import { useRouter } from "next/navigation";
+import type { Swiper as SwiperType } from "swiper";
+import { Swiper, SwiperSlide } from 'swiper/react';
+import { FreeMode, Navigation, Thumbs, Pagination } from 'swiper/modules';
+import Big from 'big.js';
 import { useAppDispatch, useAppSelector } from "@/store/hooks";
 import { useCustomToast } from "@utils/hooks/useToast";
 import { 
@@ -9,21 +13,14 @@ import {
     clearAddToCartProduct 
 } from '@/store/slices/addToCartDialogSlice';
 import { useAddProduct } from "@utils/hooks/useAddToCart";
+import {useConfig} from "@/utils/hooks/useConfig";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
-import {
-  Drawer,
-  DrawerContent,
-  DrawerHeader,
-  DrawerBody,
-  DrawerFooter
-} from "@heroui/drawer";
 import type { 
     ProductOption, 
     ResolvedVariant 
 } from "@/components/catalog/type";
-import ImgSwiperInAddToCartModal from "./ImgSwiperInAddToCartModal";
+import CommonModal from "@/components/theme/ui/CommonModal";
 import ProductOptionsInAddToCartModal from "./ProductOptionsInAddToCartModal";
-import FooterBtnInAddToCartModal from "./FooterBtnInAddToCartModal";
 import { Price } from "@components/theme/ui/Price";
 import {
     isValueAvailable,
@@ -38,8 +35,14 @@ import {
 export default function AddToCartModal() {
     const router = useRouter();
     const dispatch = useAppDispatch();
+
+    const {getCurrentCurrencyItem} = useConfig();
+    const currentCurrency = getCurrentCurrencyItem();
+
     const {isOpen, product} = useAppSelector((state) => state.addToCartDialog);
 
+    const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
+
     const { appendProductToCart } = useAddProduct();
     const { showToast } = useCustomToast();
 
@@ -147,11 +150,11 @@ export default function AddToCartModal() {
             return { variant: null, totalLinePrice: 0, totalNowPrice: 0, save: 0 };
         }
         if(variant.priceIndices && variant.priceIndices.length > 0) {
-            totalLinePrice = Number(variant.priceIndices[0].regular_min_price) * productQty;
-            totalNowPrice = Number(variant.priceIndices[0].min_price) * productQty;
+            totalLinePrice = Big(variant.priceIndices[0].regular_min_price).times(productQty).toNumber();
+            totalNowPrice = Big(variant.priceIndices[0].min_price).times(productQty).toNumber();
         }
         if(totalLinePrice !== totalNowPrice) {
-            save = -(totalLinePrice - totalNowPrice); 
+            save = Big(totalNowPrice).minus(totalLinePrice).toNumber(); 
         }
 
         return {
@@ -202,7 +205,7 @@ export default function AddToCartModal() {
         return res;
     } 
 
-    const addToCartHandler = async (callback: ()=> void) => {
+    const addToCartHandler = async () => {
         if(!isCurrentSelectionAvailable) {
             showToast("The selected options are not available!", "warning");
             return;
@@ -210,10 +213,10 @@ export default function AddToCartModal() {
 
         if(currentVariantInfo.variant) {
             await addProductToCart();
-            callback();
+            closeModal();
         }
     };
-    const buyNowHandler = async (callback: ()=> void) => {
+    const buyNowHandler = async () => {
         if(!isCurrentSelectionAvailable) {
             showToast("The selected options are not available!", "warning");
             return;
@@ -221,7 +224,7 @@ export default function AddToCartModal() {
 
         if(currentVariantInfo.variant) {
             const res = await addProductToCart();
-            callback();
+            closeModal();
             if(res && !res.error) {
                 const responseData = res.data;
                 if(responseData && responseData.success) {
@@ -237,83 +240,136 @@ export default function AddToCartModal() {
             dispatch(clearAddToCartProduct());
         }, 300);
     };
-    const openChange = (e:boolean) => {
-        if(!e) {
-            closeModal();
-        }
-    }
 
-    return (
-            <Drawer
-                backdrop={"blur"}
-                placement="bottom"
-                isDismissable={false}
-                isKeyboardDismissDisabled={true}
-                isOpen={isOpen}
-                hideCloseButton
-                onOpenChange={(e) => openChange(e)}
-            >
-                <DrawerContent className="rounded-none h-17/20 max-h-none">
-                {(onClose) => (
-                    <>
-                        <DrawerHeader className="flex flex-col gap-1">
-                            <div>
-                                Select Options
-                                <button className="absolute top-2.5 right-2.5 w-6 h-6" onClick={onClose}>
-                                    <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>
-                            {/**产品图片 */}
-                            {images.length > 0 && (
-                                <div>
-                                <ImgSwiperInAddToCartModal images={images} />
-                                </div>
-                            )}
-                        </DrawerHeader>
-                        <DrawerBody className="drawer-body relative">
-                            <div>
-                                {/**产品选项 */}
-                                <div className="box-border w-full">
-                                    <ProductOptionsInAddToCartModal 
-                                        productOptions={productOptions}
-                                        selected={selected}
-                                        disabledMap={disabledMap}
-                                        onOptionClick={handleOptionClick}
-                                    />
+    return (<>
+        <CommonModal 
+            isOpen={isOpen}
+            onClose={closeModal}
+            fullyCustomer={true}
+            contentClassName="min-w-155 max-w-248.5"
+            body={
+                <div className="box-border w-full p-6 bg-white rounded-xl">
+                    <div className="flex justify-end">
+                        <button className="flex-none w-6 h-6" onClick={closeModal}>
+                            <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 className="box-border mt-1.5 grid h-134 w-full min-w-0 grid-cols-[76px_minmax(0,1fr)_461px]">
+                        
+                        <div className="min-w-0 h-full">
+                            <Swiper
+                                onSwiper={setThumbsSwiper}
+                                spaceBetween={10}
+                                slidesPerView={5}
+                                freeMode={true}
+                                watchSlidesProgress={true}
+                                modules={[FreeMode, Navigation, Thumbs]}
+                                direction={'vertical'}
+                                className="w-full h-134 min-w-0 addtocartmodal-thumb-swiper"
+                            >
+                                {images.map((img) => {
+                                    return (
+                                    <SwiperSlide key={img.id + '_thumbs'} className="box-border h-25 rounded-xs p-0.5 border border-white [&.swiper-slide-thumb-active]:border-ly-middlegreen">
+                                        <img className="block w-full h-full object-cover"  src={img.publicPath} />
+                                    </SwiperSlide>
+                                    );
+                                })}
+                                
+                            </Swiper>
+                        </div>
+                        <div className= "relative max-w-100.5 min-w-0 h-full ml-2">
+                            <Swiper
+                                navigation={{
+                                    nextEl: '.addtocartmodal-main-swiper-next-btn',
+                                    prevEl: '.addtocartmodal-main-swiper-prev-btn',
+                                    disabledClass: 'disable'
+                                }}
+                                pagination={{
+                                    type: "fraction",
+                                    renderFraction: function (currentClass, totalClass) {
+                                        return '<span class="' + currentClass + '"></span>' +'/' +'<span class="' + totalClass + '"></span>';
+                                    },
+                                }}
+                                thumbs={{ swiper: thumbsSwiper }}
+                                modules={[Navigation, Thumbs,Pagination]}
+                                className="addtocartmodal-main-swiper w-full h-full min-w-0"
+                            >
+                                {images.map((img) => {
+                                    return (
+                                    <SwiperSlide key={img.id}>
+                                        <img className="block w-full h-full object-cover"  src={img.publicPath} />
+                                    </SwiperSlide>
+                                    );
+                                })}
+                                
+                            </Swiper>
+                            <button className="addtocartmodal-main-swiper-prev-btn group [&.disable]:bg-black/50 bg-white z-1 absolute left-3 top-1/2 -translate-y-1/2 flex items-center justify-center w-10 h-10 rounded-full">
+                                <svg className="w-6 h-6 stroke-black group-[.disable]:stroke-white" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none"><path strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M15 18L9 12L15 6"></path></svg>
+                            </button>
+                            <button className="addtocartmodal-main-swiper-next-btn group [&.disable]:bg-black/50 bg-white z-1 absolute right-3 top-1/2 -translate-y-1/2 flex items-center justify-center w-10 h-10 rounded-full">
+                                <svg className="w-6 h-6 stroke-black group-[.disable]:stroke-white" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none"><path strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M9 18L15 12L9 6"></path></svg>
+                            </button>
+                        </div>
+                                
+                        
+                        <div className="box-border px-5 min-w-0 ml-4 flex flex-col">
+                            <div className="flex-none">
+                                <div className="text-ly-14 leading-5 line-clamp-2">
+                                    {product?.name}
                                 </div>
-                            </div>
-                        </DrawerBody>
-                        <DrawerFooter className="flex-col border-t border-gray-200 gap-y-4 p-4">
-                            <div className="flex justify-between items-center">
-                                <Price 
-                                    className="text-ly-14 text-ly-gray line-through"
-                                    amount={String(currentVariantInfo.totalLinePrice)} 
-                                    currencyCode="USD"
-                                />
-                                <div className="flex items-center">
-                                    <Price 
+                                <div className="mt-4 flex items-center">
+
+                                    {/*<Price 
                                         className="text-ly-14 bg-ly-gold py-0.5 px-1"
                                         amount={String(currentVariantInfo.save)} 
                                         currencyCode="USD"
-                                    />
+                                    /> */}
                                     <Price 
-                                        className="text-ly-20 font-bold ml-1.5"
+                                        className="text-ly-24 font-bold"
                                         amount={String(currentVariantInfo.totalNowPrice)} 
-                                        currencyCode="USD"
+                                        currencyCode={currentCurrency.code}
                                     />
+                                    <Price 
+                                        className="text-ly-14 text-ly-gray line-through ml-2.5"
+                                        amount={String(currentVariantInfo.totalLinePrice)} 
+                                        currencyCode={currentCurrency.code}
+                                    />
+                                    
                                 </div>
                             </div>
-                            <div>
-                                <FooterBtnInAddToCartModal 
-                                    isAvailable={isCurrentSelectionAvailable} 
-                                    onAddToCart={()=> addToCartHandler(onClose)}
-                                    onBuyNow={()=> buyNowHandler(onClose)}
+                            <div className="flex-auto my-6 max-h-92 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-100">
+                                
+                                <ProductOptionsInAddToCartModal 
+                                    productOptions={productOptions}
+                                    selected={selected}
+                                    disabledMap={disabledMap}
+                                    onOptionClick={handleOptionClick}
                                 />
                             </div>
-                        </DrawerFooter>
-                    </>
-                )}
-                </DrawerContent>
-            </Drawer>
-    );
+                            <div className="flex-none">
+                                <div className="flex justify-between gap-3">
+                                    <button className="flex-1 flex justify-center items-center h-12 text-ly-16 font-bold text-white rounded-3xl bg-ly-deepgreen" 
+                                        type="button"
+                                        onClick={buyNowHandler}
+                                    >
+                                        Buy now
+                                    </button>
+                                    <button className="flex-1 flex justify-center items-center h-12 text-ly-16 font-bold text-white rounded-3xl bg-ly-middlegreen" 
+                                        type="button"
+                                        onClick={addToCartHandler}
+                                    >
+                                        Add to bag
+                                    </button>
+                                </div>
+                                {/* <button className="flex justify-center items-center w-full mt-3.75 h-12 text-ly-16 font-bold text-white rounded-3xl bg-[#ffc43a]" type="button">
+                                    <img className="h-6 w-18.75" src="/image/payment/paypal-logo.svg" />
+                                </button> */}
+                            </div>
+                        </div>
+                    </div>
+                </div> 
+                
+            }
+        />
+    </>);
 }

+ 14 - 9
src/components/common/AddToCartModal/AddToCartModalWrapper.tsx

@@ -1,14 +1,19 @@
 "use client";
 
-// import { AddToCartModal } from "@components/common/AddToCartModal/AddToCartModal";
-import { useAppSelector } from "@/store/hooks";
-import dynamic from 'next/dynamic';
+import AddToCartModal from "./AddToCartModal";
 
-const AddToCartModal = dynamic(() => import("./AddToCartModal"),{ssr: false});
 export function AddToCartModalWrapper() {
-  const {product} = useAppSelector((state) => state.addToCartDialog);
+  return <AddToCartModal />;
+}
 
-  return (
-    <AddToCartModal key={product?.id} />
-  );
-}
+// import { useAppSelector } from "@/store/hooks";
+// import dynamic from 'next/dynamic';
+
+// const AddToCartModal = dynamic(() => import("./AddToCartModal"),{ssr: false});
+// export function AddToCartModalWrapper() {
+//   const {product} = useAppSelector((state) => state.addToCartDialog);
+
+//   return (
+//     <AddToCartModal key={product?.id} />
+//   );
+// }

+ 0 - 38
src/components/common/AddToCartModal/FooterBtnInAddToCartModal.tsx

@@ -1,38 +0,0 @@
-"use client";
-
-import clsx from "clsx";
-
-export default function FooterBtnInAddToCartModal({
-    isAvailable,
-    onBuyNow,
-    onAddToCart
-}: {
-    isAvailable: boolean; 
-    onBuyNow: () => void,
-    onAddToCart: () => void
-}) {
-
-    const btnClass = 'flex-none flex items-center justify-center w-40 h-11.5 rounded-4xl text-ly-16 font-bold text-white';
-    return (
-        <div className="flex justify-between">
-            {isAvailable ? 
-                (<>
-                    <button className={clsx(btnClass,'bg-ly-deepgreen')}
-                        onClick={onBuyNow}
-                    >
-                        Buy Now
-                        
-                    </button>
-                    <button className={clsx(btnClass, 'bg-ly-middlegreen')}
-                        onClick={onAddToCart}
-                    >
-                        Add to Cart
-                    </button>
-                </>)
-            :
-                <p className="text-center text-ly-14">The selected options are not available!</p>
-            }
-            
-        </div>
-    );
-}

+ 0 - 26
src/components/common/AddToCartModal/ImgSwiperInAddToCartModal.tsx

@@ -1,26 +0,0 @@
-"use client";
-import { memo } from "react";
-import { Swiper, SwiperSlide } from 'swiper/react';
-import Image from "next/image";
-import type { ProductMediaType } from "@/components/catalog/type";
-
-const ImgSwiperInAddToCartModal = memo(function ImgSwiperInAddToCartModal({images}: {images: ProductMediaType[];}) { 
-    return (
-        <Swiper
-            spaceBetween={10}
-            slidesPerView={3}
-            onSlideChange={() => console.log('slide change')}
-            onSwiper={(swiper) => console.log(swiper)}
-        >
-            {images.map((img) => {
-                return (
-                <SwiperSlide key={img.id}>
-                    <Image src={img.publicPath} alt={"todo test "} width={175} height={233} />
-                </SwiperSlide>
-                );
-            })}
-        </Swiper>
-    );
-});
-
-export default ImgSwiperInAddToCartModal;

+ 0 - 1
src/components/common/AddToCartModal/OpenAddToCartModalButton.tsx

@@ -32,7 +32,6 @@ export const OpenAddToCartModalButton: FC<OpenAddToCartModalButtonProps> = ({
         variables: { id: productId },
       });
 
-      console.log("product ----- ", result);
 
       const productData = result?.data?.product;
       if (result.error) {

+ 4 - 5
src/components/common/AddToCartModal/ProductOptionsInAddToCartModal.tsx

@@ -20,7 +20,7 @@ export default function ProductOptionsInAddToCartModal({
     }) => void;
 }) {
 
-    const classNameOptionValueBtn = "text-ly-14 leading-ly-24 border border-solid rounded-lg pt-1.5 pb-1.5 pl-3 pr-3 bg-white";
+    const classNameOptionValueBtn = "text-ly-16 leading-ly-24 border border-solid rounded-lg p-3 bg-white";
     // 处理选项点击
     const handleOptionClick = (optionId: number, valueId: number) => {
         onOptionClick({
@@ -31,11 +31,10 @@ export default function ProductOptionsInAddToCartModal({
 
     return (
         <>
-            this is test
             {productOptions.map((option) => { 
-                return (<div key={option.id}>
-                    <div className="text-ly-12 font-medium">{option.label}</div>
-                    <div className="flex flex-wrap gap-3 mt-3">
+                return (<div key={option.id} className="mt-6 first:mt-0">
+                    <div className="text-ly-14 font-medium">{option.label}*</div>
+                    <div className="flex flex-wrap gap-3 mt-2">
                         {option.values.map((value) => { 
                             const isSelected = selected[option.id] === value.id;
                             const isDisabled = disabledMap[value.id] ? true : false;

+ 0 - 58
src/components/common/icons/cart/DeleteItemButton.tsx

@@ -1,58 +0,0 @@
-import LoadingDots from "@components/common/icons/LoadingDots";
-import { TrashIcon } from "@heroicons/react/24/outline";
-import { useAddProduct } from "@utils/hooks/useAddToCart";
-import clsx from "clsx";
-
-
-function SubmitButton({
-  loading,
-  handleRemoveCart,
-}: {
-  loading: boolean;
-  handleRemoveCart: () => void;
-}) {
-  return (
-    <button
-      aria-disabled={loading}
-      aria-label="Remove cart item"
-      className={clsx(
-        "ease flex h-[17px] w-[17px] cursor-pointer items-center justify-center rounded-full  transition-all duration-200",
-        {
-          "cursor-wait px-0": loading,
-        }
-      )}
-      type="button"
-      onClick={handleRemoveCart}
-    >
-      {loading ? (
-        <LoadingDots className="bg-black dark:bg-white" />
-      ) : (
-        <TrashIcon className="hover:text-accent-3 mx-[1px] h-6 w-6" />
-      )}
-    </button>
-  );
-}
-
-interface CartItemEdge {
-  node: {
-    id: string;
-    quantity: number;
-    name: string;
-    price: number;
-  };
-}
-
-export function DeleteItemButton({ item }: { item: CartItemEdge }) {
-  const { deleteProductFromCart, isRemoveLoading } = useAddProduct();
-  const itemId = item?.node?.id;
-  const handleRemoveCart = () => {
-    deleteProductFromCart(Number(itemId));
-  };
-
-  return (
-    <SubmitButton
-      handleRemoveCart={handleRemoveCart}
-      loading={isRemoveLoading}
-    />
-  );
-}

+ 0 - 85
src/components/common/icons/cart/EditItemQuantityButton.tsx

@@ -1,85 +0,0 @@
-import LoadingDots from "@components/common/icons/LoadingDots";
-import { MinusIcon, PlusIcon } from "@heroicons/react/24/outline";
-import { throttle } from "@utils/helper";
-import { useAddProduct } from "@utils/hooks/useAddToCart";
-import clsx from "clsx";
-
-function SubmitButton({
-  type,
-  handleUpdateCart,
-  pending,
-}: {
-  type: "plus" | "minus";
-  handleUpdateCart: (_: "plus" | "minus") => void;
-  pending: boolean;
-}) {
-  return (
-    <button
-      aria-disabled={pending}
-      aria-label={
-        type === "plus" ? "Increase item quantity" : "Reduce item quantity"
-      }
-      className={clsx(
-        "ease flex h-full cursor-pointer min-w-[36px] max-w-[36px] flex-none items-center justify-center rounded-full px-2 transition-all duration-200 hover:border-neutral-800 hover:opacity-80",
-        {
-          "cursor-wait": pending,
-          "ml-auto": type === "minus",
-        }
-      )}
-      type="button"
-      onClick={() => handleUpdateCart(type)}
-    >
-      {pending ? (
-        <LoadingDots className="bg-black dark:bg-white" />
-      ) : type === "plus" ? (
-        <PlusIcon className="h-4 w-4 dark:text-neutral-100" />
-      ) : (
-        <MinusIcon className="h-4 w-4 dark:text-neutral-100" />
-      )}
-    </button>
-  );
-}
-
-interface CartItemEdge {
-  node: {
-    id: string;
-    quantity: number;
-    name: string;
-    price: number;
-  };
-}
-
-export function EditItemQuantityButton({
-  item,
-  type,
-}: {
-  item: CartItemEdge;
-  type: "plus" | "minus";
-}) {
-  const { onUpdateCart, isUpdateLoading } = useAddProduct();
-
-  
-  const handleUpdateCart = throttle((type: "plus" | "minus") => {
-    let qty = item?.node?.quantity;
-    if(!isUpdateLoading){
-      if (type === "plus") {
-        qty += 1;
-      } else if (type === "minus") {
-        qty -= 1;
-      }
-      onUpdateCart({
-        cartItemId: Number(item?.node?.id),
-        quantity: qty,
-      });
-    }
-  }, 200);
-
-
-  return (
-    <SubmitButton
-      handleUpdateCart={handleUpdateCart}
-      pending={isUpdateLoading}
-      type={type}
-    />
-  );
-}

+ 11 - 5
src/components/common/portal/Portal.tsx

@@ -1,10 +1,16 @@
-'use client';
+// 'use client';
 
 
-import dynamic from 'next/dynamic'
+// import dynamic from 'next/dynamic'
 
 
-const Portal = dynamic(() => import("./PortalBox"), {
-  ssr: false,
-});
+// const Portal = dynamic(() => import("./PortalBox"), {
+//   ssr: false,
+// });
+// export default Portal;
+
+"use client";
+
+import PortalBox from "./PortalBox";
+const Portal = PortalBox;
 export default Portal;

+ 10 - 5
src/components/common/portal/PortalBox.tsx

@@ -28,6 +28,7 @@ export default function Portal({ children }: { children: React.ReactNode }) {
 完全可以在 render 时计算出来(使用document 来判断),不需要专门声明一个state在副作用里更改状态(引起二次渲染),所以eslint报错。
 */
 
+import { useEffect, useState } from 'react';
 import { createPortal } from 'react-dom';
 
 export default function PortalBox({
@@ -35,12 +36,16 @@ export default function PortalBox({
 }: {
   children: React.ReactNode;
 }) {
-  
+  const [mounted, setMounted] = useState(false);
 
-  // 服务端和客户端首次渲染时都返回 null
-  if (typeof window === "undefined") {
-    return null;
-  }
+
+  useEffect(() => {
+    // 这里规避检查是有意为之,对于PortalBox组件这是必须的
+    // eslint-disable-next-line react-hooks/set-state-in-effect
+    setMounted(true); // 组件挂载后,把标志位置为 true
+  }, []); // 空依赖数组,仅执行一次
+
+  if (!mounted) return null; // SSR 时不渲染,避免 hydration 错误
 
   const portalRoot = document.getElementById('modal-root');
 

+ 39 - 39
src/components/theme/ui/CommonModal.tsx

@@ -20,6 +20,7 @@ export default function CommonModal({
     body,
     footer,
     zIndex,
+    fullyCustomer, // 弹窗内的结构完全自己写,不使用预设的结构
 }: {
     isOpen: boolean;
     destroyOnClose?: boolean;
@@ -32,6 +33,7 @@ export default function CommonModal({
     body: React.ReactNode;
     footer?: React.ReactNode;
     zIndex?: string;
+    fullyCustomer?: boolean;
 }) {
     // show - 显示
     // outing - 隐藏动画中
@@ -53,6 +55,8 @@ export default function CommonModal({
         applyClassName = contentClassName;
     }
 
+    const contentBoxClassName = fullyCustomer ? (contentClassName || '') : (baseClassNname + ' ' + applyClassName);
+
     useLayoutEffect(() => {
         const original = document.documentElement.style.overflow;
         if(isOpen){
@@ -85,35 +89,22 @@ export default function CommonModal({
         return ()=>{ document.documentElement.style.overflow = original; };
     },[isOpen, destroyOnClose, mounted]);
 
-    // useLayoutEffect(() => {
-    //     const original = document.documentElement.style.overflow;
-    //     if(isOpen) {
-    //         document.documentElement.style.overflow = "hidden";
-    //     } else {
-    //         document.documentElement.style.overflow = "";
-    //     }
-    //     // intent: 为了实现关闭时也有动画效果,告诉eslint忽略这个错误
-    //     if(isOpen) {
-    //         // eslint-disable-next-line react-hooks/set-state-in-effect
-    //         setRootVisible('show');
-    //     } else {
-    //         if(cssInit.current) {
-    //             // eslint-disable-next-line react-hooks/set-state-in-effect
-    //             setRootVisible('outing');
-    //         }
-            
-    //     }
 
-    //     return () => { document.documentElement.style.overflow = original; };
-    // },[isOpen]);
     // 当弹窗完全显示后计算 footer 高度
     useLayoutEffect(() => {
-        if (rootVisible === 'show' && !cssInit.current && footerRef.current && headerRef.current) {
-            const footerHeight = footerRef.current.getBoundingClientRect().height;
-            const headerHeight = headerRef.current.getBoundingClientRect().height;
-            setRootPddingBottom(footerHeight);
-            setRootPddingTop(headerHeight);
-            cssInit.current = true;
+        if(fullyCustomer) {
+            if (rootVisible === 'show' && !cssInit.current) {
+            
+                cssInit.current = true;
+            }
+        } else {
+            if (rootVisible === 'show' && !cssInit.current && footerRef.current && headerRef.current) {
+                const footerHeight = footerRef.current.getBoundingClientRect().height;
+                const headerHeight = headerRef.current.getBoundingClientRect().height;
+                setRootPddingBottom(footerHeight);
+                setRootPddingTop(headerHeight);
+                cssInit.current = true;
+            }
         }
     }, [rootVisible]);
     const rootClick = (e: React.MouseEvent) => {
@@ -148,6 +139,7 @@ export default function CommonModal({
             <div 
                 ref={rootRef} 
                 className={clsx(
+                    "flex justify-center items-center",
                     "w-full h-full backdrop-blur-md backdrop-saturate-150 bg-black/30 fixed inset-0 transition-all ease-in-out",
                     (zIndex ? zIndex : "z-50"),{
                     "block fade-in": rootVisible === "show",
@@ -158,31 +150,39 @@ export default function CommonModal({
                 onClick={rootClick}
             >
                 <div 
-                    className={clsx(baseClassNname,applyClassName,
+                    className={clsx(contentBoxClassName,
                         {
                             "slide-bottom-in": rootVisible === "show",
                             "slide-bottom-out": rootVisible === "outing",
                         }
                     )}
-                    style={{
+                    style={fullyCustomer ? {...contentStyle} : {
                         ...contentStyle,
                         paddingTop: rootPddingTop,
                         paddingBottom: rootPddingBottom
                     }}
                 >
-                    <div ref={headerRef} className="w-full box-border absolute left-0 top-0">
-                        {header && header}
-                    </div>
-                    
-                    <div className="w-full box-border h-full overflow-y-auto relative scrollbar-thin scrollbar-thumb-gray-200">
-                        
-                        {body}
+                    {!fullyCustomer ? 
+                        <>
+                        <div ref={headerRef} className="w-full box-border absolute left-0 top-0">
+                            {header && header}
+                        </div>
                         
-                    </div>
+                        <div className="w-full box-border h-full overflow-y-auto relative scrollbar-thin scrollbar-thumb-gray-200">
+                            
+                            {body}
+                            
+                        </div>
 
-                    <div ref={footerRef} className={clsx("z-1 w-full box-border absolute bottom-0 left-0")}>
-                        {footer && footer}
-                    </div>
+                        <div ref={footerRef} className={clsx("z-1 w-full box-border absolute bottom-0 left-0")}>
+                            {footer && footer}
+                        </div>
+                        </>
+                    :
+                        body
+                
+                    }
+                    
                 </div>
                 
             </div>