zgl před 3 dny
rodič
revize
8432eb8b19
36 změnil soubory, kde provedl 419 přidání a 936 odebrání
  1. 1 1
      src/app/(public)/_components/CategoryTabSwiper .tsx
  2. 0 3
      src/app/(public)/_components/HomeBestSellers.tsx
  3. 0 2
      src/app/(public)/_components/HomeContent.tsx
  4. 0 2
      src/app/(public)/_components/HomeMiddleBanner.tsx
  5. 0 3
      src/app/(public)/_components/HomeNewArrival.tsx
  6. 0 2
      src/app/(public)/_components/JinGangSwiper.tsx
  7. 1 5
      src/app/(public)/_components/MiddleImageSwiper.tsx
  8. 0 1
      src/app/(public)/_components/TopImageSwiper.tsx
  9. 0 3
      src/app/(public)/_components/Trending.tsx
  10. 0 5
      src/app/(public)/_components/css/swiper-custom.css
  11. 1 1
      src/app/(public)/customer/account/_components/AccountBottomHrefSwiper.tsx
  12. 0 2
      src/app/(public)/customer/account/_components/AccountVipPopup.tsx
  13. 9 14
      src/app/(public)/product/_components/ProductInformation.tsx
  14. 0 2
      src/app/(public)/product/_components/ProductMedia.tsx
  15. 1 2
      src/app/(public)/product/_components/review/ReviewImageGalleryModal.tsx
  16. 1 3
      src/app/(public)/product/_components/youmaylike/Recommend.tsx
  17. 194 118
      src/app/globals.css
  18. 1 1
      src/app/layout.tsx
  19. 0 305
      src/components/cart/CartModal.tsx
  20. 0 23
      src/components/cart/OpenCart.tsx
  21. 0 21
      src/components/cart/index.tsx
  22. 112 93
      src/components/common/AddToCartModal/AddToCartModal.tsx
  23. 21 7
      src/components/common/AddToCartModal/AddToCartModalWrapper.tsx
  24. 0 38
      src/components/common/AddToCartModal/FooterBtnInAddToCartModal.tsx
  25. 0 27
      src/components/common/AddToCartModal/ImgSwiperInAddToCartModal.tsx
  26. 0 58
      src/components/common/icons/cart/DeleteItemButton.tsx
  27. 0 85
      src/components/common/icons/cart/EditItemQuantityButton.tsx
  28. 10 5
      src/components/common/portal/Portal.tsx
  29. 10 6
      src/components/common/portal/PortalBox.tsx
  30. 1 1
      src/components/customer/accountSwiper.tsx
  31. 0 1
      src/components/home/HomeImageBanner.tsx
  32. 6 6
      src/components/layout/footer/FooterMenu.tsx
  33. 4 4
      src/components/layout/footer/index.tsx
  34. 1 44
      src/components/layout/navbar/MobileSearch.tsx
  35. 1 1
      src/components/layout/navbar/NavbarActions.tsx
  36. 44 41
      src/components/theme/ui/CommonModal.tsx

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

@@ -3,7 +3,7 @@
 import Link from "next/link";
 import { Swiper, SwiperSlide } from "swiper/react";
 import { FreeMode } from "swiper/modules";
-import "swiper/css";
+
 
 // 标签数据,可自由增删修改文字、跳转路由
 const tabList = [

+ 0 - 3
src/app/(public)/_components/HomeBestSellers.tsx

@@ -2,9 +2,6 @@
 import { Swiper, SwiperSlide } from "swiper/react";
 import { Navigation, Pagination } from "swiper/modules";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
-// 导入Swiper基础样式
-import "swiper/css";
-import "swiper/css/navigation";
 import type { HomeProduct } from "@/types/home/type";
 import Link from "next/link";
 interface HomeBestSellersProps {

+ 0 - 2
src/app/(public)/_components/HomeContent.tsx

@@ -10,8 +10,6 @@ import MiddleImageSwiper from "./MiddleImageSwiper";
 import Trending from "./Trending";
 import RecommendedProducts from "./RecommendedProducts";
 import CategoryTabSwiper  from "./CategoryTabSwiper ";
-import "swiper/css";
-import "swiper/css/navigation";
 import type {HomeApiData,HomeProduct } from "@/types/home/type"
 import {CustomerService} from "@/components/common/CustomerService/CustomerService";
 // 定义组件入参类型

+ 0 - 2
src/app/(public)/_components/HomeMiddleBanner.tsx

@@ -2,8 +2,6 @@
 import { useState } from "react";
 import { Swiper, SwiperSlide } from "swiper/react";
 import { Navigation } from "swiper/modules";
-import "swiper/css";
-import "swiper/css/navigation";
 import Image from "next/image";
 import type { HomeSectionImage } from "@/types/home/type";
 interface middleBannerProps {

+ 0 - 3
src/app/(public)/_components/HomeNewArrival.tsx

@@ -3,9 +3,6 @@ import { Swiper, SwiperSlide } from "swiper/react";
 import { Navigation, Pagination } from "swiper/modules";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
 import Link from "next/link";
-// 导入Swiper基础样式
-import "swiper/css";
-import "swiper/css/navigation";
 import type {HomeProduct} from "@/types/home/type"
 interface NewArrivalProps {
   newArrival: HomeProduct[];

+ 0 - 2
src/app/(public)/_components/JinGangSwiper.tsx

@@ -3,8 +3,6 @@ import { useState } from "react";
 import { Swiper, SwiperSlide } from "swiper/react";
 import type { Swiper as SwiperType } from "swiper/types";
 import { Navigation } from "swiper/modules";
-import "swiper/css";
-import "swiper/css/navigation";
 import Image from "next/image";
 import type { HomeSectionImage } from "@/types/home/type";
 interface jinGangProps {

+ 1 - 5
src/app/(public)/_components/MiddleImageSwiper.tsx

@@ -1,10 +1,6 @@
 
 import { Swiper, SwiperSlide } from "swiper/react";
 import {  Pagination  } from "swiper/modules";
-import "swiper/css";
-import "swiper/css/pagination";
-import "swiper/css/navigation";
-import "./css/swiper-custom.css";
 import Link from "next/link";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
 import type {HomeProduct,HomeSectionImage} from "@/types/home/type"
@@ -62,7 +58,7 @@ const MiddleImageSwiper = ({ zeroWear,zeroWearImage }: zeroWearProps) => {
               type: "progressbar",
               el: ".my-progress-bar",
             }}
-            className="mySwiper"
+            className="mySwiper middle-image-swiper"
             breakpoints={{
               480: { slidesPerView: 1 },
               768: { slidesPerView: 2 },

+ 0 - 1
src/app/(public)/_components/TopImageSwiper.tsx

@@ -1,6 +1,5 @@
 
 import { Swiper, SwiperSlide } from 'swiper/react';
-import 'swiper/css';
 // import { Navigation, Pagination, Mousewheel, Keyboard } from 'swiper/modules';
 // 定义图片类型
 type BannerImageItem = {

+ 0 - 3
src/app/(public)/_components/Trending.tsx

@@ -1,9 +1,6 @@
 import { useState, useRef, useEffect } from "react";
 import { Swiper, SwiperSlide } from "swiper/react";
 import { Scrollbar, Navigation } from "swiper/modules";
-import "swiper/css";
-import "swiper/css/scrollbar";
-import "swiper/css/navigation";
 import Image from "next/image";
 import Link from "next/link";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";

+ 0 - 5
src/app/(public)/_components/css/swiper-custom.css

@@ -1,5 +0,0 @@
-/* swiper-custom.css */
-.swiper-horizontal > .swiper-pagination-progressbar, .swiper-pagination-progressbar.swiper-pagination-horizontal{
-    bottom: -16px ;
-    top: unset;
-}

+ 1 - 1
src/app/(public)/customer/account/_components/AccountBottomHrefSwiper.tsx

@@ -5,7 +5,7 @@ import { Swiper, SwiperSlide } from "swiper/react";
 // 只导入 Autoplay 即可!loop 内置了
 import { Autoplay } from "swiper/modules";
 
-import "swiper/css";
+
 
 const AccountBottomHrefSwiper = () => {
   const swiperRef = useRef<any>(null);

+ 0 - 2
src/app/(public)/customer/account/_components/AccountVipPopup.tsx

@@ -6,8 +6,6 @@ import { Navigation, Scrollbar } from "swiper/modules";
 import type { Swiper as SwiperType } from "swiper/types";
 import { useEffect, useState } from "react";
 import Image from "next/image";
-import "swiper/css";
-import "swiper/css/navigation";
 import { clientFetch } from "@/lib/restApiClient";
 
 // 模拟你的每一页数据,方便维护

+ 9 - 14
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]);
@@ -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)}

+ 0 - 2
src/app/(public)/product/_components/ProductMedia.tsx

@@ -1,8 +1,6 @@
 "use client";
 
 import Image from "next/image";
-import "swiper/css";
-import "swiper/css/pagination";
 import { Swiper, SwiperSlide } from "swiper/react";
 import { Pagination } from "swiper/modules";
 import type { Swiper as SwiperType } from "swiper";

+ 1 - 2
src/app/(public)/product/_components/review/ReviewImageGalleryModal.tsx

@@ -4,8 +4,7 @@ import { Swiper, SwiperSlide } from "swiper/react";
 import { Navigation } from "swiper/modules";
 import Image from "next/image";
 import type { GalleryItem } from "@/types/products/review";
-import "swiper/css";
-import "swiper/css/navigation";
+
 
 interface GalleryModalProps {
   open: boolean;

+ 1 - 3
src/app/(public)/product/_components/youmaylike/Recommend.tsx

@@ -3,9 +3,7 @@
 import { Swiper, SwiperSlide } from "swiper/react";
 import { Navigation, Pagination } from "swiper/modules";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
-// 导入Swiper基础样式
-import "swiper/css";
-import "swiper/css/navigation";
+
 
 // 模拟产品数据(12个产品,分2块,每块6个)
 const productData = [

+ 194 - 118
src/app/globals.css

@@ -1,88 +1,84 @@
+/* =========================================================
+ * Cascade layer order
+ *
+ * theme
+ *   ↓
+ * base
+ *   ↓
+ * vendor.swiper
+ *   ↓
+ * components
+ *   ↓
+ * utilities
+ * ======================================================= */
+
+@layer theme, base, vendor, components, utilities;
+@layer vendor.swiper;
+
+
+/* =========================================================
+ * Framework / vendor imports
+ *
+ * 注意:
+ * @import 必须位于普通 CSS rule 之前。
+ * Swiper 按需引入
+ * ======================================================= */
 @import "tailwindcss";
-@plugin '../../hero.ts';
-@source '../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}';
-@custom-variant dark (&:is(.dark *));
 
-html,
-body {
-  scrollbar-width: thin;
-  scrollbar-color: #6b7280 transparent;
-}
+@import "swiper/css" layer(vendor.swiper);
+@import "swiper/css/navigation" layer(vendor.swiper);
+@import "swiper/css/pagination" layer(vendor.swiper);
+@import 'swiper/css/free-mode' layer(vendor.swiper);
+@import 'swiper/css/thumbs' layer(vendor.swiper);
 
-html::-webkit-scrollbar,
-body::-webkit-scrollbar {
-  width: 6px;
-  height: 6px;
-}
 
-html::-webkit-scrollbar-track,
-body::-webkit-scrollbar-track {
-  background: transparent;
-}
+/* =========================================================
+ * HeroUI v2
+ * ======================================================= */
 
-html::-webkit-scrollbar-thumb,
-body::-webkit-scrollbar-thumb {
-  background-color: #6b7280;
-  border-radius: 9999px;
-}
+@plugin '../../hero.ts';
 
-.dark html::-webkit-scrollbar-thumb,
-.dark body::-webkit-scrollbar-thumb {
-  background-color: #d4d4d4;
-}
+@source '../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}';
 
-/**
- * 移动端适配核心:clamp() 根字号  在390px设计稿下 16px ≈ 4.1025641vw = 1rem
- * 比如 390px设计稿,有一个div宽度是120px,那么可以写 w-30,下面是换算原理:
- * tailwindcss中单位用的是rem, 默认根字号为16px,也就是在tailwindcss中,0.25rem = 4px,
- * 而对于类名 w-1 来说,这里的1就是0.25rem,即4px,那么w-1就是4px,w-2就是8px,以此类推
- * 
- * */
-html {
-  font-size: clamp(10px, 4.1025641vw, 40px);
-}
+@custom-variant dark (&:is(.dark *));
 
-/* GLOBAL VARIABLES :root中定义常规变量,不被映射到工具类*/
-:root {
-  /* Font families */
-  font-family: var(--font-outfit-variable), system-ui, sans-serif;
-  --background: #ffffff;
-  --foreground: #000;
-  --color-selected-black: #00000099;
-
-  --hero-background: #ffffff;
-  --hero-foreground: #000;
-  --dark-grey: #404040;
-  --selected-color: #e3ecff;
-  --selected-color-dark: #555b69;
-  --selected-bottom-dark: #95b6ff;
-  --selected-bg-bottom-dark: #314779;
-  --input-color: #b3b3b3;
-
-  /* 字体尺寸 */
-  --font-size-xxs: 13px;
-
-  /* 间距变量(供自定义类使用) */
-  --space-30: 120px;
-  --space-7_5: 30px;
-  --space-92_5: 23.125rem;
-}
 
-/*theme中定义的变量可以被映射到实用程序类(需要注意映射规则,不是所有变量都能映射)*/
+/* =========================================================
+ * Tailwind theme tokens
+ *
+ * @theme 中的变量会映射到 Tailwind utilities。
+ *
+ * 例如:
+ * --color-ly-gray
+ *   → text-ly-gray
+ *   → bg-ly-gray
+ *   → border-ly-gray
+ *
+ * --text-ly-16
+ *   → text-ly-16
+ *
+ * --leading-ly-24
+ *   → leading-ly-24
+ * ======================================================= */
+
 @theme {
   /*自定义颜色*/
   --color-ly-inputborder: #e5e5e5;
   --color-ly-placeholder: #A6A6A6;
   --color-ly-errorcolor: #FF0000;
+
   --color-ly-lightgray: #f9f9f9;
   --color-ly-middlegray: #f2f2f2;
-  --color-ly-gray: #d3d3d3; /*可被映射为text-ly-gray,bg-ly-gray等相关的颜色类目*/
+  --color-ly-gray: #d3d3d3;
   --color-ly-deepgray: #ACACAC;
+
   --color-ly-gold: #ffd700;
-  --color-ly-gold: #ffd700;
+
   --color-ly-green: #01754a;
   --color-ly-middlegreen: #036141;
   --color-ly-deepgreen: #1e3932;
+
+
   /*自定义字体大小 前缀 ly*/
   --text-ly-12: 0.75rem;
   --text-ly-13: 0.8125rem;
@@ -92,9 +88,9 @@ html {
   --text-ly-20: 1.25rem;
   --text-ly-22: 1.375rem;
   --text-ly-24: 1.5rem;
-  --text-ly-30: 1.875rem; /* 1.875rem (30px) */ 
-	--text-ly-36: 2.25rem; /* 2.25rem (36px) */ 	
-  --text-ly-48: 3rem; /* 3rem (48px) */ 
+  --text-ly-30: 1.875rem;
+  --text-ly-36: 2.25rem;
+  --text-ly-48: 3rem;
 
   /*自定义行高 前缀 ly*/
   --leading-ly-16: 1rem;
@@ -105,82 +101,156 @@ html {
   --leading-ly-26: 1.625rem;
   --leading-ly-28: 1.75rem;
   --leading-ly-36: 2.25rem;
-}
 
+}
 
-.dark {
-  --background: #171717;
-  --foreground: #ffffff;
 
-  --hero-background: #171717;
-  --hero-foreground: #ffffff;
-}
+/* =========================================================
+ * Runtime variables -> Tailwind theme
+ *
+ * inline 用于引用其它 CSS variables。
+ * @theme指令中的 inline 表示定义引用其他变量的主题变量, 比如 定义了一个变量--color-background,--color-background引用的是另一个变量
+ * 例如:
+ *
+ * --background
+ *      ↓
+ * --color-background
+ *      ↓
+ * bg-background
+ *
+ * 当 .dark 修改 --background 后,
+ * bg-background 会自动跟随,不需要额外写:
+ *
+ * bg-white dark:bg-background
+ * ======================================================= */
 
-/*@theme指令中的 inline 表示定义引用其他变量的主题变量
-  比如 定义了一个变量--color-background,--color-background引用的是另一个变量
-*/
 @theme inline {
-  --font-outfit: var(--font-outfit-variable), system-ui, sans-serif;
+  --font-outfit:
+    var(--font-outfit-variable),
+    system-ui,
+    sans-serif;
   --color-background: var(--background);
   --color-foreground: var(--foreground);
-  --color-font-color: var(--font-color);
-  --color-selected-black: var(--color-selected-black);
+
+  --color-selected-black: var(--selected-black);
 
   --color-dark-grey: var(--dark-grey);
+
   --color-selected-color: var(--selected-color);
   --color-selected-color-dark: var(--selected-color-dark);
+
   --color-selected-bottom-dark: var(--selected-bottom-dark);
   --color-selected-bg-bottom-dark: var(--selected-bg-bottom-dark);
-  --color-input-color: var(--input-color);
+
+  --color-input-color: var(--input-color); /*placeholder:text-input-color 设置placeholder颜色*/
 }
 
-body {
-  background: var(--background);
-  color: var(--foreground);
+
+/* =========================================================
+ * Base
+ *
+ * 只放:
+ * - 全局 CSS variables
+ * - html/body
+ * - document-level defaults
+ *
+ * 不放具体业务组件样式。
+ * ======================================================= */
+
+@layer base {
+  :root {
+    /* GLOBAL VARIABLES :root中定义常规变量,不被映射到工具类*/
+
+    --background: #ffffff;
+    --foreground: #000000;
+
+    --selected-black: rgb(0 0 0 / 60%);
+
+    --hero-background: #ffffff;
+    --hero-foreground: #000000;
+
+    --dark-grey: #404040;
+
+    --selected-color: #e3ecff;
+    --selected-color-dark: #555b69;
+
+    --selected-bottom-dark: #95b6ff;
+    --selected-bg-bottom-dark: #314779;
+
+    --input-color: #b3b3b3;
+  }
+
+
+  /* ---------- Dark theme ---------- */
+
+  .dark {
+    --background: #171717;
+    --foreground: #ffffff;
+
+    --hero-background: #171717;
+    --hero-foreground: #ffffff;
+  }
+
+
+  /* ---------- Document ---------- */
+
+  html {
+    /*--font-outfit-variable 是在根layout.tsx中定义的*/
+    font-family:
+      var(--font-outfit-variable),
+      system-ui,
+      sans-serif;
+
+    /*
+     * PC 项目推荐保留稳定 scrollbar gutter,
+     * modal / drawer 锁定 body 时减少页面左右跳动。
+     */
+    scrollbar-gutter: stable;
+
+    scrollbar-width: thin;
+    scrollbar-color: #6b7280 transparent;
+  
+    /**
+      * 移动端适配核心:clamp() 根字号  在390px设计稿下 16px ≈ 4.1025641vw = 1rem
+      * 比如 390px设计稿,有一个div宽度是120px,那么可以写 w-30,下面是换算原理:
+      * tailwindcss中单位用的是rem, 默认根字号为16px,也就是在tailwindcss中,0.25rem = 4px,
+      * 而对于类名 w-1 来说,这里的1就是0.25rem,即4px,那么w-1就是4px,w-2就是8px,以此类推
+      * 
+      * */
+
+    font-size: clamp(10px, 4.1025641vw, 40px);
+  }
+
+  body {
+    background-color: var(--background);
+    color: var(--foreground);
+  }
 }
 
+
+/* =========================================================
+ * Components
+ *
+ * 放项目自己的复合样式和第三方组件 overrides。
+ *
+ * vendor.swiper 在 components 之前,因此这里的
+ * Swiper override 不需要通过 !important 去抢优先级。
+ * ======================================================= */
+
 @layer components {
   /* Hide scrollbar for drawer content */
   .drawer-scrollbar-hidden {
     scrollbar-width: none;
-    -ms-overflow-style: none;
   }
   .drawer-scrollbar-hidden::-webkit-scrollbar {
     display: none;
   }
 
-  .placeholder-input-color::placeholder {
-    color: var(--input-color);
-  }
-
-  .dark .placeholder-selected-color-dark::placeholder {
-    color: var(--selected-color-dark);
-  }
 
   .mobile-heading h2 {
-    @apply sticky bg-white z-50 dark:bg-background top-0;
-  }
-
-  .prose h1 {
-    margin: 12px 0 !important;
-    font-family: Outfit, sans-serif !important;
-    font-size: 24px !important;
-    font-weight: 600 !important;
-  }
-
-  .prose p {
-    color: var(--color-selected-black) !important;
-    font-weight: 300 !important;
-  }
-
-  .dark .prose p {
-    color: white !important;
-    font-weight: 300 !important;
+    @apply sticky z-50 bg-background top-0;
   }
 
-  .margin-t {
-    margin-top: -9px;
-  }
 
   @keyframes shimmer {
     0% {
@@ -213,7 +283,13 @@ body {
   }
   /*输入框样式*/
   .ly-input{
-    @apply block w-full box-border text-ly-12 px-4 leading-4 border-1 border-ly-inputborder h-11.5 placeholder:text-ly-placeholder focus:outline-none
+    @apply block w-full box-border text-ly-12 px-4 leading-4 border-1 border-ly-inputborder h-11.5 placeholder:text-ly-placeholder focus:outline-none;
+  }
+
+  .middle-image-swiper.swiper-horizontal > .swiper-pagination-progressbar, 
+  .middle-image-swiper .swiper-pagination-progressbar.swiper-pagination-horizontal{
+      bottom: -1rem ;
+      top: unset;
   }
 
   .fade-in{
@@ -267,9 +343,7 @@ body {
   .ly-vipplus-bg{
     background: linear-gradient(128.05deg, rgba(250, 231, 200, 1) 0%, rgba(249, 255, 235, 1) 27.29%, rgba(242, 215, 172, 1) 64.42%, rgba(232, 202, 153, 1) 100%);
   }
-}
-
-apple-pay-button {
+  apple-pay-button {
     --apple-pay-button-box-sizing: border-box;
     --apple-pay-button-width: 300px;
     --apple-pay-button-height: 48px;
@@ -277,4 +351,6 @@ apple-pay-button {
     --apple-pay-button-padding: 6px 0px;
     display: block;
     width: 100%;
-}
+  }
+}
+

+ 1 - 1
src/app/layout.tsx

@@ -38,7 +38,7 @@ const __srOnly: React.CSSProperties = {
 export const viewport: Viewport = {
   width: 'device-width',
   initialScale: 1,
-  maximumScale: 2,
+  maximumScale: 1,
   // userScalable: false,
   viewportFit: "cover"
   // Also supported but less commonly used

+ 0 - 305
src/components/cart/CartModal.tsx

@@ -1,305 +0,0 @@
-"use client";
-import clsx from "clsx";
-import { useDisclosure } from "@heroui/use-disclosure";
-import { AnimatePresence, motion } from "framer-motion";
-import { ShoppingCartIcon } from "@heroicons/react/24/outline";
-import { DEFAULT_OPTION } from "@/utils/constants";
-import OpenCart from "./OpenCart";
-import { Price } from "../theme/ui/Price";
-import { DeleteItemButton } from "../common/icons/cart/DeleteItemButton";
-import { EditItemQuantityButton } from "../common/icons/cart/EditItemQuantityButton";
-import { useAppSelector } from "@/store/hooks";
-import Image from "next/image";
-import { NOT_IMAGE } from "@utils/constants";
-import LoadingDots from "@components/common/icons/LoadingDots";
-import { useFormStatus } from "react-dom";
-import { redirectToCheckout } from "@/utils/actions";
-import Link from "next/link";
-import { createUrl, safeParse } from "@utils/helper";
-import { useBodyScrollLock } from "@utils/hooks/useBodyScrollLock";
-import { useConfig } from "@utils/hooks/useConfig";
-
-
-type MerchandiseSearchParams = {
-  [key: string]: string;
-};
-export default function CartModal({
-  children,
-  className,
-  onOpen,
-  onClose,
-  isOpen,
-}: {
-  children?: React.ReactNode;
-  className?: string;
-  onOpen?: () => void;
-  onClose?: () => void;
-  isOpen?: boolean;
-}) {
-  const {store} = useConfig();
-  const {
-    isOpen: internalIsOpen,
-    onOpen: internalOnOpen,
-    onClose: internalOnClose,
-  } = useDisclosure();
-
-  const isControlled = isOpen !== undefined;
-  const finalIsOpen = isControlled ? isOpen : internalIsOpen;
-  const finalOnOpen = isControlled ? onOpen : internalOnOpen;
-  const finalOnClose = isControlled ? onClose : internalOnClose;
-
-  // const { isLoading, cartData: cartDetail } = useCartDetail();
-  const cartState= useAppSelector((state) => state.cartDetail);
-  const {cart:cartDetail, loading: isLoading} = cartState;
-  const cart = Array.isArray(cartDetail?.items?.edges)
-    ? cartDetail?.items?.edges
-    : [];
-
- 
-
-  useBodyScrollLock(finalIsOpen);
-
-  // const handleOpenChange = (open: boolean) => {
-  //   if (!open) {
-  //     finalOnClose?.();
-  //   }
-  // };
-
-  return (
-    <>
-      <button
-        type="button"
-        aria-label="Open cart"
-        className={clsx(
-          className,
-          isLoading ? "cursor-wait" : "cursor-pointer",
-        )}
-        disabled={isLoading}
-        onClick={finalOnOpen}
-      >
-        {children ? (
-          children
-        ) : (
-          <OpenCart quantity={cartDetail?.itemsQty} />
-        )}
-      </button>
-
-      
-        <AnimatePresence>
-          {finalIsOpen && (
-            <>
-              <motion.div
-                initial={{ opacity: 0 }}
-                animate={{ opacity: 1 }}
-                exit={{ opacity: 0 }}
-                onClick={finalOnClose}
-                className="fixed inset-0 z-40 bg-transparent lg:hidden transition-opacity"
-                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 bg-white dark:bg-black w-full max-w-[448px] border-l border-neutral-200 dark:border-neutral-800 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 p-4">
-                  <div className="flex items-center justify-between">
-                    <p
-                      className={clsx(
-                        "font-semibold",
-                        "text-xl",
-                      )}
-                    >
-                      My Cart
-                    </p>
-                 
-                      <button
-                        aria-label="Close cart"
-                        className="cursor-pointer"
-                        onClick={finalOnClose}
-                      >
-                        <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={clsx(
-                    "flex-1 overflow-y-auto px-4 py-0 drawer-scrollbar-hidden",
-                    "!px-2",
-                  )}
-                >
-                  {cart?.length === 0 ? (
-                    <div className="mt-20 flex w-full flex-col items-center justify-center overflow-hidden">
-                      <ShoppingCartIcon className="h-16" />
-                      <p className="mt-6 text-center text-2xl font-bold">
-                        Your cart is empty.
-                      </p>
-                    </div>
-                  ) : (
-                    <div className="flex h-full flex-col justify-between">
-                      <ul className="my-0 flex-grow overflow-auto py-0">
-                        {Array.isArray(cart) &&
-                          cart?.map((item: any, i: number) => {
-                            const merchandiseSearchParams =
-                              {} as MerchandiseSearchParams;
-                            const merchandiseUrl = createUrl(
-                              `/product/${item?.node.productUrlKey}`,
-                              new URLSearchParams(merchandiseSearchParams),
-                            );
-                            const baseImage: any = safeParse(
-                              item?.node?.baseImage,
-                            );
-
-                            return (
-                              <li key={i} className="flex w-full flex-col">
-                                <div
-                                  className={clsx(
-                                    "flex w-full flex-row justify-between py-4 px-1",
-                                    "gap-1 xxs:gap-3",
-                                  )}
-                                >
-                                  <Link
-                                    className="z-30 flex flex-row space-x-4"
-                                    aria-label={`${item?.node?.name}`}
-                                    href={merchandiseUrl}
-                                    onClick={finalOnClose}
-                                  >
-                                    <div className="relative h-16 w-16 cursor-pointer overflow-hidden rounded-md border border-neutral-300 bg-neutral-300 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800">
-                                      <Image
-                                        alt={
-                                          item?.node?.baseImage ||
-                                          item?.product?.name
-                                        }
-                                        className="h-full w-full object-cover"
-                                        height={64}
-                                        src={baseImage?.small_image_url || ""}
-                                        width={74}
-                                        onError={(e) =>
-                                          (e.currentTarget.src = NOT_IMAGE)
-                                        }
-                                      />
-                                    </div>
-                                    <div className="flex flex-1 flex-col text-base">
-                                      <span className="line-clamp-1 font-outfit text-base font-medium">
-                                        {item?.node?.name}
-                                      </span>
-                                      {item.name !== DEFAULT_OPTION && (
-                                        <p className="text-sm lowercase line-clamp-1 text-black dark:text-neutral-400">
-                                          {item?.node?.sku}
-                                        </p>
-                                      )}
-                                    </div>
-                                  </Link>
-                                  <div className="flex h-16 flex-col justify-between">
-                                    <Price
-                                      amount={item?.node?.price}
-                                      className="flex justify-end space-y-2 text-right font-outfit text-base font-medium"
-                                      currencyCode={store.currentCurrency}
-                                    />
-                                    <div className="flex items-center gap-x-2">
-                                      <DeleteItemButton item={item} />
-                                      <div className="ml-auto flex h-9 flex-row items-center rounded-full border border-neutral-200 dark:border-neutral-700">
-                                        <EditItemQuantityButton
-                                          item={item}
-                                          type="minus"
-                                        />
-                                        <p className="w-6 text-center">
-                                          <span className="w-full text-sm">
-                                            {item?.node?.quantity}
-                                          </span>
-                                        </p>
-                                        <EditItemQuantityButton
-                                          item={item}
-                                          type="plus"
-                                        />
-                                      </div>
-                                    </div>
-                                  </div>
-                                </div>
-                              </li>
-                            );
-                          })}
-                      </ul>
-
-                      <div className="border-0 border-t border-solid border-neutral-200 dark:border-dark-grey py-4 text-sm text-neutral-500 dark:text-neutral-400">
-                        {(cartDetail as any)?.taxAmount > 0 && (
-                          <div className="mb-3 flex items-center justify-between">
-                            <p className="text-base font-normal text-black/[60%] dark:text-white">
-                              Taxes
-                            </p>
-                            <Price
-                              amount={(cartDetail as any)?.taxAmount}
-                              className="text-right text-base font-medium text-black dark:text-white"
-                              currencyCode={store.currentCurrency}
-                            />
-                          </div>
-                        )}
-                        <div className="mb-3 flex items-center justify-between pb-1">
-                          <p className="text-base font-normal text-black/[60%] dark:text-white">
-                            Total
-                          </p>
-                          <Price
-                            amount={(cartDetail as any)?.grandTotal}
-                            className="text-right text-base font-medium text-black dark:text-white"
-                            currencyCode={store.currentCurrency}
-                          />
-                        </div>
-
-                        <form action={redirectToCheckout}>
-                          <CheckoutButton />
-                        </form>
-                      </div>
-                    </div>
-                  )}
-                </div>
-
-                <div className="p-4" />
-              </motion.div>
-            </>
-          )}
-        </AnimatePresence>
-      
-    </>
-  );
-}
-
-function CheckoutButton() {
-  const { pending } = useFormStatus();
-
-
-  return (
-    <>
-      <input
-        name="url"
-        type="hidden"
-        value="/checkout"
-      />
-      <button
-        className={clsx(
-          "block w-full rounded-full bg-blue-600 p-3 text-center text-sm font-medium text-white opacity-90 hover:opacity-100",
-          pending ? "cursor-wait" : "cursor-pointer",
-        )}
-        disabled={pending}
-        type="submit"
-      >
-        {pending ? <LoadingDots className="bg-white" /> : "Proceed to Checkout"}
-      </button>
-    </>
-  );
-}

+ 0 - 23
src/components/cart/OpenCart.tsx

@@ -1,23 +0,0 @@
-import { ShoppingCartIcon } from "@heroicons/react/24/outline";
-import clsx from "clsx";
-
-export default function OpenCart({
-  className,
-  quantity,
-}: {
-  className?: string;
-  quantity?: number | string;
-}) {
-  return (
-    <div className="relative flex  items-center justify-center rounded-md w-6 h-6 border-0 lg:border border-solid border-neutral-200 dark:border-neutral-700 lg:h-11 lg:w-11">
-      <ShoppingCartIcon className={clsx("h-5 w-5", className)} />
-{/* -mr-2  */}
-      {quantity ? (
-        <div className="absolute right-0 bottom-0 margin-t lg:-mt-2 w-3 h-3  rounded-full bg-[#FF4500FF]  text-[11px] text-center leading-3 font-medium text-white">
-          {quantity}
-        </div>
-      ) : null}
-    </div>
-  );
-}
-

+ 0 - 21
src/components/cart/index.tsx

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

+ 112 - 93
src/components/common/AddToCartModal/AddToCartModal.tsx

@@ -2,6 +2,8 @@
 
 import { useState, useMemo } from "react";
 import { useRouter } from "next/navigation";
+import { Swiper, SwiperSlide } from 'swiper/react';
+import Big from 'big.js';
 import { useAppDispatch, useAppSelector } from "@/store/hooks";
 import { useCustomToast } from "@utils/hooks/useToast";
 import { 
@@ -9,21 +11,15 @@ 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,6 +34,8 @@ 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 { appendProductToCart } = useAddProduct();
@@ -147,11 +145,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 +200,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 +208,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 +219,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 +235,104 @@ 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}
-                                    />
-                                </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 
-                                        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"
-                                        amount={String(currentVariantInfo.totalNowPrice)} 
-                                        currencyCode="USD"
-                                    />
-                                </div>
-                            </div>
-                            <div>
-                                <FooterBtnInAddToCartModal 
-                                    isAvailable={isCurrentSelectionAvailable} 
-                                    onAddToCart={()=> addToCartHandler(onClose)}
-                                    onBuyNow={()=> buyNowHandler(onClose)}
-                                />
-                            </div>
-                        </DrawerFooter>
-                    </>
+
+    return (<>
+        <CommonModal 
+            isOpen={isOpen}
+            onClose={closeModal}
+            fullyCustomer={true}
+            contentClassName="absolute box-border transition-transform duration-300 ease-out bottom-0 left-0 w-full p-4 bg-white rounded-t-xl"
+            body={<>
+
+                <div className="flex justify-between items-center text-ly-16">
+                    Select Options
+                    <button className="flex-none w-6 h-6" onClick={closeModal} 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>
+        
+                {images.length > 0 && (
+                    <div className="w-full mt-4">
+                    <Swiper
+                        spaceBetween={10}
+                        slidesPerView={3}
+                    >
+                        {images.map((img) => {
+                            return (
+                            <SwiperSlide key={img.id} className="w-24 h-32">
+                                <img className="block h-full w-full object-cover" src={img.publicPath} />
+                            </SwiperSlide>
+                            );
+                        })}
+                    </Swiper>
+                    </div>
                 )}
-                </DrawerContent>
-            </Drawer>
-    );
+                
+                <div className="box-border w-full mt-6 h-73.75 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-100">
+                    <div className="box-border w-full">
+                        <ProductOptionsInAddToCartModal 
+                            productOptions={productOptions}
+                            selected={selected}
+                            disabledMap={disabledMap}
+                            onOptionClick={handleOptionClick}
+                        />
+                    </div>
+                </div> 
+  
+                <div className="box-border w-full border-t-1 border-[#f2f2f2] mt-1.5 pt-3">
+                    <div className="flex justify-between items-center">
+                        <Price 
+                            className="text-ly-14 text-ly-gray line-through"
+                            amount={String(currentVariantInfo.totalLinePrice)} 
+                            currencyCode={currentCurrency.code}
+                        />
+                        <div className="flex items-center">
+                            <Price 
+                                className="text-ly-14 bg-ly-gold py-0.5 px-1"
+                                amount={String(currentVariantInfo.save)} 
+                                currencyCode={currentCurrency.code}
+                            />
+                            <Price 
+                                className="text-ly-20 font-bold ml-1.5"
+                                amount={String(currentVariantInfo.totalNowPrice)} 
+                                currencyCode={currentCurrency.code}
+                            />
+                        </div>
+                    </div>
+
+                    {isCurrentSelectionAvailable ? 
+                        <>
+                        <div className="flex justify-between gap-3 mt-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 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> */}
+                        </>
+                    :
+                        <p className="text-center text-ly-14">The selected options are not available!</p>
+
+                    }
+                    
+
+                </div>
+                    
+                
+            </>}
+        />
+    </>);
+
+
 }

+ 21 - 7
src/components/common/AddToCartModal/AddToCartModalWrapper.tsx

@@ -1,14 +1,28 @@
 "use client";
 
-// import { AddToCartModal } from "@components/common/AddToCartModal/AddToCartModal";
+import AddToCartModal from "./AddToCartModal";
 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);
+  if(product) {
+      return <AddToCartModal />;
+  } else {
+    return null;
+  }
+  
+}
+
+// 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} />
+//   );
+// }
+
 
-  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 - 27
src/components/common/AddToCartModal/ImgSwiperInAddToCartModal.tsx

@@ -1,27 +0,0 @@
-"use client";
-import { memo } from "react";
-import { Swiper, SwiperSlide } from 'swiper/react';
-import 'swiper/css';
-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 - 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}
-    />
-  );
-}

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

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

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

@@ -27,7 +27,7 @@ export default function Portal({ children }: { children: React.ReactNode }) {
 但是在react v19 中 认为mounted是一个「派生状态(derived state)」,
 完全可以在 render 时计算出来(使用document 来判断),不需要专门声明一个state在副作用里更改状态(引起二次渲染),所以eslint报错。
 */
-
+import { useEffect, useState } from 'react';
 import { createPortal } from 'react-dom';
 
 export default function PortalBox({
@@ -35,12 +35,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');
 

+ 1 - 1
src/components/customer/accountSwiper.tsx

@@ -18,7 +18,7 @@ interface ProductItem {
   rating: number;
   review_count: number;
 }
-// import 'swiper/css';
+
 // 模拟商品数据(和图片内容匹配)
 const productList:ProductItem[] = [
                     {

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

@@ -2,7 +2,6 @@
 
 import { useRouter } from 'next/navigation';
 import { Swiper, SwiperSlide } from 'swiper/react';
-import 'swiper/css';
 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";

+ 6 - 6
src/components/layout/footer/FooterMenu.tsx

@@ -19,11 +19,11 @@ const getUrlparams = (url: string) => {
 
 const FooterMenuItem = ({ item }: { item: ThemeOptions }) => {
   return (
-    <li className="text-selected-black dark:text-neutral-300">
+    <li className="text-selected-black">
       <Link
         aria-label={`${item?.title}`}
         title={`${item?.title}`}
-        className="block px-0 py-1 md:p-2 text-nowrap text-sm underline-offset-4 text-selected-black dark:text-neutral-300 hover:text-black hover:underline md:inline-block dark:hover:text-neutral-300"
+        className="block px-0 py-1 text-nowrap text-sm underline-offset-4 text-selected-black"
         href={getUrlparams(item.url)}
       >
         {item.title}
@@ -46,10 +46,10 @@ export default function FooterMenu({
     : firstTranslation?.options;
 
   return (
-    <div className="flex justify-between gap-x-8 lg:gap-x-[50px]">
+    <div className="">
       {/* Render columns 1 */}
       {isArray(channels?.column_1) ? (
-        <nav className="w-full lg:min-w-[160px] xl:min-w-[200px]">
+        <nav className="w-full">
           <ul>
             {channels.column_1.map((item: ThemeOptions, index: number) => {
               return <FooterMenuItem key={index} item={item} />;
@@ -60,7 +60,7 @@ export default function FooterMenu({
 
       {/* Render columns 2 */}
       {isArray(channels?.column_2) ? (
-        <nav className="w-full lg:min-w-[160px] xl:min-w-[200px]">
+        <nav className="w-full">
           <ul>
             {channels.column_2.map((item: ThemeOptions, index: number) => {
               return <FooterMenuItem key={index} item={item} />;
@@ -71,7 +71,7 @@ export default function FooterMenu({
 
       {/* Render columns 3 */}
       {isArray(channels?.column_3) ? (
-        <nav className="w-full lg:min-w-[160px] xl:min-w-[200px]">
+        <nav className="w-full">
           <ul>
             {channels.column_3.map((item: ThemeOptions, index: number) => {
               return <FooterMenuItem key={index} item={item} />;

+ 4 - 4
src/components/layout/footer/index.tsx

@@ -52,11 +52,11 @@ export default async function Footer() {
 
   return (
     <>
-      <footer className="border-t border-neutral-200 text-sm text-neutral-500 dark:border-neutral-700 dark:text-neutral-400">
-        <div className="mx-auto flex w-full max-w-screen-2xl flex-col justify-between gap-6 gap-y-6 px-6 py-12 text-sm dark:border-neutral-700 min-[880px]:flex-row min-[880px]:gap-12 min-[880px]:gap-y-20 min-[880px]:px-4">
+      <footer className="border-t border-neutral-200 text-sm text-neutral-500">
+        <div className="mx-auto flex w-full flex-col justify-between gap-6 gap-y-6 px-6 py-12 text-sm">
           <div className="flex flex-col gap-[14px]">
             <Link
-              className="flex items-center gap-2 md:pt-1 cursor-pointer"
+              className="flex items-center gap-2 cursor-pointer"
               href="/"
               aria-label="Go to homepage"
               title="Go to homepage"
@@ -97,7 +97,7 @@ export default async function Footer() {
               </Link>
             </div>
           </div>
-          <div className="flex flex-col gap-x-8 md:flex-row lg:gap-x-[50px]">
+          <div className="flex flex-col gap-x-8">
             <Suspense
               fallback={
                 <div className="flex h-[188px] w-[200px] flex-col gap-2">

+ 1 - 44
src/components/layout/navbar/MobileSearch.tsx

@@ -1,50 +1,8 @@
 "use client";
-import clsx from "clsx";
-import { Suspense, useState } from "react";
+import { Suspense } from "react";
 import Search from "./Search";
-import OpenCart from "@/components/cart/OpenCart";
 import { SearchSkeleton } from "@/components/common/skeleton/SearchSkeleton";
 
-const MobileSearch = () => {
-  const [search, setSearch] = useState<boolean>(false);
-  return (
-    <>
-      <Suspense fallback={<OpenCart />}>
-        <button
-          onClick={() => setSearch(!search)}
-          aria-label="Open cart"
-          className="size-9 lg:size-11 flex cursor-pointer items-center justify-center rounded-sm border border-solid border-neutral-200 dark:border-neutral-700 md:hidden"
-        >
-          <svg
-            xmlns="http://www.w3.org/2000/svg"
-            fill="none"
-            viewBox="0 0 24 24"
-            strokeWidth="1.5"
-            stroke="currentColor"
-            className="size-5"
-          >
-            <path
-              strokeLinecap="round"
-              strokeLinejoin="round"
-              d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
-            />
-          </svg>
-        </button>
-      </Suspense>
-      <div
-        className={clsx(
-          "absolute z-50 left-0 right-0 top-0 flex w-full justify-center bg-neutral-50 dark:bg-neutral-900 p-[15px] transition-all duration-300 md:hidden ",
-          search ? "translate-y-0 opacity-100" : " -translate-y-full opacity-0"
-        )}
-      >
-        <Suspense fallback={<SearchSkeleton />}>
-          <Search search={search} setSearch={setSearch} />
-        </Suspense>
-      </div>
-    </>
-  );
-};
-
 
 
 export const MobileSearchBar = ({
@@ -63,4 +21,3 @@ export const MobileSearchBar = ({
   );
 };
 
-export default MobileSearch;

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
src/components/layout/navbar/NavbarActions.tsx


+ 44 - 41
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,36 +89,24 @@ 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) => {
         e.stopPropagation();
@@ -148,6 +140,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,33 +151,43 @@ 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">
-                        
-                        {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">
+                            
+                            {body}
+                            
+                        </div>
 
-                    <div ref={footerRef} className={clsx("w-full box-border absolute bottom-0 left-0",{
-                        "border-t border-gray-200": footer !==null && footer !== undefined
-                    })}>
-                        {footer && footer}
-                    </div>
+                        <div ref={footerRef} className={clsx("w-full box-border absolute bottom-0 left-0",{
+                            "border-t border-gray-200": footer !==null && footer !== undefined
+                        })}>
+                            {footer && footer}
+                        </div>
+                        </>
+                    :
+                        body
+                
+                    }
+
+                    
                 </div>
                 
             </div>