Sfoglia il codice sorgente

Merge branch 'zzf-account' into dev

zhangzf 2 giorni fa
parent
commit
840df7bd26
56 ha cambiato i file con 7092 aggiunte e 691 eliminazioni
  1. 48 0
      src/app/(public)/_components/CategoryTabSwiper .tsx
  2. 276 0
      src/app/(public)/_components/HomeBestSellers.tsx
  3. 120 0
      src/app/(public)/_components/HomeContent.tsx
  4. 75 0
      src/app/(public)/_components/HomeMiddleBanner.tsx
  5. 276 0
      src/app/(public)/_components/HomeNewArrival.tsx
  6. 148 0
      src/app/(public)/_components/JinGangSwiper.tsx
  7. 163 0
      src/app/(public)/_components/MiddleImageSwiper.tsx
  8. 263 0
      src/app/(public)/_components/RecommendedProducts.tsx
  9. 43 0
      src/app/(public)/_components/TopImageSwiper.tsx
  10. 317 0
      src/app/(public)/_components/Trending.tsx
  11. 5 0
      src/app/(public)/_components/css/swiper-custom.css
  12. 74 0
      src/app/(public)/customer/account/_components/AccountBottomHrefSwiper.tsx
  13. 158 0
      src/app/(public)/customer/account/_components/AccountHeader.tsx
  14. 242 0
      src/app/(public)/customer/account/_components/AccountVipPopup.tsx
  15. 112 0
      src/app/(public)/customer/account/_components/HelpModal.tsx
  16. 183 0
      src/app/(public)/customer/account/mypoints/_components/GetPoints.tsx
  17. 28 0
      src/app/(public)/customer/account/mypoints/_components/PointsRule.tsx
  18. 16 71
      src/app/(public)/customer/account/mypoints/page.tsx
  19. 14 129
      src/app/(public)/customer/account/page.tsx
  20. 104 221
      src/app/(public)/customer/account/pointlist/page.tsx
  21. 58 0
      src/app/(public)/customer/address/_components/DefaultAddress.tsx
  22. 304 0
      src/app/(public)/customer/address/eidt/[id]/page.tsx
  23. 99 0
      src/app/(public)/customer/address/new/_components/CustomerAddressPhone.tsx
  24. 222 0
      src/app/(public)/customer/address/new/_components/NewAddressDefault.tsx
  25. 1286 169
      src/app/(public)/customer/address/new/page.tsx
  26. 13 8
      src/app/(public)/customer/address/page.tsx
  27. 302 0
      src/app/(public)/customer/order/history/_components/OrderHistory.tsx
  28. 10 0
      src/app/(public)/customer/order/history/page.tsx
  29. 47 0
      src/app/(public)/customer/order/track/order_id/[id]/_components/TrackDetail.tsx
  30. 74 0
      src/app/(public)/customer/order/track/order_id/[id]/page.tsx
  31. 30 0
      src/app/(public)/customer/order/view/order_id/[id]/_components/OrderHeader.tsx
  32. 50 0
      src/app/(public)/customer/order/view/order_id/[id]/_components/OrderInfoBlock.tsx
  33. 39 0
      src/app/(public)/customer/order/view/order_id/[id]/_components/PriceSummary.tsx
  34. 40 0
      src/app/(public)/customer/order/view/order_id/[id]/_components/ProductList.tsx
  35. 93 0
      src/app/(public)/customer/order/view/order_id/[id]/page.tsx
  36. 207 0
      src/app/(public)/hair-care-tips/page.tsx
  37. 688 0
      src/app/(public)/help-faqs/_components/Faqs.tsx
  38. 10 0
      src/app/(public)/help-faqs/page.tsx
  39. 12 10
      src/app/(public)/page.tsx
  40. 76 0
      src/app/(public)/track_order/_components/TrackFaqs.tsx
  41. 32 0
      src/app/(public)/track_order/_components/TrackOrder.tsx
  42. 47 0
      src/app/(public)/track_order/page.tsx
  43. 50 0
      src/app/api/customer/reward-points/browse-product/route.ts
  44. 40 0
      src/app/api/customer/reward-points/follow-status/route.ts
  45. 62 0
      src/app/api/customer/reward-points/follow/route.ts
  46. 61 0
      src/app/api/customer/reward-points/history/route.ts
  47. 193 0
      src/app/api/customer/token/address/route.ts
  48. 40 0
      src/app/api/shop/customer-address-gets/route.ts
  49. 41 0
      src/app/api/shop/customer-addresses/route.ts
  50. 10 4
      src/components/common/AddToCartModal/OpenAddToCartModalButton.tsx
  51. 16 5
      src/components/common/icons/ShoppingCartIcon.tsx
  52. 127 68
      src/components/customer/SettingModal.tsx
  53. 4 2
      src/components/customer/accountSwiper.tsx
  54. 40 0
      src/components/head/HeadBack.tsx
  55. 1 1
      src/types/checkout/type.ts
  56. 3 3
      src/utils/bagisto/index.ts

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

@@ -0,0 +1,48 @@
+"use client";
+import React from "react";
+import Link from "next/link";
+import { Swiper, SwiperSlide } from "swiper/react";
+import { FreeMode } from "swiper/modules";
+import "swiper/css";
+
+// 标签数据,可自由增删修改文字、跳转路由
+const tabList = [
+  { label: "HD Lace Wigs", href: "/category/hd-lace-wigs" },
+  { label: "New Arrival", href: "/category/new-arrival" },
+  { label: "Wigs", href: "/category/wigs" },
+  { label: "Colored Wigs", href: "/category/colored-wigs" },
+  { label: "Body Wave", href: "/category/body-wave" },
+  { label: "Curly Hair", href: "/category/curly-hair" },
+];
+
+const CategoryTabSwiper = () => {
+  return (
+    <div className="py-4">
+      <Swiper
+        modules={[FreeMode]}
+        freeMode={true}
+        slidesOffsetBefore={16}
+        slidesOffsetAfter={0}
+        spaceBetween={8}
+        slidesPerView="auto"
+        pagination={false}
+        navigation={false}
+       
+        className="w-full overflow-hidden"
+      >
+        {tabList.map((tab, idx) => (
+          <SwiperSlide key={idx} className="!w-auto">
+            <Link
+              href={tab.href}
+              className="inline-block px-2 border-2 border-black leading-6.5 rounded-sm bg-[#ecfdf5] text-xs text-center font-normal "
+            >
+              {tab.label}
+            </Link>
+          </SwiperSlide>
+        ))}
+      </Swiper>
+    </div>
+  );
+};
+
+export default CategoryTabSwiper;

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

@@ -0,0 +1,276 @@
+import React from "react";
+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 = [
+  // 第一块(6个)
+  {
+    id: 1,
+    img: "https://picsum.photos/400/500?random=1",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 2,
+    img: "https://picsum.photos/400/500?random=2",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 3,
+    img: "https://picsum.photos/400/500?random=3",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 4,
+    img: "https://picsum.photos/400/500?random=4",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 5,
+    img: "https://picsum.photos/400/500?random=5",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 6,
+    img: "https://picsum.photos/400/500?random=6",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+
+  // 第二块(6个)
+  {
+    id: 7,
+    img: "https://picsum.photos/400/500?random=7",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 8,
+    img: "https://picsum.photos/400/500?random=8",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 9,
+    img: "https://picsum.photos/400/500?random=9",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 10,
+    img: "https://picsum.photos/400/500?random=10",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 11,
+    img: "https://picsum.photos/400/500?random=11",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 12,
+    img: "https://picsum.photos/400/500?random=12",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+];
+
+// 拆分数据为2个块(每块6个)
+const productBlocks = [
+  productData.slice(0, 6), // 第一块
+  productData.slice(6, 12), // 第二块
+];
+
+const HomeBestSellers = () => {
+  return (
+    <div className="w-full max-w-[1200px] mx-auto px-4 py-8">
+      {/* 标题 */}
+      <h2 className="text-2xl font-bold mb-6">Best Sellers</h2>
+
+      {/* Swiper容器 */}
+      <Swiper
+        slidesPerView={1}
+        spaceBetween={20}
+        modules={[Navigation, Pagination]}
+        pagination={{
+          el: ".custom-pagination",
+          clickable: true,
+          renderBullet: (index, className) => {
+            // 所有分页默认底色为 #E5E5E5
+            return `<span class="${className} w-6 h-[5px] rounded-full mx-1 bg-[#E5E5E5]"></span>`;
+          },
+          // 激活时强制覆盖底色,加 !important 提高权重
+          bulletActiveClass: "!bg-[#333]",
+        }}
+        className="relative"
+      >
+        {/* 渲染2个滑动块 */}
+        {productBlocks.map((block, blockIndex) => (
+          <SwiperSlide key={blockIndex} className="!px-0">
+            {/* 每个块:6个产品,2列布局 */}
+            <div className="grid grid-cols-2 gap-4">
+              {block.map((product) => (
+                <div key={product.id} className="flex flex-col gap-2">
+                  {/* 产品图片 */}
+                  <img
+                    src={product.img}
+                    alt={product.title}
+                    className="w-full h-auto object-cover rounded-sm"
+                  />
+                  {/* 产品标题 */}
+                  <p className="text-sm text-gray-800 truncate">
+                    {product.title}
+                  </p>
+                  {/* 评分 + 评论数 */}
+                  <div className="flex items-center gap-1">
+                    <div className="flex">
+                      {/* 星级图标(简化版,可替换为真实星星图标) */}
+                      {[1, 2, 3, 4, 5].map((star) => (
+                        <svg
+                          key={star}
+                          className="w-4 h-4 text-yellow-400"
+                          fill="currentColor"
+                          viewBox="0 0 20 20"
+                        >
+                          <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
+                        </svg>
+                      ))}
+                    </div>
+                    <span className="text-xs text-gray-600">
+                      {product.reviewCount}
+                    </span>
+                  </div>
+                  {/* 价格 + 购物车图标 */}
+                  <div className="flex items-center justify-between">
+                    <div className="flex items-center gap-1">
+                      <span className="font-semibold">${product.price}</span>
+                      <span className="text-xs text-gray-500 line-through">
+                        ${product.originalPrice}
+                      </span>
+                    </div>
+                    {/* 购物车图标 */}
+                    <OpenAddToCartModalButton
+                      productUrlKey={
+                        "http://nshop.test/hd-transparent-deep-wave-&-curly-lace-frontal-closure-wig"
+                      }
+                      icon={
+                        <svg
+                          xmlns="http://www.w3.org/2000/svg"
+                          width="32"
+                          height="32"
+                          viewBox="0 0 32 32"
+                          fill="none"
+                        >
+                          <rect
+                            x="0"
+                            y="0"
+                            width="32"
+                            height="32"
+                            rx="8"
+                            fill="#036141"
+                          ></rect>
+                          <path
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                            d="M6.40039 7.79993L8.03372 7.79993L8.73372 10.5999M8.73372 10.5999L11.0671 19.9333L23.2004 19.9333L25.5337 10.5999L8.73372 10.5999Z"
+                          ></path>
+                          <ellipse
+                            cx="11.066040515899658"
+                            cy="23.199928283691406"
+                            rx="1.4000248908996582"
+                            ry="1.4000015258789062"
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                          ></ellipse>
+                          <ellipse
+                            cx="23.1998291015625"
+                            cy="23.199928283691406"
+                            rx="1.4000244140625"
+                            ry="1.4000015258789062"
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                          ></ellipse>
+                          <path
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                            d="M15.2656 15.2665L18.999 15.2665"
+                          ></path>
+                          <path
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                            d="M17.1348 17.1332L17.1348 13.3999"
+                          ></path>
+                        </svg>
+                      }
+                    />
+                  </div>
+                </div>
+              ))}
+            </div>
+          </SwiperSlide>
+        ))}
+
+        {/* 自定义分页器容器(底部小按钮) */}
+        <div className="custom-pagination flex justify-center mt-4"></div>
+      </Swiper>
+    </div>
+  );
+};
+
+export default HomeBestSellers;

File diff suppressed because it is too large
+ 120 - 0
src/app/(public)/_components/HomeContent.tsx


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

@@ -0,0 +1,75 @@
+"use client";
+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";
+export default function HomeMiddleBanner() {
+  const [activeIndex, setActiveIndex] = useState(0);
+
+  const [swiperRef] = useState(null);
+
+  const handleSlideChange = (swiper) => {
+    setActiveIndex(swiper.activeIndex);
+  };
+
+  const handleIndicatorClick = (index) => {
+    if (swiperRef) {
+      setActiveIndex(index);
+    }
+  };
+
+  return (
+    <div className="max-w-screen-md mx-auto p-4">
+      <Swiper
+        modules={[Navigation]}
+        spaceBetween={10}
+        slidesPerView={1}
+        onSlideChange={handleSlideChange}
+        className="mb-6"
+      >
+        <SwiperSlide>
+          <div className="relative w-full h-42">
+            <Image
+              src={
+                "https://cdn.asteriahair.com/media/wysiwyg/home/1750041184.png"
+              }
+              alt={"bbbb"}
+              fill
+              className="w-full h-auto object-cover rounded-md"
+            />
+          </div>
+        </SwiperSlide>
+        <SwiperSlide>
+          <div className="relative w-full h-42">
+            <Image
+              src={
+                "https://cdn.asteriahair.com/media/wysiwyg/home/1750041184.png"
+              }
+              alt={"bbbb"}
+              fill
+              className="w-full h-auto object-cover rounded-md"
+            />
+          </div>
+        </SwiperSlide>
+      </Swiper>
+      <div className="flex justify-center gap-2">
+        <button
+          onClick={() => handleIndicatorClick(0)}
+          className={`w-6 h-[5px] rounded-full  opacity-100 ${
+            activeIndex === 0 ? "bg-[#333]" : "bg-[#E5E5E5]"
+          }`}
+          aria-label="切换到第一块"
+        />
+        <button
+          onClick={() => handleIndicatorClick(1)}
+          className={`w-6 h-[5px] rounded-full  opacity-100 ${
+            activeIndex === 1 ? "bg-[#333]" : "bg-[#E5E5E5]"
+          }`}
+          aria-label="切换到第二块"
+        />
+      </div>
+    </div>
+  );
+}

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

@@ -0,0 +1,276 @@
+import React from "react";
+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 = [
+  // 第一块(6个)
+  {
+    id: 1,
+    img: "https://picsum.photos/400/500?random=1",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 2,
+    img: "https://picsum.photos/400/500?random=2",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 3,
+    img: "https://picsum.photos/400/500?random=3",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 4,
+    img: "https://picsum.photos/400/500?random=4",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 5,
+    img: "https://picsum.photos/400/500?random=5",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 6,
+    img: "https://picsum.photos/400/500?random=6",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+
+  // 第二块(6个)
+  {
+    id: 7,
+    img: "https://picsum.photos/400/500?random=7",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 8,
+    img: "https://picsum.photos/400/500?random=8",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 9,
+    img: "https://picsum.photos/400/500?random=9",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 10,
+    img: "https://picsum.photos/400/500?random=10",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 11,
+    img: "https://picsum.photos/400/500?random=11",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+  {
+    id: 12,
+    img: "https://picsum.photos/400/500?random=12",
+    title: "14-36 In Water Wave Huma...",
+    rating: 4.5,
+    reviewCount: 342,
+    price: 143.35,
+    originalPrice: 143.35,
+  },
+];
+
+// 拆分数据为2个块(每块6个)
+const productBlocks = [
+  productData.slice(0, 4), // 第一块
+  productData.slice(4, 8), // 第二块
+];
+
+const HomeNewArrival = () => {
+  return (
+    <div className="w-full max-w-[1200px] mx-auto px-4 py-8">
+      {/* 标题 */}
+      <h2 className="text-2xl font-bold mb-6">New Arrival</h2>
+
+      {/* Swiper容器 */}
+      <Swiper
+        slidesPerView={1}
+        spaceBetween={20}
+        modules={[Navigation, Pagination]}
+        pagination={{
+          el: ".custom-pagination",
+          clickable: true,
+          renderBullet: (index, className) => {
+            // 所有分页默认底色为 #E5E5E5
+            return `<span class="${className} w-6 h-[5px] rounded-full mx-1 bg-[#E5E5E5]"></span>`;
+          },
+          // 激活时强制覆盖底色,加 !important 提高权重
+          bulletActiveClass: "!bg-[#333]",
+        }}
+        className="relative"
+      >
+        {/* 渲染2个滑动块 */}
+        {productBlocks.map((block, blockIndex) => (
+          <SwiperSlide key={blockIndex} className="!px-0">
+            {/* 每个块:6个产品,2列布局 */}
+            <div className="grid grid-cols-2 gap-4">
+              {block.map((product) => (
+                <div key={product.id} className="flex flex-col gap-2">
+                  {/* 产品图片 */}
+                  <img
+                    src={product.img}
+                    alt={product.title}
+                    className="w-full h-auto object-cover rounded-sm"
+                  />
+                  {/* 产品标题 */}
+                  <p className="text-sm text-gray-800 truncate">
+                    {product.title}
+                  </p>
+                  {/* 评分 + 评论数 */}
+                  <div className="flex items-center gap-1">
+                    <div className="flex">
+                      {/* 星级图标(简化版,可替换为真实星星图标) */}
+                      {[1, 2, 3, 4, 5].map((star) => (
+                        <svg
+                          key={star}
+                          className="w-4 h-4 text-yellow-400"
+                          fill="currentColor"
+                          viewBox="0 0 20 20"
+                        >
+                          <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
+                        </svg>
+                      ))}
+                    </div>
+                    <span className="text-xs text-gray-600">
+                      {product.reviewCount}
+                    </span>
+                  </div>
+                  {/* 价格 + 购物车图标 */}
+                  <div className="flex items-center justify-between">
+                    <div className="flex items-center gap-1">
+                      <span className="font-semibold">${product.price}</span>
+                      <span className="text-xs text-gray-500 line-through">
+                        ${product.originalPrice}
+                      </span>
+                    </div>
+                    {/* 购物车图标 */}
+                    <OpenAddToCartModalButton
+                      productUrlKey={
+                        "http://nshop.test/hd-transparent-deep-wave-&-curly-lace-frontal-closure-wig"
+                      }
+                      icon={
+                        <svg
+                          xmlns="http://www.w3.org/2000/svg"
+                          width="32"
+                          height="32"
+                          viewBox="0 0 32 32"
+                          fill="none"
+                        >
+                          <rect
+                            x="0"
+                            y="0"
+                            width="32"
+                            height="32"
+                            rx="8"
+                            fill="#036141"
+                          ></rect>
+                          <path
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                            d="M6.40039 7.79993L8.03372 7.79993L8.73372 10.5999M8.73372 10.5999L11.0671 19.9333L23.2004 19.9333L25.5337 10.5999L8.73372 10.5999Z"
+                          ></path>
+                          <ellipse
+                            cx="11.066040515899658"
+                            cy="23.199928283691406"
+                            rx="1.4000248908996582"
+                            ry="1.4000015258789062"
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                          ></ellipse>
+                          <ellipse
+                            cx="23.1998291015625"
+                            cy="23.199928283691406"
+                            rx="1.4000244140625"
+                            ry="1.4000015258789062"
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                          ></ellipse>
+                          <path
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                            d="M15.2656 15.2665L18.999 15.2665"
+                          ></path>
+                          <path
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                            d="M17.1348 17.1332L17.1348 13.3999"
+                          ></path>
+                        </svg>
+                      }
+                    />
+                  </div>
+                </div>
+              ))}
+            </div>
+          </SwiperSlide>
+        ))}
+
+        {/* 自定义分页器容器(底部小按钮) */}
+        <div className="custom-pagination flex justify-center mt-4"></div>
+      </Swiper>
+    </div>
+  );
+};
+
+export default HomeNewArrival;

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

@@ -0,0 +1,148 @@
+"use client";
+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";
+
+const MOCK_DATA = [
+  [
+    {
+      name: "Hd Lace Wig",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
+    },
+    {
+      name: "Ready To Go",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
+    },
+    {
+      name: "Colored Wig",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
+    },
+    {
+      name: "U&V Part Wig",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
+    },
+    {
+      name: "New Arrival",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
+    },
+    {
+      name: "360 Wig",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
+    },
+  ],
+
+  [
+    {
+      name: "13x4 Lace Wig",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
+    },
+    {
+      name: "Body Wave Wig",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
+    },
+    {
+      name: "Deep Wave Wig",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
+    },
+    {
+      name: "Straight Wig",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
+    },
+    {
+      name: "Curly Wig",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
+    },
+    {
+      name: "Bob Wig",
+      img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
+    },
+  ],
+];
+
+export default function JinGangSwiper() {
+  const [activeIndex, setActiveIndex] = useState(0);
+
+  const [swiperRef, setSwiperRef] = useState(null);
+
+  const handleSlideChange = (swiper) => {
+    setActiveIndex(swiper.activeIndex);
+  };
+
+  const handleIndicatorClick = (index) => {
+    if (swiperRef) {
+      swiperRef.slideTo(index);
+      setActiveIndex(index);
+    }
+  };
+
+  return (
+    <div className="max-w-screen-md mx-auto p-4">
+      <Swiper
+        modules={[Navigation]}
+        spaceBetween={10}
+        slidesPerView={1}
+        onSwiper={setSwiperRef}
+        onSlideChange={handleSlideChange}
+        className="mb-6"
+      >
+        <SwiperSlide>
+          <div className="grid grid-cols-3 gap-2">
+            {MOCK_DATA[0].map((item, idx) => (
+              <div key={`block1-${idx}`} className="relative w-full aspect-[3/4]">
+                <Image
+                  src={item.img}
+                  alt={item.name}
+                  fill
+                  className="object-cover rounded-md"
+                />
+                {/* <div className="absolute bottom-2 left-2 text-white text-sm font-medium bg-black/50 px-2 py-1 rounded">
+                  {item.name}
+                </div> */}
+              </div>
+            ))}
+          </div>
+        </SwiperSlide>
+
+        {/* 第二大块:6个分类卡片(2行3列) */}
+        <SwiperSlide>
+          <div className="grid grid-cols-3 gap-2">
+            {MOCK_DATA[1].map((item, idx) => (
+              <div key={`block2-${idx}`} className="relative w-full aspect-[3/4]">
+                <Image
+                  src={item.img}
+                  alt={item.name}
+                  fill
+                  className="object-cover rounded-md"
+                />
+                {/* <div className="absolute bottom-2 left-2 text-white text-sm font-medium bg-black/50 px-2 py-1 rounded">
+                  {item.name}
+                </div> */}
+              </div>
+            ))}
+          </div>
+        </SwiperSlide>
+      </Swiper>
+
+      {/* 底部指示器按钮:2个,激活态黑色,未激活灰色 */}
+      <div className="flex justify-center gap-2">
+        <button
+          onClick={() => handleIndicatorClick(0)}
+          className={`w-6 h-[5px] rounded-full  opacity-100 ${
+            activeIndex === 0 ? "bg-[#333]" : "bg-[#E5E5E5]"
+          }`}
+          aria-label="切换到第一块"
+        />
+        <button
+          onClick={() => handleIndicatorClick(1)}
+          className={`w-6 h-[5px] rounded-full  opacity-100 ${
+            activeIndex === 1 ? "bg-[#333]" : "bg-[#E5E5E5]"
+          }`}
+          aria-label="切换到第二块"
+        />
+      </div>
+    </div>
+  );
+}

+ 163 - 0
src/app/(public)/_components/MiddleImageSwiper.tsx

@@ -0,0 +1,163 @@
+import React from "react";
+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 { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
+
+const productList = [
+  {
+    id: 1,
+    img: "https://picsum.photos/400/500?random=1",
+    title: "14-36 In Water Wave Hum...",
+    price: 143.35,
+  },
+  {
+    id: 2,
+    img: "https://picsum.photos/400/500?random=2",
+    title: "14-36 In Water Wave Hum.",
+    price: 143.35,
+  },
+  {
+    id: 3,
+    img: "https://picsum.photos/400/500?random=3",
+    title: "14-36 In Water Wave Hum...",
+    price: 143.35,
+  },
+  {
+    id: 4,
+    img: "https://picsum.photos/400/500?random=4",
+    title: "14-36 In Water Wave Hum.",
+    price: 143.35,
+  },
+];
+
+const MiddleImageSwiper = () => {
+  return (
+    <div className="w-full pr-4 pl-4 mx-auto  rounded-lg  ">
+      <div className="w-full">
+        <img
+          src="https://cdn.asteriahair.com/uploads/202602/11/20260211095330_7ec9031a4ab36e44.gif"
+          alt="ZEROWEAR hair banner"
+          className="w-full h-auto object-cover block"
+        />
+      </div>
+      <div className="py-4 px-4 pb-6 relative min-h-83 bg-[#f0f5f3] pb-10">
+        <div className="w-full relative">
+          <Swiper
+            modules={[Pagination]}
+            spaceBetween={16}
+            slidesPerView={2}
+            pagination={{
+              type: "progressbar",
+              el: ".my-progress-bar",
+            }}
+            className="mySwiper"
+            breakpoints={{
+              480: { slidesPerView: 1 },
+              768: { slidesPerView: 2 },
+            }}
+          >
+            {productList.map((item) => (
+              <SwiperSlide key={item.id}>
+                <div className="bg-white rounded overflow-hidden  flex flex-col">
+                  <img
+                    src={item.img}
+                    alt={item.title}
+                    className="w-full aspect-[3/4] object-cover"
+                  />
+
+                  <div className="p-2 flex flex-col gap-2">
+                    <p className="text-sm text-gray-800 truncate">
+                      {item.title}
+                    </p>
+                    {/* 价格 + 购物车图标 */}
+                    <div className="flex items-center justify-between">
+                      <div className="flex items-center gap-1">
+                        <span className="font-semibold">${item.price}</span>
+                        <span className="text-xs text-gray-500 line-through">
+                          ${item.price}
+                        </span>
+                      </div>
+                      {/* 购物车图标 */}
+                      <OpenAddToCartModalButton
+                        productUrlKey={
+                          "http://nshop.test/hd-transparent-deep-wave-&-curly-lace-frontal-closure-wig"
+                        }
+                        icon={
+                          <svg
+                            xmlns="http://www.w3.org/2000/svg"
+                            width="32"
+                            height="32"
+                            viewBox="0 0 32 32"
+                            fill="none"
+                          >
+                            <rect
+                              x="0"
+                              y="0"
+                              width="32"
+                              height="32"
+                              rx="8"
+                              fill="#036141"
+                            ></rect>
+                            <path
+                              stroke="rgba(255, 255, 255, 1)"
+                              stroke-width="1.3333333333333333"
+                              stroke-linejoin="round"
+                              stroke-linecap="round"
+                              d="M6.40039 7.79993L8.03372 7.79993L8.73372 10.5999M8.73372 10.5999L11.0671 19.9333L23.2004 19.9333L25.5337 10.5999L8.73372 10.5999Z"
+                            ></path>
+                            <ellipse
+                              cx="11.066040515899658"
+                              cy="23.199928283691406"
+                              rx="1.4000248908996582"
+                              ry="1.4000015258789062"
+                              stroke="rgba(255, 255, 255, 1)"
+                              stroke-width="1.3333333333333333"
+                              stroke-linejoin="round"
+                              stroke-linecap="round"
+                            ></ellipse>
+                            <ellipse
+                              cx="23.1998291015625"
+                              cy="23.199928283691406"
+                              rx="1.4000244140625"
+                              ry="1.4000015258789062"
+                              stroke="rgba(255, 255, 255, 1)"
+                              stroke-width="1.3333333333333333"
+                              stroke-linejoin="round"
+                              stroke-linecap="round"
+                            ></ellipse>
+                            <path
+                              stroke="rgba(255, 255, 255, 1)"
+                              stroke-width="1.3333333333333333"
+                              stroke-linejoin="round"
+                              stroke-linecap="round"
+                              d="M15.2656 15.2665L18.999 15.2665"
+                            ></path>
+                            <path
+                              stroke="rgba(255, 255, 255, 1)"
+                              stroke-width="1.3333333333333333"
+                              stroke-linejoin="round"
+                              stroke-linecap="round"
+                              d="M17.1348 17.1332L17.1348 13.3999"
+                            ></path>
+                          </svg>
+                        }
+                      />
+                    </div>
+                
+                  </div>
+                </div>
+              </SwiperSlide>
+            ))}
+          </Swiper>
+          <div className="my-progress-bar mt-5 w-full h-1 bg-gray-200 rounded-full bottom-0 [top:unset]"></div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default MiddleImageSwiper;

+ 263 - 0
src/app/(public)/_components/RecommendedProducts.tsx

@@ -0,0 +1,263 @@
+"use client";
+import React, { useState, useEffect, useCallback } from "react";
+import Image from "next/image";
+import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
+// 商品类型定义
+interface ProductItem {
+  id: number;
+  img: string;
+  title: string;
+  price: number;
+  originPrice: number;
+  star: number;
+  reviewCount: number;
+}
+
+// ========== 模拟后端分页接口(真实场景替换为axios/fetch请求) ==========
+/**
+ * @param page 当前页码
+ * @param pageSize 每页条数
+ * @returns Promise<{ list: ProductItem[], total: number }>
+ */
+const mockGetProductApi = async (page: number, pageSize: number) => {
+  // 模拟总数据22条,后端固定总数
+  const TOTAL = 22;
+  // 模拟全部源数据
+  const allMockData: ProductItem[] = Array.from({ length: TOTAL }).map(
+    (_, idx) => ({
+      id: idx + 1,
+      img: `/img/hair${(idx % 6) + 1}.jpg`,
+      title: "14-36 In Water Wave Human Hair Wig",
+      price: 143.35,
+      originPrice: 143.35,
+      star: 5,
+      reviewCount: 342,
+    }),
+  );
+
+  // 分页切片
+  const start = (page - 1) * pageSize;
+  const end = page * pageSize;
+  const pageList = allMockData.slice(start, end);
+
+  // 模拟网络延迟
+  await new Promise((resolve) => setTimeout(resolve, 600));
+  return {
+    list: pageList,
+    total: TOTAL,
+  };
+};
+
+const PAGE_SIZE = 6;
+
+export default function RecommendedProducts() {
+  // 已渲染的全部商品
+  const [productList, setProductList] = useState<ProductItem[]>([]);
+  // 当前页码
+  const [page, setPage] = useState(1);
+  // 数据总条数
+  const [total, setTotal] = useState(0);
+  // 加载状态
+  const [loading, setLoading] = useState(false);
+
+  // 是否还有下一页:已渲染数量 < 总条数
+  const hasMore = productList.length < total;
+
+  // 请求分页数据
+  const fetchPageData = useCallback(async (currentPage: number) => {
+    setLoading(true);
+    try {
+      const res = await mockGetProductApi(currentPage, PAGE_SIZE);
+      setTotal(res.total);
+      // 函数式更新,稳定依赖
+      setProductList((prev) =>
+        currentPage === 1 ? res.list : [...prev, ...res.list],
+      );
+    } catch (err) {
+      // 屏蔽 no-console 警告
+      // eslint-disable-next-line no-console
+      console.error("商品分页加载失败", err);
+    } finally {
+      setLoading(false);
+    }
+  }, []);
+
+  // 页面初始化挂载只加载一次第一页
+  useEffect(() => {
+    // 封装内部异步函数,规范写法
+    const initLoad = async () => {
+      await fetchPageData(1);
+    };
+    initLoad();
+  }, [fetchPageData]); // 补齐依赖 fetchPageData
+  // 点击View More 加载下一页
+  const handleViewMore = () => {
+    const nextPage = page + 1;
+    setPage(nextPage);
+    fetchPageData(nextPage);
+  };
+
+  // 五星星星渲染
+  const renderStars = (count: number) => {
+    return Array.from({ length: count }).map((_, idx) => (
+      <svg
+        key={idx}
+        className="w-4 h-4 text-green-600 inline-block"
+        viewBox="0 0 24 24"
+        fill="currentColor"
+      >
+        <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
+      </svg>
+    ));
+  };
+
+  return (
+    <section className="py-6 px-4 pt-8">
+      <h2 className="text-[clamp(1.5rem,4vw,2.2rem)] font-semibold mb-6">
+        Recommended For You
+      </h2>
+
+      {/* 商品2列网格 */}
+      <div className="grid grid-cols-2 gap-4">
+        {productList.map((item) => (
+          <div key={item.id} className="flex flex-col gap-3">
+            {/* Next.Image 商品图容器 */}
+            <div className="relative w-full aspect-[3/4] overflow-hidden rounded-md">
+              <Image
+                src={item.img}
+                alt={item.title}
+                fill
+                sizes="(max-width:768px) 50vw, 33vw"
+                className="object-cover"
+              />
+            </div>
+
+            {/* 标题 */}
+            <p className="text-sm text-gray-800 truncate">{item.title}</p>
+
+            {/* 星级 + 评论数 */}
+            <div className="flex items-center gap-1">
+              {renderStars(item.star)}
+              <span className="text-sm text-gray-600 ml-1 underline">
+                {item.reviewCount}
+              </span>
+            </div>
+
+            {/* 价格 + 购物车按钮 */}
+            {/* <div className="flex items-center justify-between">
+              <div className="flex items-center gap-2">
+                <span className="text-xl font-bold text-black">
+                  ${item.price}
+                </span>
+                <span className="text-sm text-gray-400 line-through">
+                  ${item.originPrice}
+                </span>
+              </div>
+              <button className="w-10 h-10 bg-emerald-600 rounded-lg flex items-center justify-center text-white">
+                <svg
+                  width="18"
+                  height="18"
+                  viewBox="0 0 24 24"
+                  fill="none"
+                  stroke="currentColor"
+                  strokeWidth="2"
+                >
+                  <path d="M6 3h12M3 6h18l-2 11H5L3 6z" />
+                  <circle cx="9" cy="20" r="1" />
+                  <circle cx="15" cy="20" r="1" />
+                </svg>
+              </button>
+            </div> */}
+            {/* 价格 + 购物车图标 */}
+            <div className="flex items-center justify-between">
+              <div className="flex items-center gap-1">
+                <span className="font-semibold">${item.price}</span>
+                <span className="text-xs text-gray-500 line-through">
+                  ${item.originPrice}
+                </span>
+              </div>
+              {/* 购物车图标 */}
+              <OpenAddToCartModalButton
+                productUrlKey={
+                  "http://nshop.test/hd-transparent-deep-wave-&-curly-lace-frontal-closure-wig"
+                }
+                icon={
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="32"
+                    height="32"
+                    viewBox="0 0 32 32"
+                    fill="none"
+                  >
+                    <rect
+                      x="0"
+                      y="0"
+                      width="32"
+                      height="32"
+                      rx="8"
+                      fill="#036141"
+                    ></rect>
+                    <path
+                      stroke="rgba(255, 255, 255, 1)"
+                      stroke-width="1.3333333333333333"
+                      stroke-linejoin="round"
+                      stroke-linecap="round"
+                      d="M6.40039 7.79993L8.03372 7.79993L8.73372 10.5999M8.73372 10.5999L11.0671 19.9333L23.2004 19.9333L25.5337 10.5999L8.73372 10.5999Z"
+                    ></path>
+                    <ellipse
+                      cx="11.066040515899658"
+                      cy="23.199928283691406"
+                      rx="1.4000248908996582"
+                      ry="1.4000015258789062"
+                      stroke="rgba(255, 255, 255, 1)"
+                      stroke-width="1.3333333333333333"
+                      stroke-linejoin="round"
+                      stroke-linecap="round"
+                    ></ellipse>
+                    <ellipse
+                      cx="23.1998291015625"
+                      cy="23.199928283691406"
+                      rx="1.4000244140625"
+                      ry="1.4000015258789062"
+                      stroke="rgba(255, 255, 255, 1)"
+                      stroke-width="1.3333333333333333"
+                      stroke-linejoin="round"
+                      stroke-linecap="round"
+                    ></ellipse>
+                    <path
+                      stroke="rgba(255, 255, 255, 1)"
+                      stroke-width="1.3333333333333333"
+                      stroke-linejoin="round"
+                      stroke-linecap="round"
+                      d="M15.2656 15.2665L18.999 15.2665"
+                    ></path>
+                    <path
+                      stroke="rgba(255, 255, 255, 1)"
+                      stroke-width="1.3333333333333333"
+                      stroke-linejoin="round"
+                      stroke-linecap="round"
+                      d="M17.1348 17.1332L17.1348 13.3999"
+                    ></path>
+                  </svg>
+                }
+              />
+            </div>
+          </div>
+        ))}
+      </div>
+
+      {/* View More 按钮:有下一页且非加载中显示 */}
+      {hasMore && (
+        <div className="flex justify-center mt-8">
+          <button
+            onClick={handleViewMore}
+            disabled={loading}
+            className="px-8 py-2 border border-black rounded-full text-base hover:bg-gray-100 transition disabled:opacity-50 disabled:cursor-not-allowed"
+          >
+            {loading ? "Loading..." : "View More"}
+          </button>
+        </div>
+      )}
+    </section>
+  );
+}

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

@@ -0,0 +1,43 @@
+import React from 'react';
+import { Swiper, SwiperSlide } from 'swiper/react';
+import 'swiper/css';
+// import { Navigation, Pagination, Mousewheel, Keyboard } from 'swiper/modules';
+
+const TopImageSwiper = () => {
+  // ✅ 直接在这里放你的图片数组,想加多少加多少
+  const images = [
+    'https://cdn.asteriahair.com/uploads/202607/01/20260701165056_761989a0fdc11d5e.jpg',
+    'https://cdn.asteriahair.com/uploads/202607/01/20260701165110_3fff435adf276b89.jpg',
+    'https://cdn.asteriahair.com/uploads/202607/01/20260701165123_c424f6f1d0b29522.jpg',
+    'https://cdn.asteriahair.com/uploads/202607/01/20260701165056_761989a0fdc11d5e.jpg',
+  ];
+
+  return (
+    <div className="w-full max-w-[1200px] mx-auto ">
+      <Swiper
+        slidesPerView={1}
+        spaceBetween={0}
+        loop={true} // 无限轮播
+      
+        // pagination={{ clickable: true }}
+        // mousewheel
+        // keyboard={{ enabled: true }}
+        className="h-auto md:h-[80vh]  overflow-hidden"
+      >
+        {/* ✅ 自动遍历图片数组,一行代码搞定所有图片 */}
+        {images.map((img, index) => (
+          <SwiperSlide key={index}>
+            <img
+              src={img}
+              alt={`slide-${index}`}
+              className="w-full h-full object-cover"
+            />
+          </SwiperSlide>
+        ))}
+        
+      </Swiper>
+    </div>
+  );
+};
+
+export default TopImageSwiper;

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

@@ -0,0 +1,317 @@
+import React, { 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 { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
+
+// 商品数据,每个卡片携带videoUrl视频地址
+type ProductItem = {
+  id: number;
+  img: string;
+  title: string;
+  price: number;
+  videoUrl: string;
+};
+
+const productList: ProductItem[] = [
+  {
+    id: 1,
+    img: "/img/hair-video-1.jpg",
+    title: "14-36 In Water Wave Human Hair HD Inv...",
+    price: 146.1,
+    videoUrl: "/video/hair-demo-1.mp4",
+  },
+  {
+    id: 2,
+    img: "/img/hair-video-2.jpg",
+    title: "14-36 In Water Wave Human Hair HD Inv...",
+    price: 146.1,
+    videoUrl: "/video/hair-demo-2.mp4",
+  },
+  {
+    id: 3,
+    img: "/img/hair-video-3.jpg",
+    title: "14-36 In Water Wave Human Hair HD Inv...",
+    price: 146.1,
+    videoUrl: "/video/hair-demo-3.mp4",
+  },
+  {
+    id: 4,
+    img: "/img/hair-video-4.jpg",
+    title: "14-36 In Water Wave Human Hair HD Inv...",
+    price: 146.1,
+    videoUrl: "/video/hair-demo-4.mp4",
+  },
+];
+
+const Trending = () => {
+  // 视频弹窗状态
+  const [openVideoModal, setOpenVideoModal] = useState(false);
+  const [currentVideoSrc, setCurrentVideoSrc] = useState("");
+  const videoRef = useRef<HTMLVideoElement>(null);
+
+  // 打开弹窗并赋值视频地址
+  const handlePlayClick = (videoUrl: string) => {
+    setCurrentVideoSrc(videoUrl);
+    setOpenVideoModal(true);
+  };
+
+  // 关闭弹窗,暂停视频
+  const closeModal = () => {
+    setOpenVideoModal(false);
+    if (videoRef.current) {
+      videoRef.current.pause();
+      videoRef.current.currentTime = 0;
+    }
+  };
+
+  // 弹窗打开自动播放
+  useEffect(() => {
+    if (openVideoModal && videoRef.current) {
+      videoRef.current.play().catch((err) => console.log("播放拦截", err));
+    }
+  }, [openVideoModal]);
+
+  return (
+    <div className="w-full max-w-[960px] mx-auto">
+      <div className="w-full max-w-[960px] mx-auto px-4 py-6">
+        {/* 顶部标题 */}
+        <h2 className="text-[clamp(1.5rem,4vw,2.2rem)] font-bold mb-6 text-black">
+          Real looks,Trending Now
+        </h2>
+
+        {/* Swiper外层容器:进度条深层Tailwind样式 */}
+        <div
+          className="
+          [&_.custom-scrollbar]:h-[3px]
+          [&_.custom-scrollbar]:bg-gray-200
+          [&_.custom-scrollbar]:rounded-full
+          [&_.swiper-scrollbar-drag]:h-full
+          [&_.swiper-scrollbar-drag]:bg-black
+          [&_.swiper-scrollbar-drag]:rounded-full
+          [&_.swiper-scrollbar-drag]:shadow-none
+          [&_.swiper-scrollbar]:bg-transparent
+          /* 自定义左右箭头样式 匹配截图灰色圆形按钮 */
+          [&_.swiper-button-next]:w-11
+          [&_.swiper-button-next]:h-11
+          [&_.swiper-button-next]:rounded-full
+          [&_.swiper-button-next]:bg-black/40
+          [&_.swiper-button-next]:text-white
+          [&_.swiper-button-next]:after:text-base
+          [&_.swiper-button-next]:after:font-bold
+          [&_.swiper-button-prev]:w-11
+          [&_.swiper-button-prev]:h-11
+          [&_.swiper-button-prev]:rounded-full
+          [&_.swiper-button-prev]:bg-black/40
+          [&_.swiper-button-prev]:text-white
+          [&_.swiper-button-prev]:after:text-base
+          [&_.swiper-button-prev]:after:font-bold
+          [&_.swiper-button-next]:right-3
+          [&_.swiper-button-prev]:left-3
+        "
+        >
+          <Swiper
+            modules={[Scrollbar, Navigation]}
+            spaceBetween={16}
+            slidesPerView={1.2} // 露出右侧下一张,和截图一致
+            navigation={{
+              prevEl: ".btn-prev", // 左箭头class
+              nextEl: ".btn-next", // 右箭头class
+              disabledClass: "opacity-20", // 滑到首尾自动加这个类,Tailwind直接用
+            }}
+            breakpoints={{
+              640: { slidesPerView: 2.2 },
+              1024: { slidesPerView: 2.5 },
+            }}
+          >
+            {productList.map((item) => (
+              <SwiperSlide key={item.id} className="!h-auto">
+                {/* 单个商品卡片 */}
+                <div className=" overflow-hidden  flex flex-col">
+                  {/* 图片区域 + 中间播放按钮 */}
+                  <div className="relative rounded-xl w-full aspect-[4/5] overflow-hidden rounded-xl">
+                    <img
+                      src={item.img}
+                      alt={item.title}
+                      className="w-full h-full object-cover"
+                    />
+                    {/* 居中圆形播放按钮 */}
+                    <button
+                      onClick={() => handlePlayClick(item.videoUrl)}
+                      className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
+                      w-24 h-24 rounded-full bg-white/60 backdrop-blur-sm
+                      flex items-center justify-center transition hover:bg-white/80"
+                    >
+                      <svg
+                        width="36"
+                        height="36"
+                        viewBox="0 0 24 24"
+                        fill="none"
+                        stroke="#222"
+                        strokeWidth="2"
+                      >
+                        <polygon points="5 3 19 12 5 21 5 3" />
+                      </svg>
+                    </button>
+                  </div>
+
+                  {/* 底部文字价格区域 */}
+                  <div className="p-3 flex flex-col rounded-xl mt-3 bg-[#f0f8f5] gap-3">
+                    <p className="text-base text-gray-900 truncate">
+                      {item.title}
+                    </p>
+                    {/* 价格 + 购物车图标 */}
+                    <div className="flex items-center justify-between">
+                      <div className="flex items-center gap-1">
+                        <span className="font-semibold">${item.price}</span>
+                        <span className="text-xs text-gray-500 line-through">
+                          ${item.price}
+                        </span>
+                      </div>
+                      {/* 购物车图标 */}
+                      <OpenAddToCartModalButton
+                        productUrlKey={
+                          "http://nshop.test/hd-transparent-deep-wave-&-curly-lace-frontal-closure-wig"
+                        }
+                        icon={
+                          <svg
+                            xmlns="http://www.w3.org/2000/svg"
+                            width="32"
+                            height="32"
+                            viewBox="0 0 32 32"
+                            fill="none"
+                          >
+                            <rect
+                              x="0"
+                              y="0"
+                              width="32"
+                              height="32"
+                              rx="8"
+                              fill="#036141"
+                            ></rect>
+                            <path
+                              stroke="rgba(255, 255, 255, 1)"
+                              stroke-width="1.3333333333333333"
+                              stroke-linejoin="round"
+                              stroke-linecap="round"
+                              d="M6.40039 7.79993L8.03372 7.79993L8.73372 10.5999M8.73372 10.5999L11.0671 19.9333L23.2004 19.9333L25.5337 10.5999L8.73372 10.5999Z"
+                            ></path>
+                            <ellipse
+                              cx="11.066040515899658"
+                              cy="23.199928283691406"
+                              rx="1.4000248908996582"
+                              ry="1.4000015258789062"
+                              stroke="rgba(255, 255, 255, 1)"
+                              stroke-width="1.3333333333333333"
+                              stroke-linejoin="round"
+                              stroke-linecap="round"
+                            ></ellipse>
+                            <ellipse
+                              cx="23.1998291015625"
+                              cy="23.199928283691406"
+                              rx="1.4000244140625"
+                              ry="1.4000015258789062"
+                              stroke="rgba(255, 255, 255, 1)"
+                              stroke-width="1.3333333333333333"
+                              stroke-linejoin="round"
+                              stroke-linecap="round"
+                            ></ellipse>
+                            <path
+                              stroke="rgba(255, 255, 255, 1)"
+                              stroke-width="1.3333333333333333"
+                              stroke-linejoin="round"
+                              stroke-linecap="round"
+                              d="M15.2656 15.2665L18.999 15.2665"
+                            ></path>
+                            <path
+                              stroke="rgba(255, 255, 255, 1)"
+                              stroke-width="1.3333333333333333"
+                              stroke-linejoin="round"
+                              stroke-linecap="round"
+                              d="M17.1348 17.1332L17.1348 13.3999"
+                            ></path>
+                          </svg>
+                        }
+                      />
+                    </div>
+                  </div>
+                </div>
+              </SwiperSlide>
+            ))}
+            {/* 自定义左右按钮,放在Swiper内部,同级slide */}
+            <button className="btn-prev absolute left-2 top-1/2 -translate-y-1/2 z-10 w-9 h-9 rounded-full bg-black/50 shadow flex items-center justify-center">
+              {/* 自定义左箭头SVG */}
+              <svg
+                width="16"
+                height="16"
+                viewBox="0 0 24 24"
+                fill="none"
+                stroke="#fff"
+                stroke-width="2"
+              >
+                <path d="M15 18l-6-6 6-6" />
+              </svg>
+            </button>
+            <button className="btn-next absolute right-2 top-1/2 -translate-y-1/2 z-10 w-9 h-9 rounded-full bg-black/50 shadow flex items-center justify-center">
+              {/* 自定义右箭头SVG */}
+              <svg
+                width="16"
+                height="16"
+                viewBox="0 0 24 24"
+                fill="none"
+                stroke="#fff"
+                stroke-width="2"
+              >
+                <path d="M9 18l6-6-6-6" />
+              </svg>
+            </button>
+          </Swiper>
+        </div>
+
+        {/* ====================== 全屏视频遮罩弹窗 ====================== */}
+        {openVideoModal && (
+          <div
+            className="fixed inset-0 z-[9999] bg-black/95 flex items-center justify-center p-4"
+            onClick={closeModal} // 点击遮罩空白关闭
+          >
+            {/* 弹窗内层,阻止冒泡 */}
+            <div
+              className="relative w-full max-w-4xl"
+              onClick={(e) => e.stopPropagation()}
+            >
+              {/* 右上角关闭按钮 */}
+              <button
+                onClick={closeModal}
+                className="absolute -top-12 right-0 text-white text-3xl hover:text-gray-300"
+              >
+                ✕
+              </button>
+              {/* 视频播放器 */}
+              <video
+                ref={videoRef}
+                src={currentVideoSrc}
+                controls
+                className="w-full rounded-lg"
+              />
+            </div>
+          </div>
+        )}
+      </div>
+      <div className="relative w-full aspect-[3/4] mt-8">
+        <Image
+          src={
+            "https://cdn.asteriahair.com/uploads/202607/01/20260701165110_3fff435adf276b89.jpg"
+          }
+          alt={"cscscs"}
+          fill
+          className="object-cover rounded-md"
+        />
+      </div>
+    </div>
+  );
+};
+
+export default Trending;

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

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

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

@@ -0,0 +1,74 @@
+"use client";
+
+import { useEffect, useRef } from "react";
+import { Swiper, SwiperSlide } from "swiper/react";
+// 只导入 Autoplay 即可!loop 内置了
+import { Autoplay } from "swiper/modules";
+
+import "swiper/css";
+
+const AccountBottomHrefSwiper = () => {
+  const swiperRef = useRef<any>(null);
+
+  const footerCategorySlides = [
+    { url: "/new-arrival.html", title: "New Arrival" },
+    { url: "/hd-lace-wig.html", title: "HD Lace" },
+    { url: "/effortless-wig.html", title: "Ready To Go Wigs" },
+    { url: "/ombrecolorwigs.html", title: "Color" },
+    { url: "/lace-frontal-wig.html", title: "Lace Frontal Wigs" },
+    { url: "/closure-wigs.html", title: "Closure Wigs" },
+  
+  ];
+
+  // 登录/注册/找回密码页面隐藏
+  useEffect(() => {
+    const path = window.location.pathname;
+    const hidePaths = [
+      "/customer/account/login",
+      "/customer/account/forgotpassword",
+      "/customer/account/create",
+    ];
+
+    if (hidePaths.some((p) => path.includes(p))) {
+      const el = document.querySelector(".footer-bottom-category");
+      if (el) el.remove();
+    }
+  }, []);
+
+  return (
+    <div className="footer-bottom-category bg-white py-3">
+        <p className="underline text-center font-bold bg-[#eee] leading-8 ">
+            HOW TO GET MORE POINTS?
+        </p>
+      <div className="footer-bottom-category-list container mx-auto px-4 bg-[#fff8f2] height-12.5 flex items-center ">
+        <Swiper
+          ref={swiperRef}
+          // ✅ 只需要 Autoplay
+          modules={[Autoplay]}
+          // ✅ loop 直接写,不需要模块!
+        //   loop={true}
+          slidesPerView="auto"
+          spaceBetween={12}
+          autoplay={{
+            delay: 1000,
+            disableOnInteraction: false,
+          }}
+          className="footer-bottom-category-swiper"
+        >
+          {footerCategorySlides.map((item, index) => (
+            <SwiperSlide key={index} className="!w-auto footer-bottom-category-item whitespace-nowrap px-4 py-2 text-sm text-gray-700 rounded-lg border border-solid border-[#b32400]">
+              <a
+                href={item.url}
+                className=" "
+              >
+                {item.title}
+              </a>
+            </SwiperSlide>
+          ))}
+        </Swiper>
+      </div>
+    </div>
+  );
+};
+
+export default AccountBottomHrefSwiper;

File diff suppressed because it is too large
+ 158 - 0
src/app/(public)/customer/account/_components/AccountHeader.tsx


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

@@ -0,0 +1,242 @@
+"use client";
+// import Link from "next/link";
+// 轮播逻辑
+import { Swiper, SwiperSlide } from "swiper/react";
+import { Navigation } from "swiper/modules";
+import "swiper/css";
+import "swiper/css/navigation";
+
+// 模拟你的每一页数据,方便维护
+const vipList = [
+  {
+    level: 1,
+    title: "V1 Privileges",
+    exppoints: 1000,
+    coupons: [
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130956.png",
+        text1: "Points Redeem",
+        text2: "Giftcard",
+        link: "javascript:void(0)",
+      },
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130957.png",
+        text1: "Exclusive",
+        text2: "$100 Coupon",
+        link: "javascript:void(0)",
+      },
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130958.png",
+        text1: "Birthday",
+        text2: "Treat",
+        link: "javascript:void(0)",
+      },
+    ],
+  },
+  {
+    level: 2,
+    title: "V2 Privileges",
+    exppoints: 2000,
+    coupons: [
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130956.png",
+        text1: "POINTS REDEEM",
+        text2: "GIFTCARD",
+        link: "javascript:void(0)",
+      },
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130957.png",
+        text1: "EXCLUSIVE",
+        text2: "$150 COUPON",
+        link: "javascript:void(0)",
+      },
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130958.png",
+        text1: "BIRTHDAY",
+        text2: "TREAT",
+        link: "javascript:void(0)",
+      },
+    ],
+  },
+  {
+    level: 3,
+    title: "V3 Privileges",
+    exppoints: 10000,
+    coupons: [
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130956.png",
+        text1: "POINTS REDEEM",
+        text2: "GIFTCARD",
+        link: "javascript:void(0)",
+      },
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130957.png",
+        text1: "EXCLUSIVE",
+        text2: "$200 COUPON",
+        link: "javascript:void(0)",
+      },
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130958.png",
+        text1: "BIRTHDAY",
+        text2: "TREAT",
+        link: "javascript:void(0)",
+      },
+    ],
+  },
+  {
+    level: 4,
+    title: "V4 Privileges",
+    exppoints: 50000,
+    coupons: [
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130956.png",
+        text1: "POINTS REDEEM",
+        text2: "GIFTCARD",
+        link: "/customer/account/pointsreceivelist",
+      },
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130957.png",
+        text1: "EXCLUSIVE",
+        text2: "$300 COUPON",
+        link: "/customer/account/couponsreceivelist/v/4/",
+      },
+      {
+        img: "https://cdn.alipearlhair.com/media/wysiwyg/wap/202301130958.png",
+        text1: "BIRTHDAY",
+        text2: "TREAT",
+        link: "/customer/account/birthday",
+      },
+    ],
+  },
+];
+export default function AccountVipPopup({
+  visible,
+  onClose,
+}: {
+  visible: boolean;
+  onClose: () => void;
+}) {
+  // 如果不显示,直接 return null
+  if (!visible) return null;
+
+  return (
+    // 全屏遮罩
+    <div className="fixed inset-0 z-50 bg-black/50">
+      {/* 弹框主体 - 从底部滑上来 */}
+      <div className="absolute bottom-0 rounded-none left-0 right-0 w-full h-full bg-white  overflow-auto">
+        <div className="flex flex-col">
+          {/* 顶部导航 */}
+          <header className="bg-black text-white py-4 px-4 flex items-center">
+            <button onClick={onClose} className="">
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                className="h-6 w-6"
+                fill="none"
+                viewBox="0 0 24 24"
+                stroke="currentColor"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M15 19l-7-7 7-7"
+                />
+              </svg>
+            </button>
+            <h1 className="mx-auto">Vip Center</h1>
+          </header>
+          <div className="vipcenter-privileges-modal-innerscroll-father">
+            <div id="vipcenter-privileges-modal-innerscroll">
+              <div
+                className="   relative
+    h-170
+    mt-8
+    px-6
+    bg-[url('https://cdn.alipearlhair.com/media/wysiwyg/wap/_2.png')]
+    bg-no-repeat
+    bg-[length:100%_100%]"
+              >
+                <div className="absolute top-0.75 left-8 text-4.5 text-[#FFD1B9] font-semibold">
+                  Vip <span className="levelcard">4</span>
+                </div>
+                <div className="flex flex-col justify-between items-center text-white">
+                  <div className="w-18 h-18 mt-6">
+                    <img
+                      className="max-w-full border-0 align-top"
+                      src="https://cdn.alipearlhair.com/media/wysiwyg/newap/202303131637.png "
+                      alt=""
+                    />
+                  </div>
+                  <p className="text-4.5 mt-3 font-semibold"></p>
+                  <p className="text-text-sm mt-1.5 text-[#EFEFEF]">
+                    18567768101@163.com
+                  </p>
+                  <p className="text-sm mt-1.5 text-[#EFEFEF]">
+                    Above current level
+                  </p>
+                </div>
+                <div className="box-border w-full mt-10.5 mb-9.75">
+                  <div className="text-center text-sm text-white mb-3">
+                    <span className="account-center-progress-text-pre">
+                      Growth Value 100309/500001
+                    </span>
+                  </div>
+                  <div className="relative h-1 bg-[#5F4040]">
+                    <div className="w-4 h-4 rounded-full bg-black absolute top-[-0.1867rem] left-0 block"></div>
+                    <div className="rounded-[1.2821vw] w-[35%] h-full bg-[rgba(255,255,255,1)]"></div>
+                  </div>
+                  {/* 轮播 */}
+                  <div className="swiper-container-center vip-swiper">
+                    <Swiper
+                      modules={[Navigation]}
+                      navigation // 开启左右箭头
+                      slidesPerView={1}
+                      spaceBetween={0}
+                      className="swiper-container-horizontal"
+                    >
+                      {vipList.map((item, idx) => (
+                        <SwiperSlide
+                          key={idx}
+                          className="swiper-slide-color"
+                          style={{ width: "343px" }}
+                          data-exppoints={item.exppoints}
+                          data-level={item.level}
+                        >
+                          {/* 标题 */}
+                          <h3 className="text-lg font-medium mb-4">
+                            {item.title}
+                          </h3>
+
+                          {/* 权益卡片容器 */}
+                          <div className="vipcenter-privileges-coupnts flex gap-3 flex-wrap">
+                            {item.coupons.map((coupon, cIdx) => (
+                              <a
+                                key={cIdx}
+                                href={coupon.link}
+                                className="vipcenter-privileges-coupnt-a vipcenter-privileges-coupnt-ponit"
+                              >
+                                <button className="vipcenter-privileges-coupnt flex flex-col items-center p-2 border rounded">
+                                  <img
+                                    src={coupon.img}
+                                    alt=""
+                                    className="w-10 h-10 object-contain mb-1"
+                                  />
+                                  <p className="text-sm">{coupon.text1}</p>
+                                  <p className="text-sm">{coupon.text2}</p>
+                                </button>
+                              </a>
+                            ))}
+                          </div>
+                        </SwiperSlide>
+                      ))}
+                    </Swiper>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 112 - 0
src/app/(public)/customer/account/_components/HelpModal.tsx

@@ -0,0 +1,112 @@
+import Link from "next/link";
+export default function HelpModal({
+  helpvisible,
+  helponClose,
+}: {
+  helpvisible: boolean;
+  helponClose: () => void;
+}) {
+  // 如果不显示,直接 return null
+  if (!helpvisible) return null;
+  return (
+    <div className="fixed inset-0 z-51 bg-black/50">
+      {/* 弹框主体 - 从底部滑上来 */}
+      <div className="absolute bottom-0 rounded-none left-0 right-0 w-full h-full bg-white  overflow-auto">
+        <div className="flex flex-col">
+          {/* 顶部导航 */}
+          <header className="bg-black text-white py-4 px-4 flex items-center">
+            <button onClick={helponClose} className="mr-4">
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                className="h-6 w-6"
+                fill="none"
+                viewBox="0 0 24 24"
+                stroke="currentColor"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M15 19l-7-7 7-7"
+                />
+              </svg>
+            </button>
+            <h1 className="text-lg font-medium">HELP & SUPPORT</h1>
+          </header>
+          {/* 菜单列表 */}
+          <div className="px-4 py-2">
+            {/* Information */}
+            <Link
+              href={"/help-faqs"}
+              className="py-4 border-b border-gray-100 flex justify-between items-center"
+            >
+              {/* <div > */}
+              <span className="text-black font-medium">Help & FAQ</span>
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                className="h-5 w-5 text-gray-400"
+                fill="none"
+                viewBox="0 0 24 24"
+                stroke="currentColor"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M9 5l7 7-7 7"
+                />
+              </svg>
+              {/* </div> */}
+            </Link>
+            {/* Address */}
+            <Link
+              href={"/track_order"}
+              className="py-4 border-b border-gray-100 flex justify-between items-center"
+            >
+              {/* <div > */}
+              <span className="text-black font-medium">Track Order</span>
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                className="h-5 w-5 text-gray-400"
+                fill="none"
+                viewBox="0 0 24 24"
+                stroke="currentColor"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M9 5l7 7-7 7"
+                />
+              </svg>
+              {/* </div> */}
+            </Link>
+            {/* help & Support */}
+            <Link
+              href={"/hair-care-tips"}
+              className="py-4 border-b border-gray-100 flex justify-between items-center"
+            >
+              {/* <div > */}
+              <span className="text-black font-medium">Hair Care Tips</span>
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                className="h-5 w-5 text-gray-400"
+                fill="none"
+                viewBox="0 0 24 24"
+                stroke="currentColor"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M9 5l7 7-7 7"
+                />
+              </svg>
+              {/* </div> */}
+            </Link>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 183 - 0
src/app/(public)/customer/account/mypoints/_components/GetPoints.tsx

@@ -0,0 +1,183 @@
+"use client";
+// import Link from "next/link";
+import React, { useState, useEffect } from "react";
+import { clientFetch } from "@/lib/restApiClient";
+
+export default function GetPoints() {
+  const [taskData, setTaskData] = useState<any>(null);
+  const [loading, setLoading] = useState(true);
+  useEffect(() => {
+    const fetchRewardPointsHistory = async () => {
+      try {
+        setLoading(true);
+        const res = await clientFetch("/api/customer/reward-points/follow-status");
+      
+        setTaskData(res.data.data);
+        console.log("积分任务数据:", res.data.data);
+      } catch (error) {
+        console.error("获取任务状态失败:", error);
+      } finally {
+        setLoading(false);
+      }
+    };
+    fetchRewardPointsHistory();
+  }, []);
+
+  function canTriggerTask(item: (typeof earnPointsItems)[0]) {
+  
+    if (!taskData) return false;
+
+    const { type } = item;
+    const { browsed_product_ids, platforms } = taskData;
+
+    if (!type) return true;
+
+   
+    if (browsed_product_ids.includes(type)) {
+      return false;
+    }
+
+    const platform = platforms.find((p: any) => p.code === type);
+    if (platform) {
+      return !platform.followed;
+    }
+
+    return true;
+  }
+
+  
+  const handleClaimPoints = async (item: any) => {
+ 
+    if (!canTriggerTask(item)) return;
+
+    try {
+      console.log("触发领取积分,任务类型:", item.type);
+      
+      // const getpointsres = await clientFetch("/api/customer/reward-points/follow", {  
+      //   method: "POST",
+      //   body: JSON.stringify({ platform: item.type }),
+      // });
+      // 这个浏览获取积分接口后面放到列表页去调
+      const getpointsres = await clientFetch("/api/customer/reward-points/browse-product", {  
+        method: "POST",
+        body: JSON.stringify({ categorys: item.type }),
+      });
+      if(getpointsres.data.success){
+        alert("Points claimed successfully!");
+      }
+      
+      const res = await clientFetch("/api/customer/reward-points/follow-status");
+      setTaskData(res.data.data);
+    } catch (err) {
+      console.error("领取失败", err);
+      alert("Failed to claim points");
+    }
+  };
+
+  const earnPointsItems = [
+    {
+      title: "Share a plain text review",
+      desc: "To Get 50 points",
+      points: "",
+      type: "",
+    },
+    {
+      title: "Share your wig review with photos/videos",
+      desc: "To Get 100 Points",
+      points: "",
+      type: "",
+    },
+    {
+      title: "Log In Our Website",
+      desc: "Every Day To Get Points",
+      points: "",
+      type: "",
+    },
+    {
+      title: "Browse New Arrival Page ≥ 15 Seconds",
+      desc: "To Get 10 Points",
+      points: "",
+      type: "aaa",
+    },
+    {
+      title: "Browse HD Lace Wigs ≥ 15 Seconds",
+      desc: "To Get 10 Points",
+      points: "",
+      type: "ccc",
+    },
+    {
+      title: "Browse Ready To Go Wigs ≥ 15 Seconds",
+      desc: "To Get 10 Points",
+      points: "",
+      type: "bbbb",
+    },
+    {
+      title: "Follow Our Instagram Account",
+      desc: "To Get 20 Points",
+      points: "",
+      type: "ig",
+    },
+    {
+      title: "Follow Our Facebook Account",
+      desc: "To Get 20 Points",
+      points: "",
+      type: "fb",
+    },
+    {
+      title: "Follow Our Youtube Channel",
+      desc: "To Get 20 Points",
+      points: "",
+      type: "ytb",
+    },
+    {
+      title: "Follow Our Tiktok Account",
+      desc: "To Get 20 Points",
+      points: "",
+      type: "tt",
+    },
+  ];
+
+  return (
+    <div className="space-y-2">
+      {loading ? (
+        <div>Loading tasks...</div>
+      ) : (
+        earnPointsItems.map((item, index) => {
+          const canClick = canTriggerTask(item);
+          return (
+            <div
+              key={index}
+              className="h-15.5 flex items-center justify-between bg-[#F8F8F8] p-2.5 rounded-md "
+            >
+              <div>
+                <h3 className="font-medium text-black">{item.title}</h3>
+                {item.desc && <p className="text-sm text-gray-500">{item.desc}</p>}
+              </div>
+              <div className="flex items-center gap-6">
+                {item.points && (
+                  <span className="font-medium">{item.points}</span>
+                )}
+                <button
+                  onClick={() => handleClaimPoints(item)}
+                  disabled={!canClick}
+                  className={`transition-colors ${
+                    canClick ? "text-black" : "text-gray-300 cursor-not-allowed"
+                  }`}
+                >
+                  <svg
+                    viewBox="0 0 24 24"
+                    width="16"
+                    height="16"
+                    fill="currentColor"
+                  >
+                    <path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z" />
+                  </svg>
+                </button>
+              </div>
+            </div>
+          );
+        })
+      )}
+    </div>
+  );
+}

+ 28 - 0
src/app/(public)/customer/account/mypoints/_components/PointsRule.tsx

@@ -0,0 +1,28 @@
+"use client";
+import React, { useState } from "react";
+import PointsRuleModal from "@/components/customer/PointsRule";
+export default function PointsRule() {
+  // 控制弹窗显隐的状态
+  const [isModalOpen, setIsModalOpen] = useState(false);
+
+  // 打开弹窗(页面点击触发)
+  const ruleClick = () => {
+    console.log("aaaa");
+    setIsModalOpen(true);
+  };
+  return (
+    <>
+      <div
+        onClick={ruleClick}
+        className="text-black text-sm font-medium underline"
+      >
+        Rules
+      </div>
+      {/* 积分规则组件 */}
+      <PointsRuleModal
+        visible={isModalOpen}
+        handleCloseModal={() => setIsModalOpen(false)}
+      />
+    </>
+  );
+}

+ 16 - 71
src/app/(public)/customer/account/mypoints/page.tsx

@@ -1,9 +1,7 @@
-"use client";
 import Image from "next/image";
 import Link from "next/link";
-import React, { useState } from "react";
-import PointsRuleModal from "@/components/customer/PointsRule";
-
+import PointsRule from "./_components/PointsRule"
+import GetPoints from "./_components/GetPoints"
 const AccountPointsPage = () => {
   // 模拟用户积分数据
   const userPoints = 100009;
@@ -30,34 +28,13 @@ const AccountPointsPage = () => {
     },
   ];
 
-  // 积分获取项配置
-  const earnPointsItems = [
-    { title: "Make a Purchase", desc: "", points: "1 Points Per $1" },
-    { title: "Post a Review", desc: "", points: "+50 points" },
-    {
-      title: "Log In",
-      desc: "",
-      points: "+10 points",
-    },
-    {
-      title: "Reward for Registering",
-      desc: "",
-      points: "+200 points",
-    },
-  ];
-   // 控制弹窗显隐的状态
-  const [isModalOpen, setIsModalOpen] = useState(false);
-
-  // 打开弹窗(页面点击触发)
-  const ruleClick = () => {
-    console.log('aaaa');
-    setIsModalOpen(true);
-  };
-
   return (
     <div className="w-full h-full">
       <div className="bg-[#fff] pr-2.5 pl-2.5 w-full text-center text-base h-11 leading-11 font-semibold relative text-[#0b0b0b]">
-        <Link href={"/customer/account"} className="absolute left-2.5 text-2xl inline-block top-1/2  -translate-y-1/2">
+        <Link
+          href={"/customer/account"}
+          className="absolute left-2.5 text-2xl inline-block top-1/2  -translate-y-1/2"
+        >
           <svg
             xmlns="http://www.w3.org/2000/svg"
             width="24"
@@ -73,8 +50,8 @@ const AccountPointsPage = () => {
               d="M15 18L9 12L15 6"
             ></path>
           </svg>
-          </Link>
-        <span className="vipReturnTitles">SHIPPING ADDRESS</span>
+        </Link>
+        <span className="vipReturnTitles">My Points</span>
       </div>
       <div className=" bg-[#FFF5F2] px-4 py-8 max-w-3xl mx-auto pt-6 pb-5">
         {/* 顶部积分展示区 */}
@@ -138,12 +115,13 @@ const AccountPointsPage = () => {
           </div>
           {/* Details & Rules 链接 */}
           <div className="flex gap-4 text-sm font-medium">
-            <a href="#" className="text-black text-sm font-medium underline">
+            <Link
+              href="/customer/account/pointlist"
+              className="text-black text-sm font-medium underline"
+            >
               Details
-            </a>
-            <a href="#" onClick={ruleClick} className="text-black text-sm font-medium underline">
-              Rules
-            </a>
+            </Link>
+            <PointsRule/>
           </div>
         </div>
 
@@ -169,42 +147,9 @@ const AccountPointsPage = () => {
         <h2 className="text-base mt-6 mb-8  font-bold text-black text-center">
           Earn Points
         </h2>
-        <div className="space-y-2">
-          {earnPointsItems.map((item, index) => (
-            <div
-              key={index}
-              className="h-15.5 flex items-center justify-between bg-[#F8F8F8] p-2.5 rounded-md "
-            >
-              <div>
-                <h3 className="font-medium text-black">{item.title}</h3>
-                {item.desc && (
-                  <p className="text-sm text-gray-500">{item.desc}</p>
-                )}
-              </div>
-              <div className="flex items-center gap-6">
-                {item.points && (
-                  <span className="font-medium">{item.points}</span>
-                )}
-                <button className="text-gray-400 hover:text-black transition-colors">
-                  <svg
-                    viewBox="0 0 24 24"
-                    width="16"
-                    height="16"
-                    fill="currentColor"
-                  >
-                    <path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z" />
-                  </svg>
-                </button>
-              </div>
-            </div>
-          ))}
-        </div>
+        <GetPoints />
       </div>
-      {/* 积分规则组件 */}
-      <PointsRuleModal 
-         visible={isModalOpen}
-          handleCloseModal={() => setIsModalOpen(false)}
-      />
+    
     </div>
   );
 };

File diff suppressed because it is too large
+ 14 - 129
src/app/(public)/customer/account/page.tsx


+ 104 - 221
src/app/(public)/customer/account/pointlist/page.tsx

@@ -1,201 +1,97 @@
 "use client";
-// import Image from "next/image";
 import React, { useState, useEffect, useRef, useCallback } from "react";
+import Link from "next/link";
+import { clientFetch } from "@/lib/restApiClient";
 
+// 👉 你的 TS 类型(正确)
 interface ListDates {
+  amount: number;
+  balance: number;
+  created_at: string;
+  detail: string;
+  expired_day: number;
+  expired_time: string | null;
   id: number;
-  points: string;
-  title: string;
-  type: string;
-  // desc: string;
-  date: string;
-  // createTime: string;
-  currentPoints: string;
-  // 你自己加字段...
+  order_id: number | null;
+  remaining_points: number;
+  status: number;
+  status_text: string;
+  type: number;
+  type_text: string;
 }
-// 模拟积分明细数据(可替换为接口请求)
-const mockPointsData: ListDates[] = [
-  {
-    id: 1,
-    type: "rewarded",
-    title: "Check In",
-    points: "+10 Points",
-    date: "May 17, 2026 11:07 PM",
-    currentPoints: "My points:100009",
-  },
-  {
-    id: 2,
-    type: "rewarded",
-    title: "Points Return of Redeem Hair Usage",
-    points: "+12000 Points",
-    date: "Oct 23, 2025 1:11 AM",
-    currentPoints: "My points:99999",
-  },
-  {
-    id: 3,
-    type: "used",
-    title: "Points Redeem Hair",
-    points: "-6000 Points",
-    date: "Oct 17, 2025 10:27 PM",
-    currentPoints: "My points:87999",
-  },
-  {
-    id: 4,
-    type: "used",
-    title: "Points Redeem Hair",
-    points: "-6000 Points",
-    date: "Oct 17, 2025 10:25 PM",
-    currentPoints: "My points:93999",
-  },
-  {
-    id: 5,
-    type: "rewarded",
-    title: "Updated by Admin",
-    points: "+99999 Points",
-    date: "Oct 17, 2025 10:24 PM",
-    currentPoints: "My points:99999",
-  },
-  {
-    id: 6,
-    type: "used",
-    title: "Points Redeem Giftcard",
-    points: "-300 Points",
-    date: "Oct 13, 2025 8:41 PM",
-    currentPoints: "My points:0",
-  },
-  {
-    id: 7,
-    type: "rewarded",
-    title: "Reward for Registering",
-    points: "+200 Points",
-    date: "Jun 9, 2025 7:39 PM",
-    currentPoints: "My points:300",
-  },
-  {
-    id: 8,
-    type: "rewarded",
-    title: "Reward for Signing up Newsletter",
-    points: "+100 Points",
-    date: "Jun 9, 2025 7:39 PM",
-    currentPoints: "My points:100",
-  },
-  // 补充测试数据(凑够分页演示)
-  {
-    id: 9,
-    type: "rewarded",
-    title: "Check In",
-    points: "+10 Points",
-    date: "May 16, 2026 10:00 PM",
-    currentPoints: "My points:100000",
-  },
-  {
-    id: 10,
-    type: "used",
-    title: "Points Redeem Accessory",
-    points: "-500 Points",
-    date: "May 15, 2026 9:00 AM",
-    currentPoints: "My points:99990",
-  },
-  {
-    id: 11,
-    type: "rewarded",
-    title: "Review Product",
-    points: "+50 Points",
-    date: "May 14, 2026 8:00 PM",
-    currentPoints: "My points:100490",
-  },
-  {
-    id: 12,
-    type: "rewarded",
-    title: "Refer a Friend",
-    points: "+200 Points",
-    date: "May 13, 2026 7:00 PM",
-    currentPoints: "My points:100440",
-  },
-  {
-    id: 13,
-    type: "used",
-    title: "Points Redeem Cash",
-    points: "-1000 Points",
-    date: "May 12, 2026 6:00 PM",
-    currentPoints: "My points:100240",
-  },
-  {
-    id: 14,
-    type: "rewarded",
-    title: "Check In",
-    points: "+10 Points",
-    date: "May 11, 2026 5:00 PM",
-    currentPoints: "My points:101240",
-  },
-];
 
 const PointsDetails = () => {
-  // 状态管理
-  const [activeTab, setActiveTab] = useState("all"); // 激活标签:all/rewarded/used
-  const [listData, setListData] = useState<ListDates[]>([]); // 展示的列表数据
-  const [page, setPage] = useState(1); // 当前页码
-  const [loading, setLoading] = useState(false); // 加载状态
-  const [hasMore, setHasMore] = useState(true); // 是否有更多数据
-  const listRef = useRef<HTMLDivElement>(null); // 列表容器ref,用于监听滚动
+  // 状态
+  const [activeTab, setActiveTab] = useState<string>(""); // earned / redeemed / 空
+  const [listData, setListData] = useState<ListDates[]>([]);
+  const [page, setPage] = useState<number>(1);
+  const [loading, setLoading] = useState<boolean>(false);
+  const [hasMore, setHasMore] = useState<boolean>(true);
+  const listRef = useRef<HTMLDivElement>(null);
+  const PAGE_SIZE = 10;
 
-  // 每页展示12条
-  const PAGE_SIZE = 12;
-  const fetchData = useCallback((page: number, tab: string): Promise<ListDates[]> => {
-    return new Promise((resolve) => {
-        setTimeout(() => {
-          let data = mockPointsData;
-          const prePage = page - 1;
-          const start = prePage * PAGE_SIZE;
-          const end = page * PAGE_SIZE;
-          if (tab === "rewarded") {
-            data = mockPointsData.filter(
-              (item) => item.type === "rewarded",
-            );
-          } else if (tab === "used") {
-            data = mockPointsData.filter((item) => item.type === "used");
-          }
-          const res = data.slice(start, end);
-          resolve(res);
-        }, 800);
-    });
-  },[]);
-  // 加载更多数据
-  const loadMore = useCallback(() => {
+  // ==============================================
 
+  const loadData = useCallback(async (currentPage: number, tab: string) => {
     setLoading(true);
-    
-    fetchData(page+1,activeTab).then((res:any) => {
+    try {
+      const resp = await clientFetch(
+        `/api/customer/reward-points/history?page=${currentPage}&per_page=${PAGE_SIZE}&amountType=${tab}`,
+      );
 
-        setListData(res);
+      console.log("接口返回:", resp);
 
-        setLoading(false);
-        setPage(page+1);
-        if(res.length < PAGE_SIZE) {
-           setHasMore(false);
+      if (resp.status === 200) {
+        const formData = resp.data?.data || resp.data || { history: [] };
+        const newList = formData.history || [];
+        // 测试数据
+        //  const testList = [...newList, ...newList, ...newList, ...newList];
+        // 分页判断:如果返回不足10条 = 没有更多
+        if (newList.length < PAGE_SIZE) {
+          setHasMore(false);
+        } else {
+          setHasMore(true);
         }
-    });
-  },[fetchData,page,activeTab]);
-  
-  // 初始化
-  useEffect(() => {
-    // 筛选数据
-    fetchData(1,'all').then((result:any)=> {
-        
-        setListData(result);
-        // 判断是否有更多数据
-        setHasMore(!(result.length < PAGE_SIZE));
-    });
-    
+
+        if (currentPage === 1) {
+          setListData(newList);
+        } else {
+          setListData((prev) => [...prev, ...newList]);
+        }
+      }
+    } catch (err) {
+      console.error("加载失败:", err);
+    } finally {
+      setLoading(false);
+    }
   }, []);
 
-  // 监听滚动:触底加载
+  const loadMore = useCallback(() => {
+    console.log("loading:", loading, "hasMore:", hasMore);
+
+    if (loading || !hasMore) return;
+    const nextPage = page + 1;
+    loadData(nextPage, activeTab);
+    setPage(nextPage);
+  }, [loading, hasMore, page, activeTab, loadData]);
+
+  useEffect(() => {
+    // 封装内部异步函数,规范写法
+    const initLoad = async () => {
+      setListData([]); // 切换时清空列表
+      loadData(1, activeTab);
+      setPage(1);
+    };
+    initLoad();
+  }, [activeTab]);
+
   useEffect(() => {
+    const container = listRef.current;
     const handleScroll = () => {
-      if (loading || !hasMore) return;
-      const container = listRef.current;
-      // 滚动到底部的判断:滚动高度 + 可视高度 ≥ 总高度 - 20(阈值)
-      if (container &&
+      // console.log("container:",container,"container.scrollTop:",container.scrollTop,"container.clientHeight:",container.clientHeight,"container.scrollHeight:",container.scrollHeight);
+
+      if (!container) return;
+      if (
         container.scrollTop + container.clientHeight >=
         container.scrollHeight - 20
       ) {
@@ -203,94 +99,82 @@ const PointsDetails = () => {
       }
     };
 
-    const container = listRef.current;
-    if (container) {
-      container.addEventListener("scroll", handleScroll);
-      return () => container.removeEventListener("scroll", handleScroll);
-    }
-  }, [loading, hasMore, loadMore]);
+    container?.addEventListener("scroll", handleScroll);
+    return () => container?.removeEventListener("scroll", handleScroll);
+  }, [loadMore]);
 
   const clickActiveTab = (tab: string) => {
-      setActiveTab(tab);
-      setPage(1);
-      fetchData(1, tab);
-  }
+    setActiveTab(tab);
+  };
 
   return (
     <div className="min-h-screen bg-white">
-      {/* 顶部导航栏 */}
+      {/* 顶部 */}
       <div className="sticky top-0 z-10 bg-white border-b border-gray-100 py-4 px-4 flex items-center">
-        {/* 返回按钮 */}
-        <button className="mr-4">
+        <Link href={"/customer/account/mypoints"} className="mr-4">
           <svg viewBox="0 0 24 24" width="20" height="20" fill="black">
             <path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
           </svg>
-        </button>
-        {/* 标题 */}
+        </Link>
         <h1 className="text-xl font-bold text-black">Points Details</h1>
       </div>
 
-      {/* 标签切换栏 */}
+      {/* Tab */}
       <div className="flex border-b border-gray-200">
         <button
-          onClick={() => clickActiveTab("all")}
+          onClick={() => clickActiveTab("")}
           className={`px-6 py-3 font-medium text-sm ${
-            activeTab === "all"
-              ? "bg-black text-white"
-              : "bg-white text-gray-600 hover:bg-gray-50"
+            activeTab === "" ? "bg-black text-white" : "bg-white text-gray-600"
           }`}
         >
           ALL
         </button>
         <button
-          onClick={() => clickActiveTab("rewarded")}
+          onClick={() => clickActiveTab("earned")}
           className={`px-6 py-3 font-medium text-sm ${
-            activeTab === "rewarded"
+            activeTab === "earned"
               ? "bg-black text-white"
-              : "bg-white text-gray-600 hover:bg-gray-50"
+              : "bg-white text-gray-600"
           }`}
         >
           REWARDED
         </button>
         <button
-          onClick={() => clickActiveTab("used")}
+          onClick={() => clickActiveTab("redeemed")}
           className={`px-6 py-3 font-medium text-sm ${
-            activeTab === "used"
+            activeTab === "redeemed"
               ? "bg-black text-white"
-              : "bg-white text-gray-600 hover:bg-gray-50"
+              : "bg-white text-gray-600"
           }`}
         >
           USED
         </button>
       </div>
 
-      {/* 积分明细列表(带滚动监听) */}
+      {/* 列表 */}
       <div ref={listRef} className="h-[calc(100vh-120px)] overflow-y-auto">
         {listData.length > 0 ? (
           <div className="divide-y divide-gray-100">
             {listData.map((item) => (
               <div key={item.id} className="px-4 py-4">
-                {/* 左侧:标题+日期 */}
                 <div className="flex justify-between items-start">
                   <div>
                     <h3 className="text-base font-medium text-black">
-                      {item.title}
+                      {item.detail}
                     </h3>
-                    <p className="text-xs text-gray-500 mt-1">{item.date}</p>
+                    <p className="text-xs text-gray-500 mt-1">
+                      {item.created_at}
+                    </p>
                   </div>
-                  {/* 右侧:积分变动 */}
                   <div className="text-right">
                     <p
-                      className={`text-base font-medium ${
-                        item.points.startsWith("+")
-                          ? "text-black"
-                          : "text-black"
-                      }`}
+                      className={`text-base font-medium ${item.amount >= 0 ? "text-green-600" : "text-black"}`}
                     >
-                      {item.points}
+                      {item.amount >= 0 ? `+${item.amount}` : item.amount}{" "}
+                      Points
                     </p>
                     <p className="text-xs text-gray-500 mt-1">
-                      {item.currentPoints}
+                      My points: {item.balance}
                     </p>
                   </div>
                 </div>
@@ -298,16 +182,15 @@ const PointsDetails = () => {
             ))}
           </div>
         ) : (
-          // 空数据提示
           <div className="flex items-center justify-center h-32">
             <p className="text-gray-500 text-sm">NO DATA</p>
           </div>
         )}
 
-        {/* 加载中/无更多提示 */}
+        {/* 加载提示 */}
         <div className="px-4 py-3 text-center text-sm">
           {loading && <p className="text-gray-500">Loading...</p>}
-          {!loading && !hasMore && (
+          {!loading && !hasMore && listData.length > 0 && (
             <p className="text-gray-500">NO MORE DATA</p>
           )}
         </div>

+ 58 - 0
src/app/(public)/customer/address/_components/DefaultAddress.tsx

@@ -0,0 +1,58 @@
+"use client";
+import { useState, useEffect } from "react";
+import { clientFetch } from "@/lib/restApiClient";
+import Link from "next/link";
+export default function DefaultAddress() {
+  const [addressDatas, setAddressDatas] = useState<any[]>([]); // 加 any[] 提示
+
+  useEffect(() => {
+    const load = async () => {
+      // const page = 1;
+      const resp = await clientFetch(
+        `/api/customer/token/address`, ///api/shop/customer-addresses?page=${page}
+      );
+      console.log("完整返回:", resp.data);
+      resp.data.data= Array.isArray(resp.data.data) ? resp.data.data : [];
+      const sortedList = [...resp.data.data].sort((a, b) => {
+        // true(1) 排在 false(0) 前面
+        if (a.default_address !== b.default_address) {
+          return b.default_address ? 1 : -1;
+        }
+        // 相同状态,保留原顺序(返回0即可)
+        return 0;
+      });
+      setAddressDatas(sortedList);
+      console.log("aaaa",sortedList,addressDatas);
+      
+    };
+    load();
+  }, []);
+  return (
+    <>
+      {(addressDatas || []).map((item) => (
+        <div key={item.id} className="mb-6">
+          {item.default_address ? (
+            <h2 className="text-xl font-bold uppercase mb-3">
+              Default Billing Address
+            </h2>
+          ) : null}
+          <div className="border border-gray-200 p-4">
+            <p className="mb-1">{item.first_name + item.last_name}</p>
+            <p className="mb-1">{item.address}</p>
+            <p className="mb-1">{item.city + "," + item.postcode}</p>
+            <p className="mb-1">{item.country}</p>
+            <p className="mb-4">T: {item.phone}</p>
+            <div className="text-right">
+              <Link
+                href={`/customer/address/eidt/${item.id}`}
+                className="text-blue-600 underline hover:text-blue-800"
+              >
+                Change Billing Address
+              </Link>
+            </div>
+          </div>
+        </div>
+      ))}
+    </>
+  );
+}

+ 304 - 0
src/app/(public)/customer/address/eidt/[id]/page.tsx

@@ -0,0 +1,304 @@
+"use client";
+import { useState, useEffect, useMemo } from "react";
+import { useParams } from "next/navigation";
+import Link from "next/link";
+import { clientFetch } from "@/lib/restApiClient";
+import Select from "@/components/theme/ui/Select";
+import { useForm, FormProvider, Controller } from "react-hook-form";
+import CustomerAddressPhone from "../../new/_components/CustomerAddressPhone";
+const CustomerAddressEditPage = () => {
+  // 定义表单类型(严格对齐你要的字段)
+  interface AddressFormData {
+    first_name: string;
+    last_name: string;
+    email: string;
+    phone: string;
+    address: string[];
+    country: string;
+    state: string;
+    city: string;
+    postcode: string;
+    default_address: number;
+  }
+  const { id } = useParams();
+  // const [form, setForm] = useState({});
+  // 1. 根据ID获取地址详情
+  console.log("addressid:", id);
+  const COUNTRIES = [
+    { id: 1, code: "AF", name: "Afghanistan" },
+    { id: 2, code: "AX", name: "Åland Islands" },
+    { id: 3, code: "AL", name: "Albania" },
+    { id: 4, code: "DZ", name: "Algeria" },
+    { id: 49, code: "CN", name: "China" },
+    { id: 244, code: "US", name: "United States" },
+  ];
+  const countriesOptions = useMemo(() => {
+    return COUNTRIES.map((country) => ({
+      value: country.code,
+      label: country.name,
+      id: String(country.id),
+    }));
+  }, [COUNTRIES]);
+  const [actionType, setActionType] = useState<"save" | "delete">("save");
+    // 修改地址到接口
+    const UpdataAddress = async (formData: AddressFormData) => {
+      const res = await clientFetch(`/api/customer/token/address?id=${id}`, {
+        method: "PUT",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify(formData),
+      });
+      const json = await res;
+      console.log("提交成功,返回结果:", json);
+      if(json?.data?.success){
+          window.location.reload();
+      }else{
+        alert("error");
+      }
+      return json;
+    };
+  // 删除地址接口
+  const  DeleteAddress =async ()=>{
+     const res = await clientFetch(`/api/customer/token/address?id=${id}`, {
+        method: "DELETE",
+        headers: {
+          "Content-Type": "application/json",
+        }
+      });
+      const json = await res;
+      console.log("删除成功,返回结果:", json);
+  }
+  // 表单提交
+  const addressFormOnSubmit = async (formData: AddressFormData) => {
+    console.log("actionType",actionType);
+    
+    if (actionType === "save") {
+      UpdataAddress(formData);
+    } else {
+      DeleteAddress();
+      console.log("执行删除");
+    }
+  };
+
+  // 表单实例(严格对齐你需要的字段)
+  const addressForm = useForm<AddressFormData>({
+    mode: "onBlur",
+    reValidateMode: "onChange",
+    defaultValues: {
+      first_name: "",
+      last_name: "",
+      email: "",
+      phone: "",
+      address: ["", ""], // 数组格式
+      country: "",
+      state: "",
+      city: "",
+      postcode: "",
+      default_address: 0,
+    },
+  });
+
+  const {
+    reset,
+    register,
+    handleSubmit,
+    formState: { errors },
+  } = addressForm;
+  useEffect(() => {
+    if (!id) return;
+    const load = async () => {
+      const resp = await clientFetch(`/api/customer/token/address?id=${id}`);
+      console.log("【完整接口响应】", resp);
+
+      // 自动兼容后端 99% 的返回格式
+      const formData = resp.data?.data || resp.data || {};
+      console.log("✅ 回填数据:", formData);
+
+      reset(formData);
+    };
+    load();
+  }, [id, reset]);
+  return (
+    <div className="w-full h-full">
+      <div  className="bg-[#fff] pr-2.5 pl-2.5 w-full text-center text-base h-11 leading-11 font-semibold relative text-[#0b0b0b]">
+        <Link
+          href={"/customer/address"}
+          // onClick={() => window.history.back()}
+          className="absolute left-2.5 text-2xl inline-block top-1/2  -translate-y-1/2"
+        >
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="24"
+            height="24"
+            viewBox="0 0 24 24"
+            fill="none"
+          >
+            <path
+              stroke="rgba(0, 0, 0, 1)"
+              stroke-width="1.2"
+              stroke-linejoin="round"
+              stroke-linecap="round"
+              d="M15 18L9 12L15 6"
+            ></path>
+          </svg>
+        </Link>
+        <span className="vipReturnTitles">ADDRESS EDIT</span>
+      </div>
+      <div className="max-w-md mx-auto bg-[#f8f8f8]">
+        <FormProvider {...addressForm}>
+          <form
+            className="overflow-hidden"
+            onSubmit={handleSubmit(addressFormOnSubmit)}
+          >
+            <div className="bg-[#f8f8f8] p-[20px_10px]">
+              {/* 姓名 */}
+              <div className="text-start">*Name</div>
+              <div className="flex justify-between items-center gap-3">
+                {/* firstname */}
+                <input
+                  type="text"
+                  placeholder="firstname"
+                  {...register("first_name", {
+                    required: "First name is required",
+                  })}
+                  className="w-43 h-11 rounded-sm border border-solid border-[rgba(102,102,102,1)] indent-4 text-[#a6a6a6] text-base leading-11 font-normal"
+                />
+                {/* lastname */}
+                <input
+                  type="text"
+                  placeholder="last_name"
+                  {...register("last_name", {
+                    required: "Last name is required",
+                  })}
+                  className="w-43 h-11 rounded-sm border border-solid border-[rgba(102,102,102,1)] indent-4 text-[#a6a6a6] text-base leading-11 font-normal box-[unset]"
+                />
+              </div>
+              {errors.first_name && (
+                <p className="text-red-500 text-sm mt-1">
+                  {errors.first_name.message}
+                </p>
+              )}
+              {errors.last_name && (
+                <p className="text-red-500 text-sm mt-1">
+                  {errors.last_name.message}
+                </p>
+              )}
+
+              {/* 手机号 */}
+              <CustomerAddressPhone />
+              <Controller
+                control={addressForm.control}
+                name="country"
+                rules={{ required: "Country is required" }}
+                render={({ field }) => {
+                  return (
+                    <Select
+                      placeholder="Country/Region"
+                      defaultValue={field.value}
+                      onChange={field.onChange}
+                      onBlur={field.onBlur}
+                      name={field.name}
+                      options={countriesOptions}
+                      error={errors.country?.message as string}
+                    />
+                  );
+                }}
+              />
+              {/* <input {...register("country")} /> */}
+              {/* 邮箱 */}
+              <div className="mt-4">
+                <label>Email Address*</label>
+                <input
+                  type="email"
+                  placeholder="Email"
+                  {...register("email", {
+                    required: "Email is required",
+                    pattern: {
+                      value: /^\S+@\S+\.\S+$/,
+                      message: "Invalid email",
+                    },
+                  })}
+                  className="w-full h-11 rounded-sm border border-[rgba(102,102,102,1)] indent-4 text-base mt-1"
+                />
+                {errors.email && (
+                  <p className="text-red-500 text-sm mt-1">
+                    {errors.email.message}
+                  </p>
+                )}
+              </div>
+
+              {/* 邮编 */}
+              <div className="mt-4">
+                <label>Zip/Postal Code</label>
+                <input
+                  type="text"
+                  placeholder="Postcode"
+                  {...register("postcode")}
+                  className="w-full h-11 rounded-sm border border-[rgba(102,102,102,1)] indent-4 text-base mt-1"
+                />
+              </div>
+
+              {/* 省/州 */}
+              <div className="mt-4">
+                <label>State / Province</label>
+                <input
+                  type="text"
+                  placeholder="State"
+                  {...register("state")}
+                  className="w-full h-11 rounded-sm border border-[rgba(102,102,102,1)] indent-4 text-base mt-1"
+                />
+              </div>
+
+              {/* 城市 */}
+              <div className="mt-4">
+                <label>City</label>
+                <input
+                  type="text"
+                  placeholder="City"
+                  {...register("city")}
+                  className="w-full h-11 rounded-sm border border-[rgba(102,102,102,1)] indent-4 text-base mt-1"
+                />
+              </div>
+
+              {/* 默认地址 */}
+              <label className="flex items-center gap-2 cursor-pointer mt-4">
+                <input
+                  type="checkbox"
+                  {...register("default_address", { valueAsNumber: true })}
+                  className="w-5 h-5"
+                />
+                <span>Use as my default address</span>
+              </label>
+
+              {/* 提交按钮 */}
+              <button
+                className="w-full h-11 leading-11 bg-[#1bbc9b] text-white text-center mt-6 rounded"
+                type="button"
+                onClick={() => {
+                  setActionType("save");
+                  document.getElementById("realSubmit")?.click(); 
+                }}
+              >
+                Save Address
+              </button>
+              <button
+                className="w-full h-11 leading-11 bg-[#1bbc9b] text-white text-center mt-6 rounded"
+                type="button"
+                onClick={() => {
+                  if (!confirm("确定删除该地址?")) return;
+                  setActionType("delete");
+                  document.getElementById("realSubmit")?.click(); 
+                }}
+              >
+                Delete Address
+              </button>
+              <button id="realSubmit" type="submit" hidden />
+            </div>
+          </form>
+        </FormProvider>
+      </div>
+    </div>
+  );
+};
+export default CustomerAddressEditPage;

+ 99 - 0
src/app/(public)/customer/address/new/_components/CustomerAddressPhone.tsx

@@ -0,0 +1,99 @@
+"use client";
+import {
+  formatFinalPhoneValue,
+  getPhoneCodeBycountryCode,
+} from "@/components/theme/ui/PhoneNumberInput/phoneCodeMetaData";
+
+import {  useEffect, useState } from "react";
+// import { useQuery } from "@apollo/client/react";
+import { useFormContext, Controller, useWatch } from "react-hook-form";
+import { IS_VALID_FULL_PHONE, IS_VALID_PHONECODE } from "@utils/constants";
+// import { GET_COUNTRY_STATES } from "@/graphql";
+import PhoneNumberInput from "@/components/theme/ui/PhoneNumberInput/PhoneNumberInput";
+
+export default function CustomerAddressPhone() {
+  const {
+    getValues,
+    setValue,
+    formState: {  },
+    control,
+  } = useFormContext();
+
+  // 🔴 修复1:监听父表单的 country 字段(原 shippingCountry 是错误字段)
+  const selectedCountryCode = useWatch({
+    control,
+    name: "country",
+  });
+
+  // 是否用户手动选择过区号
+  const [hasUserSelectedPhoneCode, setHasUserSelectedPhoneCode] =
+    useState(false);
+
+  // 国家切换 → 自动同步电话区号(兼容 reset 回填)
+  useEffect(() => {
+    const currentPhone = getValues("phone");
+    const regPhoneCode = IS_VALID_PHONECODE;
+
+    if (currentPhone && !hasUserSelectedPhoneCode) {
+      const newPhoneCode = getPhoneCodeBycountryCode(selectedCountryCode);
+      const codeMatch = regPhoneCode.exec(currentPhone);
+      let purePhone = currentPhone;
+
+      // 剥离已有区号
+      if (codeMatch) {
+        purePhone = currentPhone.replace(codeMatch[0], "");
+      }
+
+      // 新区号和原有区号不一致才更新
+      if (codeMatch && newPhoneCode !== codeMatch[0].trim()) {
+        setValue("phone", formatFinalPhoneValue(newPhoneCode, purePhone));
+      }
+    }
+  }, [selectedCountryCode, hasUserSelectedPhoneCode, getValues, setValue]);
+
+  // const { data: statesData, loading: statesLoading } = useQuery(
+  //   GET_COUNTRY_STATES,
+  //   {
+  //     variables: {},
+  //     skip: !selectedCountryCode,
+  //   },
+  // );
+
+  return (
+    <div className="w-full mt-4">
+      <label className="text-ly-12 block mb-4 font-semibold">
+        Phone number *
+      </label>
+      {/* ✅ Controller 本身写法没问题,保留即可,字段 name="phone" 和父表单一致 */}
+      <Controller
+        name="phone"
+        control={control}
+        rules={{
+          required: "Phone Number is required",
+          pattern: {
+            value: IS_VALID_FULL_PHONE,
+            message: "Invalid Phone Number",
+          },
+        }}
+        render={({ field, fieldState }) => {
+          return (
+            <PhoneNumberInput
+              placeholder="Please Input Your Phone Number"
+              value={field.value}
+              onChange={field.onChange}
+              onBlur={field.onBlur}
+              name={field.name}
+              ref={field.ref}
+              error={fieldState.error?.message}
+              countryCode={selectedCountryCode}
+              hasUserSelectedPhoneCode={hasUserSelectedPhoneCode}
+              onHasUserSelectedPhoneCode={() =>
+                setHasUserSelectedPhoneCode(true)
+              }
+            />
+          );
+        }}
+      />
+    </div>
+  );
+}

+ 222 - 0
src/app/(public)/customer/address/new/_components/NewAddressDefault.tsx

@@ -0,0 +1,222 @@
+"use client";
+import { useForm, FormProvider } from "react-hook-form";
+import { useMemo } from "react";
+import CustomerAddressPhone from "./CustomerAddressPhone";
+import Select from "@/components/theme/ui/Select";
+import { clientFetch } from "@/lib/restApiClient";
+import { useRouter } from "next/navigation";
+// 定义表单类型(严格对齐你要的字段)
+interface AddressFormData {
+  first_name: string;
+  last_name: string;
+  email: string;
+  phone: string;
+  address: string[];
+  country: string;
+  state: string;
+  city: string;
+  postcode: string;
+  default_address: number;
+}
+
+export default function NewAddressDefault({ countries }: { countries: any[] }) {
+  // 国家选项
+  const countriesOptions = useMemo(() => {
+    return countries.map((country) => ({
+      value: country.code,
+      label: country.name,
+      id: String(country.id),
+    }));
+  }, [countries]);
+   const router = useRouter();
+  // 提交地址到接口
+  const AddAdress = async (formData: AddressFormData) => {
+    const res = await clientFetch("/api/customer/token/address", {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify(formData),
+    });
+    const json = await res;
+    console.log("提交成功,返回结果:", json);
+    if(json?.status== 201){
+        router.push("/customer/address/");
+    }else{
+      alert("error");
+    }
+    return json;
+  };
+
+  // 表单提交
+  const addressFormOnSubmit = async (formData: AddressFormData) => {
+    console.log("最终提交给后端的数据:", formData);
+    await AddAdress(formData);
+  };
+
+  // 表单实例(严格对齐你需要的字段)
+  const addressForm = useForm<AddressFormData>({
+    mode: "onBlur",
+    reValidateMode: "onChange",
+    defaultValues: {
+      first_name: "",
+      last_name: "",
+      email: "",
+      phone: "",
+      address: ["", ""], // 数组格式
+      country: "",
+      state: "",
+      city: "",
+      postcode: "",
+      default_address: 0,
+    },
+  });
+
+  const {
+    register,
+    handleSubmit,
+    formState: {  },
+  } = addressForm;
+
+  return (
+    <div className="w-full h-full">
+      <FormProvider {...addressForm}>
+        <form
+          className="overflow-hidden"
+          onSubmit={handleSubmit(addressFormOnSubmit)}
+        >
+          {/* 头部 */}
+          <div className="bg-[#fff] px-2.5 w-full text-center text-base h-11 leading-11 font-semibold relative text-[#0b0b0b]">
+            <div
+              onClick={() => window.history.back()}
+              className="absolute left-2.5 text-2xl inline-block top-1/2 -translate-y-1/2"
+            >
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                width="24"
+                height="24"
+                viewBox="0 0 24 24"
+                fill="none"
+              >
+                <path
+                  stroke="rgba(0, 0, 0, 1)"
+                  strokeWidth="1.2"
+                  strokeLinejoin="round"
+                  strokeLinecap="round"
+                  d="M15 18L9 12L15 6"
+                ></path>
+              </svg>
+            </div>
+            <span>Add New Address</span>
+          </div>
+
+          <div className="bg-[#f8f8f8] p-[20px_10px]">
+            {/* 姓名 */}
+             <div className="text-start">*Name</div>
+            <div className="flex justify-between items-center gap-3">
+              {/* firstname */}
+              <input
+                type="text"
+                placeholder="firstname"
+                {...register("first_name", {
+                  required: "First name is required",
+                })}
+                className="w-43 h-11 rounded-sm border border-solid border-[rgba(102,102,102,1)] indent-4 text-[#a6a6a6] text-base leading-11 font-normal"
+              />
+              {/* lastname */}
+              <input
+                type="text"
+                placeholder="last_name"
+                {...register("last_name", {
+                  required: "Last name is required",
+                })}
+                className="w-43 h-11 rounded-sm border border-solid border-[rgba(102,102,102,1)] indent-4 text-[#a6a6a6] text-base leading-11 font-normal box-[unset]"
+              />
+            </div>
+            {errors.first_name && <p className="text-red-500 text-sm mt-1">{errors.first_name.message}</p>}
+            {errors.last_name && <p className="text-red-500 text-sm mt-1">{errors.last_name.message}</p>}
+
+            {/* 手机号 */}
+            <CustomerAddressPhone />
+
+            {/* 国家 */}
+            <div className="mt-4">
+              <Select
+                placeholder="Country/Region"
+                {...register("country", { required: "Country is required" })}
+                options={countriesOptions}
+                error={errors.country?.message as string}
+              />
+            </div>
+
+            {/* 邮箱 */}
+            <div className="mt-4">
+              <label>Email Address*</label>
+              <input
+                type="email"
+                placeholder="Email"
+                {...register("email", {
+                  required: "Email is required",
+                  pattern: { value: /^\S+@\S+\.\S+$/, message: "Invalid email" },
+                })}
+                className="w-full h-11 rounded-sm border border-[rgba(102,102,102,1)] indent-4 text-base mt-1"
+              />
+              {errors.email && <p className="text-red-500 text-sm mt-1">{errors.email.message}</p>}
+            </div>
+
+            {/* 邮编 */}
+            <div className="mt-4">
+              <label>Zip/Postal Code</label>
+              <input
+                type="text"
+                placeholder="Postcode"
+                {...register("postcode")}
+                className="w-full h-11 rounded-sm border border-[rgba(102,102,102,1)] indent-4 text-base mt-1"
+              />
+            </div>
+
+            {/* 省/州 */}
+            <div className="mt-4">
+              <label>State / Province</label>
+              <input
+                type="text"
+                placeholder="State"
+                {...register("state")}
+                className="w-full h-11 rounded-sm border border-[rgba(102,102,102,1)] indent-4 text-base mt-1"
+              />
+            </div>
+
+            {/* 城市 */}
+            <div className="mt-4">
+              <label>City</label>
+              <input
+                type="text"
+                placeholder="City"
+                {...register("city")}
+                className="w-full h-11 rounded-sm border border-[rgba(102,102,102,1)] indent-4 text-base mt-1"
+              />
+            </div>
+
+            {/* 默认地址 */}
+            <label className="flex items-center gap-2 cursor-pointer mt-4">
+              <input
+                type="checkbox"
+                {...register("default_address", { valueAsNumber: true })}
+                className="w-5 h-5"
+              />
+              <span>Use as my default address</span>
+            </label>
+
+            {/* 提交按钮 */}
+            <button
+              className="w-full h-11 leading-11 bg-[#1bbc9b] text-white text-center mt-6 rounded"
+              type="submit"
+            >
+              Save Address
+            </button>
+          </div>
+        </form>
+      </FormProvider>
+    </div>
+  );
+}

File diff suppressed because it is too large
+ 1286 - 169
src/app/(public)/customer/address/new/page.tsx


+ 13 - 8
src/app/(public)/customer/address/page.tsx

@@ -1,8 +1,7 @@
-"use client";
 import React from "react";
-// import { useState } from "react";
-// import { useEffect } from "react";
-const CustomerAddressPage = () => {
+import Link from "next/link";
+import  DefaultAddress from "./_components/DefaultAddress";
+const CustomerAddressPage = async () => {
   // 模拟地址数据(可根据实际需求替换)
   const addressData = {
     billing: {
@@ -22,11 +21,12 @@ const CustomerAddressPage = () => {
       phone: "2123165641",
     },
   };
+ 
   return (
     <div className="w-full h-full">
       <div className="bg-[#fff] pr-2.5 pl-2.5 w-full text-center text-base h-11 leading-11 font-semibold relative text-[#0b0b0b]">
-        <div
-          onClick={() => window.history.back()}
+        <Link href={"/customer/account"}
+          // onClick={() => window.history.back()}
           className="absolute left-2.5 text-2xl inline-block top-1/2  -translate-y-1/2"
         >
           <svg
@@ -44,10 +44,11 @@ const CustomerAddressPage = () => {
               d="M15 18L9 12L15 6"
             ></path>
           </svg>
-        </div>
+        </Link>
         <span className="vipReturnTitles">SHIPPING ADDRESS</span>
       </div>
       <div className="max-w-md mx-auto p-4 bg-[#f8f8f8]">
+        <DefaultAddress  />
         {/* 账单地址模块 */}
         <div className="mb-6">
           <h2 className="text-xl font-bold uppercase mb-3">
@@ -70,7 +71,7 @@ const CustomerAddressPage = () => {
             </div>
           </div>
         </div>
-
+        
         {/* 收货地址模块 */}
         <div>
           <h2 className="text-xl font-bold uppercase mb-3">
@@ -93,7 +94,11 @@ const CustomerAddressPage = () => {
             </div>
           </div>
         </div>
+        <Link href={"/customer/address/new"} className="text-center bg-[#000] block text-white mt-5  width-full height-11 leading-11 text-lg" >
+          + New Address
+        </Link>
       </div>
+    
     </div>
   );
 };

+ 302 - 0
src/app/(public)/customer/order/history/_components/OrderHistory.tsx

@@ -0,0 +1,302 @@
+"use client";
+import { useEffect, useState, useRef, useCallback } from "react";
+import { useRouter } from "next/navigation";
+import Link from "next/link";
+import { useSearchParams } from "next/navigation";
+
+// 模拟订单数据
+const mockOrders = [
+  {
+    id: 600217097,
+    date: "3/4/2026",
+    status: "Processing",
+    items: [
+      {
+        name: "Highlight Wigs For Women Headband Wigs with Blonde Highlights",
+        specs: "10, #022, Straight, 5 Free Headbands",
+        price: "88 points + $154.05x1",
+      },
+    ],
+    progress: "Preparing Order",
+    total: "88 Points + $158.67",
+  },
+  {
+    id: 100055662,
+    date: "3/3/2026",
+    status: "Processing",
+    items: [
+      {
+        name: "Alipearl Lace Front Wig 180% Density Body Wave Human Hair Wigs Pre...",
+        specs: "10+180%, Medium, No Adjustable Band",
+        price: "$128.82x1",
+      },
+    ],
+    progress: "Preparing Order",
+    total: "$132.68",
+  },
+  {
+    id: 600217055,
+    date: "3/3/2026",
+    status: "Processing",
+    items: [
+      {
+        name: "3 Bundles Deep Wave Hair With 4*4 Lace Closure Alipearl Brazilian Hair",
+        specs: "8 8 8, Free Part, 8",
+        price: "$142.14x1",
+      },
+      {
+        name: "Princess Braids Wig Blonde Barbie Pre-Styled Highlight Wig 13×4 Front Wig",
+        specs: "12",
+        price: "$279.32x1",
+        image:
+          "https://cdn.alipearlhair.com/media/catalog/product/cache/6/thumbnail/750x1000/9df78eab33525d08d6e5fb8d27136e95/b/o/body_wave_lace_front_wig_2.jpg", // 替换为实际图片路径
+      },
+      {
+        name: "Deep Wave Wig 100 Human Hair Swiss Lace Wig Lace Front Wig",
+        specs: "10, Medium, 100%",
+        price: "$252.00x1",
+      },
+      {
+        name: "Braids Wig Blonde Barbie Pre-Styled Highlight Wig 13×4 Front Wig",
+        specs: "12",
+        price: "6000 points + $279.32x1",
+        image:
+          "https://cdn.alipearlhair.com/media/catalog/product/cache/6/thumbnail/750x1000/9df78eab33525d08d6e5fb8d27136e95/b/o/body_wave_lace_front_wig_2.jpg", // 替换为实际图片路径
+      },
+      {
+        name: "random free wigs-2",
+        specs: "",
+        price: "$0.00x1",
+      },
+    ],
+    progress: "Preparing Order",
+    discount: "-$80.00",
+    total: "$872.78",
+  },
+];
+
+// 标签列表
+const tabs = [
+  { key: "all", label: "All" },
+  { key: "processing", label: "Processing" },
+  { key: "complete", label: "Complete" },
+  { key: "Payment", label: "Payment Review" }, // 补充模拟标签,测试滚动
+];
+
+export default function OrderHistory() {
+  const router = useRouter();
+  const [activeTab, setActiveTab] = useState("all");
+  const [filteredOrders, setFilteredOrders] = useState(mockOrders);
+  // 组件内新增
+  const searchParams = useSearchParams();
+  const lastHashRef = useRef<string>("");
+  // 模拟接口请求:不同标签传不同参数
+  const fetchOrders = (tabKey: string) => {
+    console.log("请求接口参数:", tabKey); // 实际项目替换为真实接口调用
+    // 这里简化逻辑,仅过滤Processing状态(实际接口返回对应数据)
+    if (tabKey === "processing") {
+      setFilteredOrders(mockOrders.filter((o) => o.status === "Processing"));
+    } else if (tabKey === "complete") {
+      setFilteredOrders([]); // 模拟无完成订单
+    } else {
+      setFilteredOrders(mockOrders);
+    }
+  };
+  // 把更新状态+请求的逻辑抽离,Effect 只调用这个函数,不直接写setState
+  const updateTabByHash = useCallback(
+    (hashKey: string) => {
+      const validTab = tabs.find((t) => t.key === hashKey.toLowerCase());
+      const targetKey = validTab ? validTab.key : "all";
+      // setState 全部放在回调函数里,不在 useEffect 顶层同步执行
+      setActiveTab(targetKey);
+      fetchOrders(targetKey);
+    },
+    [tabs, fetchOrders],
+  );
+
+  // 监听哈希变化,Effect 内部无任何直接setState
+  useEffect(() => {
+    const currentHash = searchParams.get("") || "all";
+    // 对比上一次缓存的hash,避免重复执行
+    if (lastHashRef.current !== currentHash) {
+      lastHashRef.current = currentHash;
+      // 仅调用抽离好的函数,Effect内部没有setActiveTab
+      updateTabByHash(currentHash);
+    }
+  }, [searchParams, updateTabByHash]);
+
+  // 点击标签切换
+  const handleTabClick = (tabKey: string) => {
+    // window.location.hash = tabKey; // 更新URL哈希
+    router.push(`#${tabKey}`, { scroll: false });
+    setActiveTab(tabKey);
+    fetchOrders(tabKey);
+  };
+
+  return (
+    <>
+      <div className="min-h-screen bg-gray-50">
+        {/* 顶部返回栏 + 标题 */}
+        <Link href={"/customer/account"} className="sticky top-0 z-20 bg-white px-4 py-3 border-b flex justify-between items-center w-full">
+          <button className="text-black text-xl mr-4">
+            <svg
+              className="w-6 h-6"
+              viewBox="0 0 1024 1024"
+              version="1.1"
+              xmlns="http://www.w3.org/2000/svg"
+              p-id="111033"
+              width="200"
+              height="200"
+            >
+              <path
+                d="M391.466667 512l339.882666-339.882667a46.592 46.592 0 1 0-65.877333-65.877333L292.693333 479.061333a46.592 46.592 0 0 0 0 65.877334l372.821334 372.821333a46.592 46.592 0 1 0 65.877333-65.877333L391.466667 512z"
+                p-id="111034"
+              ></path>
+            </svg>
+          </button>
+          <h1 className="text-xl font-bold mx-auto">Orders</h1>
+          <div className="w-6"></div>
+        </Link>
+
+        {/* 标签栏(固定顶部 + 横向滚动) */}
+        <div className="sticky top-14 z-10 bg-white px-4 py-2 border-b">
+          <div className="flex items-center space-x-6 overflow-x-auto pb-1 scrollbar-hide">
+            {tabs.map((tab) => (
+              <button
+                key={tab.key}
+                onClick={() => handleTabClick(tab.key)}
+                className={`whitespace-nowrap py-1 px-3 rounded-full text-sm font-medium transition-colors
+                ${
+                  activeTab === tab.key
+                    ? "bg-black text-white"
+                    : "text-gray-600 hover:bg-gray-100"
+                }`}
+              >
+                {tab.label}
+              </button>
+            ))}
+          </div>
+        </div>
+
+        {/* 订单列表 */}
+        <div className="px-4 py-3">
+          {filteredOrders.length === 0 ? (
+            <div className="text-center py-8 text-gray-500">
+              No orders found
+            </div>
+          ) : (
+            filteredOrders.map((order) => (
+              <div
+                key={order.id}
+                className="bg-white rounded-lg p-4 mb-4 border"
+              >
+                {/* 订单状态 + 编号 + 日期 */}
+                <div className="mb-3">
+                  <p className="font-medium text-gray-800">• {order.status}</p>
+                  <div className="flex justify-between text-sm text-gray-600 mt-1">
+                    <span>Order Number: {order.id}</span>
+                    <span>Date: {order.date}</span>
+                  </div>
+                </div>
+
+                {/* 订单商品列表 */}
+                <div className="mb-4 space-y-3">
+                  {order.items.map((item, idx) => (
+                    <div key={idx} className="flex gap-3">
+                      {/* 商品图片(有则显示,无则占位) */}
+                      {item.image ? (
+                        <img
+                          src={item.image}
+                          alt={item.name}
+                          className="w-22.5 h-22.5 object-cover rounded"
+                        />
+                      ) : (
+                        <div className="w-22.5 h-22.5 bg-gray-100 rounded"></div>
+                      )}
+                      {/* 商品信息 */}
+                      <div className="flex-1">
+                        <p className="text-sm text-gray-800 line-clamp-2">
+                          {item.name}
+                        </p>
+                        {item.specs && (
+                          <p className="text-xs text-gray-500 mt-1">
+                            {item.specs}
+                          </p>
+                        )}
+                        <p className="text-xs text-gray-600 mt-1">
+                          {item.price}
+                        </p>
+                      </div>
+                    </div>
+                  ))}
+                </div>
+
+                {/* 订单进度条 */}
+                <Link
+                  href={`/customer/order/track/order_id/${order.id}`}
+                  className="mb-4 h-8 bg-gray-100 rounded-full h-2 flex items-center px-2"
+                >
+                  <span className="ml-2 text-xs text-gray-700 flex-1 flex justify-between">
+                    {order.progress}
+                    <span>
+                      <svg
+                        className="w-4 h-4"
+                        viewBox="0 0 1024 1024"
+                        version="1.1"
+                        xmlns="http://www.w3.org/2000/svg"
+                        p-id="3784"
+                        width="200"
+                        height="200"
+                      >
+                        <path
+                          d="M761.056 532.128c0.512-0.992 1.344-1.824 1.792-2.848 8.8-18.304 5.92-40.704-9.664-55.424L399.936 139.744a48 48 0 0 0-65.984 69.76l316.96 299.84L335.2 813.632a48 48 0 0 0 66.624 69.12l350.048-337.376c0.672-0.672 0.928-1.6 1.6-2.304 0.512-0.48 1.056-0.832 1.568-1.344 2.72-2.848 4.16-6.336 6.016-9.6z"
+                          p-id="3785"
+                        ></path>
+                      </svg>
+                    </span>
+                  </span>
+                </Link>
+
+                {/* 折扣 + 总计 + 操作按钮 */}
+                <div className="text-right text-sm">
+                  {order.discount && (
+                    <p className="text-red-500 flex items-center justify-between">
+                      <span>Discount</span> <span>{order.discount}</span>
+                    </p>
+                  )}
+                  <p className="font-bold mt-1 flex items-center justify-between">
+                    <span>
+                      Order Total ({order.items.length} item
+                      {order.items.length > 1 ? "s" : ""})
+                    </span>{" "}
+                    <span>{order.total}</span>
+                  </p>
+
+                  <div className="flex justify-between mt-3">
+                    <Link
+                      href={`/customer/order/view/order_id/${order.id}`}
+                      className="text-black-600 text-sm underline"
+                    >
+                      View Order
+                    </Link>
+                    <Link
+                      href={`/customer/order/track/order_id/${order.id}`}
+                      state={{
+                        addressData: {
+                          /* 你的地址对象 */
+                        },
+                      }}
+                      className="border border-gray-300 rounded-full px-4 py-1 text-sm text-gray-800 hover:bg-gray-50"
+                    >
+                      Track Order
+                    </Link>
+                  </div>
+                </div>
+              </div>
+            ))
+          )}
+        </div>
+      </div>
+    </>
+  );
+}

+ 10 - 0
src/app/(public)/customer/order/history/page.tsx

@@ -0,0 +1,10 @@
+
+import OrderHistory from "./_components/OrderHistory"
+export default function OderIndex(){
+
+  return (
+    <>
+     <OrderHistory />
+    </>
+  );
+}

+ 47 - 0
src/app/(public)/customer/order/track/order_id/[id]/_components/TrackDetail.tsx

@@ -0,0 +1,47 @@
+"use client";
+import { useEffect, useState } from "react";
+
+// 地址类型
+interface AddressData {
+  name: string;
+  line1: string;
+  line2: string;
+}
+
+export default function TrackAddress() {
+  const [addr, setAddr] = useState<AddressData | null>(null);
+
+  // 模拟接口拿单条订单地址数据
+  useEffect(() => {
+    const getAddress = async () => {
+      // 模拟接口返回假数据,和截图一致
+      const mockRes: AddressData = {
+        name: "zhang zhenfei",
+        line1: "aaaaaaaaaaaa, zzzzzzzz, Alabama",
+        line2: "90602, United States",
+      };
+      setAddr(mockRes);
+    };
+    getAddress();
+  }, []);
+
+  if (!addr) return <div className="p-4 text-gray-500">加载地址...</div>;
+
+  return (
+    <div className="flex items-start gap-3 py-3 border-b border-gray-200">
+      {/* 飞机图标 */}
+      <div className="mt-1 shrink-0 text-gray-700">
+        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
+          <path d="M17 16l-4-4V7h4l-4-4-4 4h4v5l-4 4" />
+          <line x1="12" y1="19" x2="12" y2="22" />
+        </svg>
+      </div>
+
+      {/* 地址文本 */}
+      <div className="flex flex-col">
+        <p className="text-gray-800">{addr.name}, {addr.line1}</p>
+        <p className="text-gray-600 mt-1">{addr.line2}</p>
+      </div>
+    </div>
+  );
+}

+ 74 - 0
src/app/(public)/customer/order/track/order_id/[id]/page.tsx

@@ -0,0 +1,74 @@
+import OrderHeader from "../../../view/order_id/[id]/_components/OrderHeader";
+import AccountSwiper from "@components/customer/accountSwiper";
+import TrackDetail from "./_components/TrackDetail"
+type Params = {
+  id: string;
+};
+
+export default async function TrackOrderDetail({ params }: { params: Params }) {
+  // 拿到动态订单ID:211600
+  const orderId = params.id;
+
+  // === 服务端请求订单接口,传入订单号 ===
+//   const res = await fetch(
+//     `${process.env.API_URL}/api/order/detail?id=${orderId}`,
+//     {
+//       next: { revalidate: 0 },
+//     },
+//   );
+  //   const orderData = await res.json();
+  console.log("orderId:",orderId);
+  // const orderData = {
+  //   shipTo: "Zhang Zhenfei,\nAaaaaaaa, Zzzzzz, Alaska\n90602, United States",
+  //   orderNo: orderId, // 自动使用路由传入的订单号
+  //   status: "Processing",
+  //   orderDate: "March 6, 2026",
+  //   shippingMethod: "USPS/FedEx(2-7 Business Days)",
+  //   paymentMethod: "Klarna",
+  //   CustomerEmail:"18567768109@163.Com",
+  //   paymentDetail: {
+  //     Customer: "18567768109",
+  //     Email: "@163.Com",
+  //     "Payment Method": "PayoneerKlarna",
+  //     "Payment Status": "Processing",
+  //     "Order No": "100055676",
+  //     "Customer Firstname": "Zhang",
+  //     "Customer Lastname": "Zhenfei",
+  //   },
+  //   // 多商品数组,自动兼容1件/多件商品
+  //   items: [
+  //     {
+  //       name: "Human Hair Headband Wigs Curly Bob Wig Straight Hair Wigs",
+  //       spec: "8, Straight, 5 Free Headbands.",
+  //       price: "$114.11",
+  //       qty: 1,
+  //     },
+  //     // 你可以取消注释,测试多商品渲染
+  //     /*
+  //     {
+  //       name: "Deep Wave Lace Front Human Hair Wig",
+  //       spec: "13x4 Lace, Medium Cap, 180% Density",
+  //       price: "$252.00",
+  //       qty: 2,
+  //     },
+  //     */
+  //   ],
+  //   price: {
+  //     subtotal: "$114.11",
+  //     shipping: "$0.00",
+  //     insurance: "$3.42",
+  //     total: "$117.53",
+  //   },
+  // };
+  return (
+    <div className="bg-gray-50 min-h-screen">
+      {/* 顶部固定导航栏(客户端组件:返回按钮) */}
+      <OrderHeader title="Track Order" />
+
+      <div className="px-5 py-4">
+        <TrackDetail />
+      </div>
+         <AccountSwiper/>
+    </div>
+  );
+}

+ 30 - 0
src/app/(public)/customer/order/view/order_id/[id]/_components/OrderHeader.tsx

@@ -0,0 +1,30 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+
+export default function OrderHeader({ title }: { title: string }) {
+  const router = useRouter();
+  return (
+    <div className="sticky top-0 z-50 bg-white px-5 py-4 border-b">
+      <div className="flex items-center w-full">
+        <button onClick={() => router.back()} className="text-xl">
+          <svg
+            className="w-6 h-6"
+            viewBox="0 0 1024 1024"
+            version="1.1"
+            xmlns="http://www.w3.org/2000/svg"
+            p-id="111033"
+            width="200"
+            height="200"
+          >
+            <path
+              d="M391.466667 512l339.882666-339.882667a46.592 46.592 0 1 0-65.877333-65.877333L292.693333 479.061333a46.592 46.592 0 0 0 0 65.877334l372.821334 372.821333a46.592 46.592 0 1 0 65.877333-65.877333L391.466667 512z"
+              p-id="111034"
+            ></path>
+          </svg>
+        </button>
+        <h1 className="flex-grow text-center text-lg font-bold">{title}</h1>
+      </div>
+    </div>
+  );
+}

+ 50 - 0
src/app/(public)/customer/order/view/order_id/[id]/_components/OrderInfoBlock.tsx

@@ -0,0 +1,50 @@
+interface Props {
+  data: {
+    shipTo: string;
+    orderNo: string;
+    status: string;
+    orderDate: string;
+    shippingMethod: string;
+    paymentMethod: string;
+    CustomerEmail:string;
+    paymentDetail: Record<string, string>;
+  };
+}
+
+export default function OrderInfoBlock({ data }: Props) {
+  return (
+    <>
+      <h2 className="text-xl font-bold mb-6 border-b pb-3">Order Information</h2>
+
+      {/* 两列网格布局,所有行严格对齐,Payment区域不会错位 */}
+      <div className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-5">
+        <div className="font-medium text-gray-700">Ship To:</div>
+        <div>{data.shipTo}</div>
+
+        <div className="font-medium text-gray-700">Order Number:</div>
+        <div>{data.orderNo}</div>
+
+        <div className="font-medium text-gray-700">Status:</div>
+        <div>{data.status}</div>
+
+        <div className="font-medium text-gray-700">Order Date:</div>
+        <div>{data.orderDate}</div>
+
+        <div className="font-medium text-gray-700">Customer Email:</div>
+        <div>{data.CustomerEmail}</div>
+        <div className="font-medium text-gray-700">Shipping Method:</div>
+        <div>{data.shippingMethod}</div>
+        <div className="font-medium text-gray-700">Payment Method:</div>
+        <div className="bg-pink-300 px-3 py-1 rounded w-fit">Klarna</div>
+        {/* <div className="flex flex-col gap-2">
+          <span className="bg-pink-300 px-3 py-1 rounded w-fit">Klarna.</span>
+          <div className="space-y-1">
+            {Object.entries(data.paymentDetail).map(([key, val]) => (
+              <div key={key}>{key} {val}</div>
+            ))}
+          </div>
+        </div> */}
+      </div>
+    </>
+  );
+}

+ 39 - 0
src/app/(public)/customer/order/view/order_id/[id]/_components/PriceSummary.tsx

@@ -0,0 +1,39 @@
+interface PriceData {
+  subtotal: string;
+  shipping: string;
+  insurance: string;
+  total: string;
+}
+
+interface Props {
+  priceData: PriceData;
+}
+
+export default function PriceSummary({ priceData }: Props) {
+  return (
+    <div className="mt-6">
+      <div className="space-y-3 border-t pt-4">
+        <div className="flex justify-between text-gray-700">
+          <span>Subtotal</span>
+          <span>{priceData.subtotal}</span>
+        </div>
+        <div className="flex justify-between text-gray-700">
+          <span>Shipping & Handling</span>
+          <span>{priceData.shipping}</span>
+        </div>
+        <div className="flex justify-between text-gray-700">
+          <span>Insurance (Insurance for Lost)</span>
+          <span>{priceData.insurance}</span>
+        </div>
+      </div>
+
+      {/* 底部总价栏,浅黄色背景 */}
+      <div className="bg-amber-50 mt-4 py-3 px-2 rounded">
+        <div className="flex justify-between items-center">
+          <p className="text-lg font-bold">Grand Total</p>
+          <p className="text-lg font-bold">{priceData.total}</p>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 40 - 0
src/app/(public)/customer/order/view/order_id/[id]/_components/ProductList.tsx

@@ -0,0 +1,40 @@
+interface ProductItem {
+  name: string;
+  spec: string;
+  price: string;
+  qty: number;
+  image:string;
+}
+
+interface Props {
+  items: ProductItem[];
+}
+
+export default function ProductList({ items }: Props) {
+  return (
+    <div className="mb-6 space-y-4">
+      {items.map((item, index) => (
+        <div className="flex items-center justify-between gap-3" key={index}>
+          {item.image ? (
+            <img
+              src={item.image}
+              alt={item.name}
+              className="w-22.5 h-23 object-cover rounded shrink-0"
+            />
+          ) : (
+            <div className="w-31 h-23 bg-gray-100 rounded shrink-0"></div>
+          )}
+
+          <div>
+            <p className="font-medium">{item.name}</p>
+            <p className="text-sm text-gray-600 my-1">{item.spec}</p>
+            <div className="flex justify-between">
+              <span>{item.price}</span>
+              <span>X {item.qty}</span>
+            </div>
+          </div>
+        </div>
+      ))}
+    </div>
+  );
+}

+ 93 - 0
src/app/(public)/customer/order/view/order_id/[id]/page.tsx

@@ -0,0 +1,93 @@
+import OrderHeader from "./_components/OrderHeader";
+import OrderInfoBlock from "./_components/OrderInfoBlock";
+import ProductList from "./_components/ProductList";
+import PriceSummary from "./_components/PriceSummary";
+
+type Params = {
+  id: string;
+};
+
+export default async function OrderDetail({ params }: { params: Params }) {
+  // 拿到动态订单ID:211600
+  const orderId = params.id;
+
+  // === 服务端请求订单接口,传入订单号 ===
+//   const res = await fetch(
+//     `${process.env.API_URL}/api/order/detail?id=${orderId}`,
+//     {
+//       next: { revalidate: 0 },
+//     },
+//   );
+  //   const orderData = await res.json();
+  console.log("orderId:",orderId);
+  const orderData = {
+    shipTo: "Zhang Zhenfei,\nAaaaaaaa, Zzzzzz, Alaska\n90602, United States",
+    orderNo: orderId, // 自动使用路由传入的订单号
+    status: "Processing",
+    orderDate: "March 6, 2026",
+    shippingMethod: "USPS/FedEx(2-7 Business Days)",
+    paymentMethod: "Klarna",
+    CustomerEmail:"18567768109@163.Com",
+    paymentDetail: {
+      Customer: "18567768109",
+      Email: "@163.Com",
+      "Payment Method": "PayoneerKlarna",
+      "Payment Status": "Processing",
+      "Order No": "100055676",
+      "Customer Firstname": "Zhang",
+      "Customer Lastname": "Zhenfei",
+    },
+    // 多商品数组,自动兼容1件/多件商品
+    items: [
+      {
+        name: "Human Hair Headband Wigs Curly Bob Wig Straight Hair Wigs",
+        spec: "8, Straight, 5 Free Headbands.",
+        price: "$114.11",
+        qty: 1,
+      },
+      // 你可以取消注释,测试多商品渲染
+      /*
+      {
+        name: "Deep Wave Lace Front Human Hair Wig",
+        spec: "13x4 Lace, Medium Cap, 180% Density",
+        price: "$252.00",
+        qty: 2,
+      },
+      */
+    ],
+    price: {
+      subtotal: "$114.11",
+      shipping: "$0.00",
+      insurance: "$3.42",
+      total: "$117.53",
+    },
+  };
+  return (
+    <div className="bg-gray-50 min-h-screen">
+      {/* 顶部固定导航栏(客户端组件:返回按钮) */}
+      <OrderHeader title="Order Information" />
+
+      <div className="px-5 py-4">
+        {/* 收货信息、支付信息区块 */}
+        <OrderInfoBlock data={orderData} />
+
+        {/* 分享按钮区域 */}
+        <div className="my-6">
+          <h3 className="text-lg font-bold mb-4">Share order:</h3>
+          <div className="flex gap-6">
+            <span>fb</span>
+            <span>X</span>
+            <span>pinterest</span>
+            <span>whatsapp</span>
+          </div>
+        </div>
+
+        {/* 商品列表:循环渲染,自动兼容1件或多件商品 */}
+        <ProductList items={orderData.items} />
+
+        {/* 金额汇总 */}
+        <PriceSummary priceData={orderData.price} />
+      </div>
+    </div>
+  );
+}

+ 207 - 0
src/app/(public)/hair-care-tips/page.tsx

@@ -0,0 +1,207 @@
+import HeadBack from "@/components/head/HeadBack"
+export default function HairCareTips() {
+  return (
+    <>
+    <HeadBack />
+      <div className="min-h-screen bg-gray-50">
+        <div className="max-w-4xl mx-auto bg-white  shadow-sm p-8 sm:p-10">
+          <h1 className="text-2xl font-bold text-center text-gray-800 mb-10">
+            Human Hair Care Instructions
+          </h1>
+
+          <div className="space-y-8">
+        
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                1. About Combing
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                When Brushing Use The Appropriate Tools For Different Textures,
+                Start At The Ends Of Your Hair And Gently Work Your Way Up, In
+                This Way It Avoids Tangling And Pulling Damage. Always Brush In
+                A Downward Motion And Do Not Brush Harshly.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                2. About Washing
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                Detangle Your Wig Gently By Using A Wide Tooth Comb. Curly Wigs
+                Should Be Untangled By Hands, No Combs Or Brushes.
+                <br />
+                Fill A Basin Or A Container With Cool Water And Mix In A Bit Of
+                Shampoo And Conditioner.
+                <br />
+                Immerse Your Wig And Swirl It Around Gently.
+                <br />
+                Let It Soak For About 5 Minutes.
+                <br />
+                Rinse Your Wig By Gently Running Cool Water Over It. Do Not Rub
+                As It Will Tangle Up The Wig.
+                <br />
+                Pat Dry With A Towel. Do Not Wring Or Rub, As Again, It Will
+                Mess Up The Wig.
+                <br />
+                Place Your Wig On A Wig Stand And Leave It To Air Dry.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                3. About Day & Night Maintenance
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                We Highly Suggested The Hair Be Washed And Conditioned At Least
+                Twice A Week.
+                <br />
+                Please Use A Soft Fabric Or Satin Hair Scrunchie To Achieve A
+                High Ponytail In Order To Avoid Tangling And Dryness.
+                <br />
+                When Not Wearing Your Wig, Place Your Wig On A Wig Stand Or
+                Mannequin Head.
+                <br />
+                At Night, It Is Better To Tie Hair Up With A Satin Scrunchie Or
+                Into A Satin Bonnet, An Alternative Satin Pillowcase.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                4. About Moisturize
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                At Least Once A Week .Go Through The Rows Of Your Braid Base And
+                Gently Apply Moisturizing Hair With Scalp Cream Or Argan Oil
+                (Eg, Mizani Cream, Coconut Oil, Shea Butter, Lotion)
+                <br />
+                It Is Advisable To Use The Products Containing The Herb In The
+                Case Where The Hair Begins To Become Dry And Yellow And Use The
+                Products With Special Effects Or Further Maintenance Which Are
+                Made For Protection&Cleaning In The Case Where The Hair
+                Bifurcates Or Even Becomes Invulnerable To Fragility.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                5. Straight Hair Care
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                Straight Hair Is Easy To Manage. Use Small Amounts Of Hair Care
+                Products Is Suggested.
+                <br />
+                We Suggest Lightweight Serums For Straight Hair Products.
+                <br />
+                When Combing, A Wide Tooth Comb Is The Best Choice.
+                <br />
+                Hair Care Products That Contain Sulfate, Parabens, Or Alcohol
+                Will Dry Out The Air And Cause The Tangling,Please Do Not Choose
+                These Kinds Of Hair Care Products.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                6. Curly Hair Care
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                Due To The Curl's Characteristics, Curly Hair Needs More
+                Hydration.
+                <br />
+                We Suggest Lightweight Serums And Leave-In Conditioners For
+                Curly Hair Products. These Care Products Will Help Keep Curly
+                Hair Moisturized And Tangle-Free. When Applying The Products, A
+                Quarter Size Of Each Product Is Enough To Keep Hair Defined, No
+                Need For More.
+                <br />
+                When Combing, A Wide Tooth Comb Is The Best Choice.
+                <br />
+                Please Avoid Using A Fine Tooth Comb For Curly Hair Since It
+                Will Cause Tangling And Shedding.
+                <br />
+                Hair Care Products That Contain Sulfate, Parabens, Or Alcohol
+                Will Dry Out The Air And Cause Tangling, Please Do Not Choose
+                These Kinds Of Hair Care Products.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                7. About Hot Tools
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                Heat Is One Of The Biggest Enemy, The More You Use It, The
+                Shorter Your Hair Extension Will Last. We Don’t Suggest You Use
+                Hot Tools Too Often.
+                <br />
+                It Is Better To Use Heat Protectant Before Styling Your Wig And
+                We Do Suggest You Guys Flat Iron Your Wig In High Temperatures
+                With Caution. For Colored Hair, 350F Degrees Is The Suggested
+                Temperature; Flat Ironing Your Wig In A Temperature Above 400F
+                Degrees Is Not Suggested For Natural Black Hair Since It Will
+                Cause The Dryness Of The Wig. Meantime, Please Be Aware Of That
+                Be Sure To Leave The Hot Tools On Your Wig For No More Than 30
+                Seconds.
+                <br />
+                Tips: When Curling Or Straightening Your Wig With Hot Tools,
+                Please Clean Your Tools In Time To Avoid There Have Few Loose
+                Hair On The Tools. It Is Normal That There Have Loose Hair As
+                Well As You Clean Up The Hot Tools When Restyling.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                8. Notes
+              </h2>
+              <div className="text-gray-700 leading-relaxed space-y-1">
+                <p>
+                  (1) Oil Sheen, Oil Sheen Is Not Recommended For Blond Hair,
+                  Oil May Cause Weave To Ship Out.
+                </p>
+                <p>
+                  (2) Wear A Cap Or Keep Your Hair Out Of Water. When Swimming.
+                  Chlorine And Salt Water Can Cause The Hair To Tangle And Mat
+                  Up.
+                </p>
+                <p>
+                  (3) Even If It’s 100% Human Hair Made, It’s Not Advised To
+                  Perm, Color, Or Any Other Chemical Process To Your Own Hair
+                  Extensions. At Least, Not Perm Too Often.
+                </p>
+                <p>
+                  (4) It Is Okay To Bleach The Knots But It Is Better To Find
+                  The Professional Hair Stylist To Help. ( It Is Better To Use
+                  The Foundation Method Rather Than Bleaching The Knots Which
+                  Can Help The Hair Lasts Longer.)
+                </p>
+                <p>
+                  (5) As A General Rule It Is Easier To Darken The Hair Than To
+                  Lighten Hair. We Recommend To Dyeing Darker, Since It Is
+                  Difficult For The Original Color To Fade. Improper Dying May
+                  Ruin The Hair. It Is Better To Find The Professional Hair
+                  Stylist To Help Dye The Hair.
+                </p>
+                <p>
+                  (6) Keep Your Hair Clean. Hair Tangles When Dirt And Sweat
+                  Build Up, Ash It After Exercise, Swimming, Etc.
+                </p>
+                <p>
+                  (7) Use Good Shampoo ( Sulfate Free, Parabens Free, Or Alcohol
+                  Free ), Your Stylist Can Advise You What Product Is Best For
+                  Your Hair. When Lots Of Water Is Added At Once To Very Dry
+                  Hair, Hair Can Swell Up And Tangle.
+                </p>
+                <p>
+                  (8) When Removing, Please Consult Your Professional Stylist.
+                </p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </>
+  );
+}

+ 688 - 0
src/app/(public)/help-faqs/_components/Faqs.tsx

@@ -0,0 +1,688 @@
+"use client";
+import { useState } from "react";
+import HeadBack from "@/components/head/HeadBack";
+type FaqItem = {
+  q: React.ReactNode;
+  a: React.ReactNode;
+};
+
+type FaqSection = {
+  title: string;
+  items: FaqItem[];
+};
+
+type FaqGroup = {
+  groupTitle: string;
+  sections: FaqSection[];
+};
+
+const faqData: FaqGroup[] = [
+  {
+    groupTitle: "Question Before Purchase",
+    sections: [
+      {
+        title: "1. About Order",
+        items: [
+          {
+            q: "How do I pay for an order?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  To pay for your order on www.asteriahair.com, follow the steps
+                  below:
+                </p>
+                <p>
+                  (1) Select the hair items you would like to order and click
+                  the "Buy Now" button.
+                </p>
+                <p>
+                  (2) Fill in the address information and choose the shipping
+                  method and payment method on the checkout page, then click the
+                  "Place Order" button.
+                </p>
+                <p>(3) Go to the payment platform to complete the payment.</p>
+              </div>
+            ),
+          },
+          {
+            q: "How do I use my coupon code? Can I use two coupon codes in one order?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  The purchasing process on our website is: Buy Now &gt Checkout
+                  &gt Confirm &gt Payment.
+                </p>
+                <p>
+                  When you are in the process of Checkout or Confirm, you can
+                  apply the coupon code if you have one, and please note that
+                  only one coupon code is allowed for one order.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "I have bought something but seen it at a different price on your website.",
+            a: (
+              <p>
+                As an online business we act much faster in adjusting our prices
+                in response to trends, stock and demand from customers. It's our
+                general view not to refund the difference.
+              </p>
+            ),
+          },
+          {
+            q: "Is there any hidden charges to my order?",
+            a: (
+              <p>
+                Basically, the final amount you will pay is just the price of
+                the product plus shipping(if you choose a paid shipping).
+                Sometimes, because of the exchange rate of currency, when the
+                payment finally cleared through the bank, the total amount will
+                be a little different to the price of the product, but its only
+                less than 0.5% of the total price.
+              </p>
+            ),
+          },
+          {
+            q: "Do I have to pay the customs duties & taxes?",
+            a: (
+              <p>
+                Orders shipped outside of the United States may be subject to
+                import taxes, customs duties and fees levied by the destination
+                country. The recipient of an international shipment may be
+                subject to such import taxes, customs duties and fees.
+                Additional charges for customs clearance must be fulfilled by
+                the recipient. Customs policies vary widely, the customs duty is
+                determined by the custom of your country, and it is uncertain to
+                know how much about it, you should contact your local customs
+                office for more information.
+              </p>
+            ),
+          },
+        ],
+      },
+      {
+        title: "2. About Products",
+        items: [
+          {
+            q: "Is your hair 100% human hair?",
+            a: <p>Yes, we only sell high quality 100% human virgin hair.</p>,
+          },
+          {
+            q: "Can I ask for small/large cap?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  Our Stock Size is Medium cap which is 22.5 inches, and we have
+                  the combs and elastic band or adjustable straps inside to help
+                  fit the head. Kindly Reminder:
+                </p>
+                <p>
+                  For full Lace Wig and 360 Lace Wig: The size of our full lace
+                  wigs and 360 lace wigs could not be changed.
+                </p>
+                <p>
+                  For U-part Wig and Headband Wig: The cap size of U-part wigs
+                  and headband wigs could not be changed, but the U-part wig’s
+                  top is empty so you can adjust the wig according to your head
+                  size, and the headband wig’s cap can be available with
+                  adjustable strap, elastic velcro snapping design wig with 5
+                  Clips, which can fit all head sizes perfectly, cap can be
+                  available with adjustable strap.
+                </p>
+                <p>For lace front / lace closure wig:</p>
+                <p>
+                  (1) Small Size is 21.5 inches, Large Size is 23.5 inches, you
+                  should know your own size and make a suitable decision.
+                </p>
+                <p>
+                  (2) If you change to those special cap sizes, you may need to
+                  wait about another extra 5-10 days. Would you like to wait? If
+                  you can wait and you want to ask for the small/big cap, please
+                  remember to leave the comments about it when you check out or
+                  send emails to us. Customer service email:{" "}
+                  <a
+                    href="mailto:server@asteriahair.com"
+                    className="text-blue-600 underline"
+                  >
+                    server@asteriahair.com
+                  </a>
+                  .
+                </p>
+                <p>
+                  (3) Custom-sized hairnets are subject to an additional charge
+                  of approximately $10.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "Does your wig come in bleached?",
+            a: (
+              <p>
+                Yes, The lace of our wigs are slightly bleached under the
+                condition that the hair would not shed, if you need more
+                bleaching, we advise to find a professional hairstylist to help.
+              </p>
+            ),
+          },
+          {
+            q: "Is your wig pre plucked?",
+            a: (
+              <p>
+                Yes, all of our wigs come slightly pre plucked, you can
+                customize it to your liking once you receive it.
+              </p>
+            ),
+          },
+          {
+            q: "Can your hair be dyed / bleached?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  Yes. Our hairs are 100% human virgin hair and all our natural
+                  color hair can be dyed or bleached to #27, as a general rule
+                  it is easier to darken the hair than to lighter the hair. We
+                  do suggest getting a professional opinion by an experienced
+                  colorist and having your hairdresser dye the hair as you will
+                  get the results you want, coloring them yourself always poses
+                  a high risk of not coming out the shade you want.
+                </p>
+                <p>
+                  We would advise for a one clip weft to be tested prior to
+                  coloring or restyling the full set to make sure the resulting
+                  shade is what you anticipate.
+                </p>
+                <p>
+                  Note: Any dyeing and bleaching is done at the customer's own
+                  discretion and we cannot be held accountable if the results do
+                  not come out as intended.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "How many hair bundles can make a full head?",
+            a: (
+              <p>
+                Normally the weight of 1 bundle is 95g-105g,we suggest you to
+                take 3-4 bundles for a full head,Especially for the length from
+                20inch to 30inch,4 bundles are necessary.
+              </p>
+            ),
+          },
+        ],
+      },
+      {
+        title: "3. About Payment",
+        items: [
+          {
+            q: "What types of payment do you accept?",
+            a: (
+              <p>
+                We accept the following payment methods: Paypal, Credit Cards
+                (Visa Card, MasterCard, and American Express), Klarna, Western
+                Union, Moneygram. If you have any payment problems, please
+                contact with us:{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                .
+              </p>
+            ),
+          },
+          {
+            q: "Do you accept payment by installment?",
+            a: <p>Yes, Now we support PayPal Pay later and Klarna payment.</p>,
+          },
+          {
+            q: "When I pay via PayPal, do I have to have a PayPal account? Can I use credit cards?",
+            a: (
+              <p>
+                It is not necessary to have a PayPal account to order, you can
+                pay with Credit/Debit Card directly. When you check out, please
+                click paypal button, then you can choose the "Pay with Credit
+                Card" on pop-up payment page.
+              </p>
+            ),
+          },
+          {
+            q: "Why is my card declined?",
+            a: (
+              <p>
+                Sometimes when your card is expired, insufficient balance or
+                poor credit which may have a record of Chargeback or others will
+                lead to card declined when paying. So we suggest customers not
+                take a dispute for no reasons, because your credit record will
+                be affected, and your next shopping will be declined. When your
+                card is declined, you can call your bank for the problem. Or you
+                can try another card. If you still have problems, please send an
+                email to us :{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                .
+              </p>
+            ),
+          },
+          {
+            q: "Why did my payment fail?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  If your payment for an order has failed, please check it is
+                  not due to the following situations:
+                </p>
+                <p>
+                  (1) Card security code failed, Please note that an incorrect
+                  card security code could cause payment failure. Card security
+                  code is the short numeric code on the back of your credit
+                  card. Please verify that you have entered the correct card
+                  security code and try again.
+                </p>
+                <p>
+                  (2) Insufficient fund, If you meet the insufficient fund error
+                  log when you make payment by credit card, please make sure
+                  that you have sufficient funds in your account and that
+                  payment for your order does not exceed your credit limit.
+                </p>
+                <p>
+                  (3) Please also make sure you have used a Visa or MasterCard
+                  personal credit card to finish payment, because at present
+                  debit card or business card is unable to be supported.
+                </p>
+                <p>
+                  (4) Exceed limit, Please make sure the order amount does not
+                  exceed your credit limit,or the payment is unable to go
+                  through smoothly.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "If I pay on your website, Is my information safe?",
+            a: (
+              <p>
+                We are sure that our website is legal, which has been certified
+                by the card organization. So please don’t worry about that. Your
+                payment information is kept by your bank or card organization,
+                but not saved by us. We only use your shipping info for
+                delivery. Your information is safe and we will keep your
+                information confidential.
+              </p>
+            ),
+          },
+          {
+            q: "Can you send me an invoice?",
+            a: (
+              <p>
+                Yes, we can send you the invoice via Paypal Or Klarna if you
+                need, just feel free to contact us via email:{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                .
+              </p>
+            ),
+          },
+        ],
+      },
+      {
+        title: "4. About Shipment",
+        items: [
+          {
+            q: "How long will my hair be shipped after I place an order?",
+            a: (
+              <p>
+                Generally, your package will be shipped within 24 hours after
+                order confirm (except Weekends and Holiday). However, we may
+                need to wait for 2 or 3 days for the stock at times, thus the
+                delivery will be delayed for 2 or 3 days.
+              </p>
+            ),
+          },
+          {
+            q: "Which express do you use? How long can I receive my order?",
+            a: (
+              <p>
+                We use DHL, FedEx , USPS, GES, TNT, DPEX or UPS. Generally, the
+                package will be delivered within 3-7 business days to most
+                countries once shipped out. The time you receive your order will
+                depend on many factors, such as the shipping method and the
+                location of the shipping address. Please be aware of that bad
+                weathers, pandemic may cause the shipping delays sometimes. Once
+                your order has shipped out, you will receive the tracking number
+                by email.
+              </p>
+            ),
+          },
+          {
+            q: "Can you provide the Overnight / Expedite shipping service?",
+            a: (
+              <p>
+                1-2 day expedite shipping is available for USA orders if the
+                hair you want is in stock in our US warehouse. Please contact us
+                to confirm it before you order. Our Customer service email:{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                .
+              </p>
+            ),
+          },
+          {
+            q: "Can I receive my order on Saturday or Sunday?",
+            a: (
+              <p>
+                Most express companies are weekend off. If the package has been
+                arrived your city, you can go to the local sites to pick it up.
+              </p>
+            ),
+          },
+          {
+            q: "I am in a remote area, can you ship it?",
+            a: (
+              <p>
+                Generally, we can ship to most of the countries and regions
+                worldwide, but individual places may be charged remote fees. If
+                the zip code in the area can not be found on the express
+                delivery system, we will not be able to ship it, you need to
+                provide a new address.
+              </p>
+            ),
+          },
+          {
+            q: "Can I use Po.box as the shipping address?",
+            a: (
+              <p>
+                We don't accept PO.box (including FPO or APO) as the shipping
+                address, because the Express(DHL/FedEx/UPS/USPS/GES/TNT) can't
+                deliver and we only accept the physical address as shipping
+                address.
+              </p>
+            ),
+          },
+        ],
+      },
+      {
+        title: "5. About Account",
+        items: [
+          {
+            q: "How to register a member?",
+            a: (
+              <div className="space-y-1">
+                <p>(1) Open the Web page, click on the "sign up".</p>
+                <p>
+                  (2) You need fill out your mailbox address、password and
+                  telephone number.
+                </p>
+                <p>
+                  (3) Click “Create An Account” the specified page appears, you
+                  need to improve your information.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "How to reset my password?",
+            a: (
+              <p>
+                Go to sign in page and click forget password. We will quickly
+                email you a link to reset your password, if you don't get the
+                email, please check your spam email at first. If still no email,
+                please feel free to contact customer service, we can help reset
+                your password.
+              </p>
+            ),
+          },
+          {
+            q: "How can I check my reward points?",
+            a: (
+              <p>
+                Please be aware of that you need to be registered to earn and
+                spend Reward Points. Sign in your account and you can check your
+                reward points at “Points” Section. For more details, please
+                check our reward points page.
+              </p>
+            ),
+          },
+        ],
+      },
+    ],
+  },
+  {
+    groupTitle: "Question After Purchase",
+    sections: [
+      {
+        title: "1. About Track Order",
+        items: [
+          {
+            q: "How to track my order?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  Method 1: Log into “MY ACCOUNT” to view the status of your
+                  order Track your shipment by using the tracking number
+                  provided to you once your items have been shipped. You cannot
+                  track your order before it has been shipped. If you don’t know
+                  what your tracking number is or if you’re having difficulty
+                  tracking your package, please feel free to contact our
+                  customer service department for assistance.
+                </p>
+                <p>
+                  Method 2: You can use your order number or shipment number to
+                  track your order though our Track Order page.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "Why haven't I received my order yet?",
+            a: (
+              <p>
+                Once the package has been shipped out, we will email you the
+                tracking number, you can check the status. If not, please feel
+                free to contact our customer service:{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                , we will help you to check it.
+              </p>
+            ),
+          },
+          {
+            q: "Why is the shipment of my order always at the pending status?",
+            a: (
+              <p>
+                If it shows abnormality or pending in short time, that maybe
+                normal transport state. If it lasts a long time( over 1 day),
+                you can call the DHL/FedEx/UPS/USPS/TNT to ask about what is
+                going on about it. Or you can send us the Email to{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                , we will help you out.
+              </p>
+            ),
+          },
+          {
+            q: "If I'm not at home, how can I sign for my package? Can I have a delivery service without signature",
+            a: (
+              <p>
+                If you are not at home, you can ask your family and neighbors to
+                sign for it for you, or you can contact the local express
+                customer service and reschedule with them to deliver your
+                package, or you can choose to pick up the package yourself. In
+                order for you to successfully receive the package, we recommend
+                that you sign for it yourself, after you applied the service
+                without signature, if the package is lost or stolen, we do not
+                take any responsibility.
+              </p>
+            ),
+          },
+          {
+            q: "Why the shipment status shows that I have signed. But I did not receive the parcel yet.",
+            a: (
+              <p>
+                Once that happens, it means someone has signed the package for
+                you. It may be your neighbor or family and friends, It is also
+                possible that the courier drop your package around your house.
+                Please confirm it first. If not, please contact the express
+                company first to tell them your situation and let them help you
+                find the package, and please ask them as many details as
+                possible, whch is important to find the package later.
+              </p>
+            ),
+          },
+        ],
+      },
+      {
+        title: "2. About Return / Refunds",
+        items: [
+          {
+            q: "What is your return & exchange policy?",
+            a: (
+              <p>
+                We offer 30 days no reason return if the hair is in its original
+                state. For more details please check our return policy /
+                exchange policy page.
+              </p>
+            ),
+          },
+          {
+            q: "When will I get a refund after returning the package?",
+            a: (
+              <p>
+                A full refund will be provided to the original method of payment
+                after we have received, inspected and confirmed. All customers
+                must contact us after sending the package back and provide us
+                with the tracking information in order for us to process the
+                refund. Some financial institutions can take up to 3 business
+                days to process the refund. Please allow a few business days for
+                your financial institution to reflect the refund on your
+                statement prior to contacting us.
+              </p>
+            ),
+          },
+          {
+            q: "If I refuse to accept the package, can I get a full refund?",
+            a: (
+              <p>
+                We do not accept the refusal without any reasons. If you refuse
+                to sign the package and lead to the return, you will need to pay
+                for the return shipping and the shipping fee we paid the first
+                time. Any expenses incurred during the return of the parcel will
+                also be deducted from your payment. So we suggest that customers
+                contact us within 24 hours via email before shipping if they
+                have any problems (such as place the wrong order/ want to cancel
+                /want to exchange), so that we can help you to solve it timely.
+              </p>
+            ),
+          },
+        ],
+      },
+    ],
+  },
+];
+export default function Faqs() {
+  const [openSections, setOpenSections] = useState<string[]>([]);
+  const [openItems, setOpenItems] = useState<string[]>([]);
+
+  const toggleSection = (g: number, s: number) => {
+    const k = `${g}-${s}`;
+    setOpenSections((p) =>
+      p.includes(k) ? p.filter((x) => x !== k) : [...p, k],
+    );
+  };
+
+  const toggleItem = (g: number, s: number, i: number) => {
+    const k = `${g}-${s}-${i}`;
+    setOpenItems((p) => (p.includes(k) ? p.filter((x) => x !== k) : [...p, k]));
+  };
+
+  return (
+    <>
+      <HeadBack />
+      <div className="max-w-4xl mx-auto px-4 py-12">
+        {faqData.map((group, groupIdx) => (
+          <div key={groupIdx} className="mb-14">
+            <h2 className="text-2xl font-bold mb-6 text-gray-800">
+              {group.groupTitle}
+            </h2>
+
+            <div className="space-y-4">
+              {group.sections.map((sec, secIdx) => {
+                const secKey = `${groupIdx}-${secIdx}`;
+                const secOpen = openSections.includes(secKey);
+
+                return (
+                  <div
+                    key={secKey}
+                    className="border border-gray-200 rounded-lg overflow-hidden"
+                  >
+                    <button
+                      onClick={() => toggleSection(groupIdx, secIdx)}
+                      className="w-full flex justify-between items-center px-5 py-3 bg-gray-50 text-left font-semibold text-gray-800"
+                    >
+                      {sec.title}
+                      <span className="text-xl">{secOpen ? "−" : "+"}</span>
+                    </button>
+
+                    {secOpen && (
+                      <div className="divide-y divide-gray-100">
+                        {sec.items.map((item, itemIdx) => {
+                          const itemKey = `${groupIdx}-${secIdx}-${itemIdx}`;
+                          const itemOpen = openItems.includes(itemKey);
+
+                          return (
+                            <div key={itemKey} className="px-5 py-4">
+                              <button
+                                onClick={() =>
+                                  toggleItem(groupIdx, secIdx, itemIdx)
+                                }
+                                className="w-full flex justify-between items-center text-left font-medium text-gray-700"
+                              >
+                                <span>{item.q}</span>
+                                <span className="text-xl ml-2">
+                                  {itemOpen ? "−" : "+"}
+                                </span>
+                              </button>
+
+                              {itemOpen && (
+                                <div className="mt-3 text-gray-600 text-sm md:text-base">
+                                  {item.a}
+                                </div>
+                              )}
+                            </div>
+                          );
+                        })}
+                      </div>
+                    )}
+                  </div>
+                );
+              })}
+            </div>
+          </div>
+        ))}
+      </div>
+    </>
+  );
+}

+ 10 - 0
src/app/(public)/help-faqs/page.tsx

@@ -0,0 +1,10 @@
+
+import Faqs from "./_components/Faqs"
+export default function FaqPage() {
+  
+  return (
+    <>
+        <Faqs />
+    </>
+  );
+}

+ 12 - 10
src/app/(public)/page.tsx

@@ -1,24 +1,26 @@
-import { GET_THEME_CUSTOMIZATION } from "@/graphql";
-import RenderThemeCustomization from "@components/home/RenderThemeCustomization";
+// import { GET_THEME_CUSTOMIZATION } from "@/graphql";
+// import RenderThemeCustomization from "@components/home/RenderThemeCustomization";
 import HomeImageBanner from "@components/home/HomeImageBanner";
-import { ThemeCustomizationResponse } from "@/types/theme/theme-customization";
-import { cachedGraphQLRequest } from "@/utils/hooks/useCache";
+// import { ThemeCustomizationResponse } from "@/types/theme/theme-customization";
+// import { cachedGraphQLRequest } from "@/utils/hooks/useCache";
 import {DatePicker} from "@heroui/date-picker";
+import HomeContent from "./_components/HomeContent"
 export const revalidate = 3600;
 
 export default async function Home() {
   // const 
-  const {data} = await cachedGraphQLRequest<ThemeCustomizationResponse>(
-    "home",
-    GET_THEME_CUSTOMIZATION,
-    { first: 20 }
-  );
+  // const {data} = await cachedGraphQLRequest<ThemeCustomizationResponse>(
+  //   "home",
+  //   GET_THEME_CUSTOMIZATION,
+  //   { first: 20 }
+  // );
 
   return (
     <>
       <HomeImageBanner />
       <DatePicker label={"Birth date"} labelPlacement={"outside"} />
-      <RenderThemeCustomization themeCustomizations={data?.themeCustomizations ?? {edges: []}} />
+      {/* <RenderThemeCustomization themeCustomizations={data?.themeCustomizations ?? {edges: []}} /> */}
+      <HomeContent />
     </>
     
   );

+ 76 - 0
src/app/(public)/track_order/_components/TrackFaqs.tsx

@@ -0,0 +1,76 @@
+"use client";
+import { useState } from "react";
+// FAQ 数据
+const faqList = [
+  {
+    question: "Tracking Number & Where Can I Find It?",
+    answer: `Your tracking number will be sent to you via email along with detailed order information. You can use the order number or tracking number to track your package here. If you have any further questions, feel free to contact us at: service@asteriahair.com.`,
+  },
+  {
+    question: "Why Can't I Track My Order?",
+    answer: `The tracking information may not be updated immediately after shipping. Please recheck after 24-48 hours. Or you can contact us via live chat or mail to:service@asteriahair.com`,
+  },
+  {
+    question: "Why Is My Order Still Processing?",
+    answer: `Most of orders processed within 72 hours, but if the product is customized wig, hot-selling wig or length, the processing time may be delayed by 2-3 days. Please don't worry, we will try our best to ship your package out ASAP.`,
+  },
+  {
+    question: "Delivery Failed, What Should I Do?",
+    answer: `If the delivery failed, we will notify you to pick it up by SMS/Email. Please contact the logistics company for re-delivery or pick it up by yourself. Otherwise, the loss caused by not picking up the package in time shall be borne by yourself.`,
+  },
+];
+export default function TrackFaqs() {
+  // 当前展开的FAQ索引,null=全部收起
+  const [activeIndex, setActiveIndex] = useState<number | null>(null);
+
+  // 切换折叠面板(互斥,只展开一个)
+  const toggleFaq = (idx: number) => {
+    setActiveIndex(activeIndex === idx ? null : idx);
+  };
+
+  return (
+    <div>
+      {faqList.map((item, idx) => (
+        <div
+          key={idx}
+          className={`border-t border-gray-200 ${idx === 0 ? "border-t-0" : ""}`}
+        >
+          {/* 问题标题栏 */}
+          <button
+            type="button"
+            onClick={() => toggleFaq(idx)}
+            className="w-full flex justify-between items-center px-6 py-5 text-left text-lg"
+          >
+            <span>Q:{item.question}</span>
+            {/* 上下箭头图标 */}
+            <svg
+              width="20"
+              height="20"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              strokeWidth="1.8"
+              strokeLinecap="round"
+              strokeLinejoin="round"
+              className={`transition-transform duration-200 ${
+                activeIndex === idx ? "rotate-180" : ""
+              }`}
+            >
+              <path d="M18 15l-6-6-6 6" />
+            </svg>
+          </button>
+          {/* 回答内容,展开/收起 */}
+          <div
+            className={`px-6 overflow-hidden transition-all duration-300 ${
+              activeIndex === idx ? "max-h-[800px] pb-6" : "max-h-0"
+            }`}
+          >
+            <p className="text-base leading-relaxed text-gray-800">
+              A:{item.answer}
+            </p>
+          </div>
+        </div>
+      ))}
+    </div>
+  );
+}

+ 32 - 0
src/app/(public)/track_order/_components/TrackOrder.tsx

@@ -0,0 +1,32 @@
+"use client";
+import { useState } from "react";
+
+export default function TrackOrder() {
+  const [orderNo, setOrderNo] = useState("");
+  // 查询提交
+  const handleTrackSubmit = (e: React.FormEvent) => {
+    e.preventDefault();
+    console.log("查询单号:", orderNo);
+    // 这里写你的查询接口逻辑
+  };
+  return (
+    <>
+      {/* 查询表单 */}
+      <form onSubmit={handleTrackSubmit} className="space-y-6">
+        <input
+          type="text"
+          value={orderNo}
+          onChange={(e) => setOrderNo(e.target.value)}
+          placeholder="* Please Input Your Number Here"
+          className="w-full px-5 py-4 text-base bg-white border-0 rounded-[8px] outline-none focus:ring-2 focus:ring-emerald-700"
+        />
+        <button
+          type="submit"
+          className="w-full py-5 bg-emerald-800 text-white text-xl rounded-full hover:bg-emerald-900 transition-colors"
+        >
+          Track Order
+        </button>
+      </form>
+    </>
+  );
+}

+ 47 - 0
src/app/(public)/track_order/page.tsx

@@ -0,0 +1,47 @@
+import HeadBack from "@/components/head/HeadBack";
+import TrackOrder from "./_components/TrackOrder";
+import TrackFaqs from "./_components/TrackFaqs";
+
+export default function TrackOrderPage() {
+  return (
+    <>
+      <HeadBack />
+      <div className="min-h-screen bg-[#f0ede4] text-black">
+        <div className="max-w-3xl mx-auto px-4 py-8">
+          {/* 顶部 Track Order 区域 */}
+          <section className="mb-10">
+            <h1 className="text-[clamp(1.8rem,4vw,2.5rem)] font-normal mb-6">
+              Track Order
+            </h1>
+            <div className="space-y-3 mb-8 text-base leading-relaxed">
+              <p>Want To Check The Status Of Your Order?</p>
+              <p>
+                Please Input Your Order Number And Click The Track Order Button
+                To Find The Tracking Information.
+              </p>
+            </div>
+
+            <TrackOrder />
+          </section>
+
+          {/* FAQ 区域 */}
+          <section className="bg-white rounded-[8px] overflow-hidden">
+            <h2 className="text-[clamp(1.5rem,3vw,2rem)] font-normal p-6 pb-4">
+              People Also Ask
+            </h2>
+            {/* 问答 */}
+            <TrackFaqs />
+          </section>
+
+          {/* You May Like 标题区域 */}
+          <div className="mt-10">
+            <h2 className="text-[clamp(1.4rem,3vw,1.8rem)] font-normal">
+              You May Like
+            </h2>
+            {/* 下方可自行添加商品列表 */}
+          </div>
+        </div>
+      </div>
+    </>
+  );
+}

+ 50 - 0
src/app/api/customer/reward-points/browse-product/route.ts

@@ -0,0 +1,50 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+export async function POST(req: NextRequest) {
+  try {
+    const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+    const params = await req.json();
+    const response = await restApiFetch<{
+      data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
+      variables:{
+        categorys:string,
+      } ;
+    }>({
+      api: "/customer/reward-points/browse-product",
+      method: "POST",
+      cache: "no-store",
+      variables: params,
+      guestToken: authorizationToken,
+    });
+    // 打印后端原始返回结构
+    console.log(
+      "接口原始response.body =",
+      JSON.stringify(response.body, null, 2),
+    );
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+  } catch (error) {
+    console.log("/customer/token/address---", error); // 调试用
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 },
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 },
+    );
+  }
+}

+ 40 - 0
src/app/api/customer/reward-points/follow-status/route.ts

@@ -0,0 +1,40 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+
+export async function GET(req: NextRequest) {
+  try {
+    const guestToken = getAuthToken(req);
+    const response = await restApiFetch<any>({
+      api: "/customer/reward-points/follow-status",
+      method: "GET",
+      cache: "no-store",
+      guestToken,
+    });
+
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+
+  } catch (error) {
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 }
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 }
+    );
+  }
+}

+ 62 - 0
src/app/api/customer/reward-points/follow/route.ts

@@ -0,0 +1,62 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+export async function POST(req: NextRequest) {
+  try {
+    const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+    const params = await req.json();
+    const response = await restApiFetch<{
+      data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
+      variables:{
+        platform:string,
+      } ;
+    //   {
+    //     first_name: string;
+    //     last_name: string;
+    //     email: string;
+    //     phone: string;
+    //     address: string;
+    //     country: string;
+    //     state: string;
+    //     city: string;
+    //     postcode: string;
+    //     default_address: number;
+    //   }
+    }>({
+      api: "/customer/reward-points/follow",
+      method: "POST",
+      cache: "no-store",
+      variables: params,
+      guestToken: authorizationToken,
+    });
+    // 打印后端原始返回结构
+    console.log(
+      "接口原始response.body =",
+      JSON.stringify(response.body, null, 2),
+    );
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+  } catch (error) {
+    console.log("/customer/token/address---", error); // 调试用
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 },
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 },
+    );
+  }
+}

+ 61 - 0
src/app/api/customer/reward-points/history/route.ts

@@ -0,0 +1,61 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+
+export async function GET(req: NextRequest) {
+  try {
+    const guestToken = getAuthToken(req);
+
+    // 👇 取出所有前端传过来的查询参数
+    const searchParams = req.nextUrl.searchParams;
+    const page = searchParams.get("page");
+    const per_page = searchParams.get("per_page");
+    const amountType = searchParams.get("amountType");
+
+    // 👇 构建查询参数数组(自动过滤空值)
+    const queryParts: string[] = [];
+    if (page) queryParts.push(`page=${page}`);
+    if (per_page) queryParts.push(`per_page=${per_page}`);
+    if (amountType) queryParts.push(`amountType=${amountType}`);
+
+    // 👇 最终拼接 URL
+    let apiUrl = "/customer/reward-points/history";
+    if (queryParts.length > 0) {
+      apiUrl += "?" + queryParts.join("&");
+    }
+
+    console.log("最终请求API地址:", apiUrl);
+
+    const response = await restApiFetch<any>({
+      api: apiUrl,
+      method: "GET",
+      cache: "no-store",
+      guestToken,
+    });
+
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+
+  } catch (error) {
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 }
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 }
+    );
+  }
+}

+ 193 - 0
src/app/api/customer/token/address/route.ts

@@ -0,0 +1,193 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+// import type { GiftListBody,FetchWrap  } from '@/types/api/gift/lists';
+export async function GET(req: NextRequest) {
+  try {
+    const guestToken = getAuthToken(req);
+    // 从url取查询参数 id
+    const addressId = req.nextUrl.searchParams.get("id");
+
+    const apiUrl = addressId
+      ? `/customer/token/address/${addressId}`
+      : `/customer/token/address`;
+
+    console.log("11111----:", apiUrl);
+  
+    const response = await restApiFetch<any>({
+      api: apiUrl, //
+      method: "GET",
+      cache: "no-store",
+      guestToken,
+    });
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+  } catch (error) {
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 },
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 },
+    );
+  }
+}
+export async function POST(req: NextRequest) {
+  try {
+    const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+    const params = await req.json();
+    const response = await restApiFetch<{
+      data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
+      variables: {
+        first_name: string;
+        last_name: string;
+        email: string;
+        phone: string;
+        address: string;
+        country: string;
+        state: string;
+        city: string;
+        postcode: string;
+        default_address: number;
+      };
+    }>({
+      api: "/customer/token/address",
+      method: "POST",
+      cache: "no-store",
+      variables: params,
+      guestToken: authorizationToken,
+    });
+    // 打印后端原始返回结构
+    console.log(
+      "接口原始response.body =",
+      JSON.stringify(response.body, null, 2),
+    );
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+  } catch (error) {
+    console.log("/customer/token/address---", error); // 调试用
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 },
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 },
+    );
+  }
+}
+export async function PUT(req: NextRequest) {
+  try {
+    const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+    const params = await req.json();
+    // 从url取查询参数 id
+    const addressId = req.nextUrl.searchParams.get("id");
+    const response = await restApiFetch<{
+      data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
+      variables: {
+        first_name: string;
+        last_name: string;
+        email: string;
+        phone: string;
+        address: string;
+        country: string;
+        state: string;
+        city: string;
+        postcode: string;
+        default_address: number;
+      };
+    }>({
+      api: `/customer/token/address/${addressId}`,
+      method: "PUT",
+      cache: "no-store",
+      variables: params,
+      guestToken: authorizationToken,
+    });
+    // 打印后端原始返回结构
+    console.log(
+      "接口原始response.body =",
+      JSON.stringify(response.body, null, 2),
+    );
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+  } catch (error) {
+    console.log("/customer/token/address---", error); // 调试用
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 },
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 },
+    );
+  }
+}
+export async function DELETE(req: NextRequest) {
+  try {
+    const guestToken = getAuthToken(req);
+    const addressId = req.nextUrl.searchParams.get("id");
+    const response = await restApiFetch<any>({
+      api: `/customer/token/address/${addressId}`, //
+      method: "DELETE",
+      cache: "no-store",
+      guestToken,
+    });
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+  } catch (error) {
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 },
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 },
+    );
+  }
+}

+ 40 - 0
src/app/api/shop/customer-address-gets/route.ts

@@ -0,0 +1,40 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+// import type { GiftListBody,FetchWrap  } from '@/types/api/gift/lists';
+export async function GET(req: NextRequest) {
+    try {
+        const guestToken = getAuthToken(req);
+        // 从查询参数取 page,默认 1
+        const id = req.nextUrl.searchParams.get("id") || "1";
+        const response = await restApiFetch<any>({
+            api: `/shop/customer-address-gets?id=${id}`,
+            method:'GET',
+            cache:'no-store',
+            guestToken,
+        });
+        return NextResponse.json({
+            status: response.status,
+            data: response.body,
+        });
+    } catch (error) {
+        if (isBagistoError(error)) {
+            return NextResponse.json(
+                {
+                    data: null,
+                    error: error.cause ?? error,
+                },
+                { status: 200 }
+            );
+        }
+
+        return NextResponse.json(
+            {
+                message: "Network error",
+                error: error instanceof Error ? error.message : error,
+            },
+            { status: 500 }
+        );
+    }
+}

+ 41 - 0
src/app/api/shop/customer-addresses/route.ts

@@ -0,0 +1,41 @@
+import { NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+import { NextRequestHint } from "next/dist/server/web/adapter";
+// import type { GiftListBody,FetchWrap  } from '@/types/api/gift/lists';
+export async function GET(req: NextRequestHint) {
+    try {
+        const guestToken = getAuthToken(req);
+        // 从查询参数取 page,默认 1
+        const page = req.nextUrl.searchParams.get("page") || "1";
+        const response = await restApiFetch<any>({
+            api: `/shop/customer-addresses?page=${page}`,
+            method:'GET',
+            cache:'no-store',
+            guestToken,
+        });
+        return NextResponse.json({
+            status: response.status,
+            data: response.body,
+        });
+    } catch (error) {
+        if (isBagistoError(error)) {
+            return NextResponse.json(
+                {
+                    data: null,
+                    error: error.cause ?? error,
+                },
+                { status: 200 }
+            );
+        }
+
+        return NextResponse.json(
+            {
+                message: "Network error",
+                error: error instanceof Error ? error.message : error,
+            },
+            { status: 500 }
+        );
+    }
+}

+ 10 - 4
src/components/common/AddToCartModal/OpenAddToCartModalButton.tsx

@@ -1,5 +1,5 @@
 "use client";
-
+import { FC, ReactNode } from "react";
 import { useAppDispatch } from "@/store/hooks";
 import { openAddToCartDialog } from '@/store/slices/addToCartDialogSlice';
 import { GET_PRODUCT_BY_URL_KEY } from "@/graphql";
@@ -8,8 +8,14 @@ import { SingleProductResponse } from "@/components/catalog/type";
 import { LoadingSpinner } from "@components/common/LoadingSpinner";
 import ShoppingCartIcon from "@components/common/icons/ShoppingCartIcon";
 import { useCustomToast } from "@utils/hooks/useToast";
-
-export function OpenAddToCartModalButton({productUrlKey}: {productUrlKey: string}) {
+interface OpenAddToCartModalButtonProps {
+  productUrlKey: string;
+  icon?: ReactNode;
+}
+export const OpenAddToCartModalButton: FC<OpenAddToCartModalButtonProps> = ({
+  productUrlKey,
+  icon,
+}) => {
     const dispatch = useAppDispatch();
     // const [loading, setLoading] = useState(false);
     const { showToast } = useCustomToast();
@@ -54,7 +60,7 @@ export function OpenAddToCartModalButton({productUrlKey}: {productUrlKey: string
     return (
 
         <button onClick={openDialog} disabled={loading}>
-            { loading ? <LoadingSpinner colorClassName={"text-ly-green"} /> : <ShoppingCartIcon />}
+            { loading ? <LoadingSpinner colorClassName={"text-ly-green"} /> : <ShoppingCartIcon icon={icon} />}
         </button>
         
     );

+ 16 - 5
src/components/common/icons/ShoppingCartIcon.tsx

@@ -1,12 +1,23 @@
+import { ComponentProps, ReactNode } from "react";
 import clsx from "clsx";
 
-export default function ShoppingCartIcon(props: React.ComponentProps<"svg">) {
+interface ShoppingCartIconProps extends ComponentProps<"svg"> {
+  icon?: ReactNode;
+}
+
+export default function ShoppingCartIcon({
+  className,
+  icon,
+}: ShoppingCartIconProps) {
+  // 如果传了 icon → 直接返回自定义图标
+  if (icon) {
+    return <>{icon}</>;
+  }
+
+  // 没传 → 返回默认购物车图标
   return (
     <svg
-      className={clsx(
-        "fill-ptimary h-5 w-5 dark:fill-transparent",
-        props.className,
-      )}
+      className={clsx("fill-primary h-5 w-5 dark:fill-transparent", className)}
       fill="none"
       stroke="currentColor"
       strokeWidth="1.5"

+ 127 - 68
src/components/customer/SettingModal.tsx

@@ -1,5 +1,7 @@
 "use client";
 import Link from "next/link";
+import { useState } from "react";
+import HelpModal from "../../app/(public)/customer/account/_components/HelpModal";
 export default function SettingModal({
   visible,
   onClose,
@@ -7,86 +9,143 @@ export default function SettingModal({
   visible: boolean;
   onClose: () => void;
 }) {
+  const [showSetting, setShowSetting] = useState(false);
   // 如果不显示,直接 return null
   if (!visible) return null;
-
+  
   return (
-    // 全屏遮罩
-    <div className="fixed inset-0 z-50 bg-black/50">
-      {/* 弹框主体 - 从底部滑上来 */}
-      <div className="absolute bottom-0 rounded-none left-0 right-0 w-full h-full bg-white  overflow-auto">
-        <div className="flex flex-col">
-          {/* 顶部导航 */}
-          <header className="bg-black text-white py-4 px-4 flex items-center">
-            <button onClick={onClose} className="mr-4">
-              <svg
-                xmlns="http://www.w3.org/2000/svg"
-                className="h-6 w-6"
-                fill="none"
-                viewBox="0 0 24 24"
-                stroke="currentColor"
-              >
-                <path
-                  strokeLinecap="round"
-                  strokeLinejoin="round"
-                  strokeWidth={2}
-                  d="M15 19l-7-7 7-7"
-                />
-              </svg>
-            </button>
-            <h1 className="text-lg font-medium">Setting</h1>
-          </header>
+    <>
+      {/* // 全屏遮罩 */}
+      <div className="fixed inset-0 z-50 bg-black/50">
+        {/* 弹框主体 - 从底部滑上来 */}
+        <div className="absolute bottom-0 rounded-none left-0 right-0 w-full h-full bg-white  overflow-auto">
+          <div className="flex flex-col">
+            {/* 顶部导航 */}
+            <header className="bg-black text-white py-4 px-4 flex items-center">
+              <button onClick={onClose} className="mr-4">
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  className="h-6 w-6"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  stroke="currentColor"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M15 19l-7-7 7-7"
+                  />
+                </svg>
+              </button>
+              <h1 className="text-lg font-medium">Setting</h1>
+            </header>
 
-          {/* 头像区域 */}
-          <div className="bg-[#FFF5F0] py-8 flex flex-col items-center">
-            <div className="w-20 h-20 bg-white rounded-full flex items-center justify-center border-2 border-black">
-              <span className="text-black font-bold text-lg">LOGO</span>
+            {/* 头像区域 */}
+            <div className="bg-[#FFF5F0] py-8 flex flex-col items-center">
+              <div className="w-20 h-20 bg-white rounded-full flex items-center justify-center border-2 border-black">
+                <span className="text-black font-bold text-lg">LOGO</span>
+              </div>
             </div>
-          </div>
 
-          {/* 菜单列表 */}
-          <div className="px-4 py-2">
-            {/* Information */}
-            <Link href={"/customer/account/edit"} className="py-4 border-b border-gray-100 flex justify-between items-center" > 
-            {/* <div > */}
-              <span className="text-black font-medium">Information</span>
-              <svg
-                xmlns="http://www.w3.org/2000/svg"
-                className="h-5 w-5 text-gray-400"
-                fill="none"
-                viewBox="0 0 24 24"
-                stroke="currentColor"
+            {/* 菜单列表 */}
+            <div className="px-4 py-2">
+              {/* Information */}
+              <Link
+                href={"/customer/account/edit"}
+                className="py-4 border-b border-gray-100 flex justify-between items-center"
+              >
+                {/* <div > */}
+                <span className="text-black font-medium">Information</span>
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  className="h-5 w-5 text-gray-400"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  stroke="currentColor"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M9 5l7 7-7 7"
+                  />
+                </svg>
+                {/* </div> */}
+              </Link>
+              {/* Address */}
+              <Link
+                href={"/customer/address"}
+                className="py-4 border-b border-gray-100 flex justify-between items-center"
               >
-                <path
-                  strokeLinecap="round"
-                  strokeLinejoin="round"
-                  strokeWidth={2}
-                  d="M9 5l7 7-7 7"
-                />
-              </svg>
-            {/* </div> */}
-            </Link>
+                {/* <div > */}
+                <span className="text-black font-medium">Address</span>
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  className="h-5 w-5 text-gray-400"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  stroke="currentColor"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M9 5l7 7-7 7"
+                  />
+                </svg>
+                {/* </div> */}
+              </Link>
+              {/* help & Support */}
+              <div
+                onClick={() => {
+                  setShowSetting(true);
+                }}
+                className="py-4 border-b border-gray-100 flex justify-between items-center"
+              >
+                {/* <div > */}
+                <span className="text-black font-medium">help & Support</span>
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  className="h-5 w-5 text-gray-400"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  stroke="currentColor"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M9 5l7 7-7 7"
+                  />
+                </svg>
+                {/* </div> */}
+              </div>
+              {/* Subscribe 开关 */}
+              <div className="py-4 border-b border-gray-100 flex justify-between items-center">
+                <span className="text-black font-medium">Subscribe</span>
+                <div className="relative w-12 h-6 rounded-full bg-black">
+                  <div className="absolute right-0.5 top-0.5 w-5 h-5 bg-white rounded-full"></div>
+                </div>
+              </div>
 
-            {/* Subscribe 开关 */}
-            <div className="py-4 border-b border-gray-100 flex justify-between items-center">
-              <span className="text-black font-medium">Subscribe</span>
-              <div className="relative w-12 h-6 rounded-full bg-black">
-                <div className="absolute right-0.5 top-0.5 w-5 h-5 bg-white rounded-full"></div>
+              {/* Log Out */}
+              <div className="py-4 border-b border-gray-100">
+                <button className="text-black font-medium w-full text-left">
+                  Log Out
+                </button>
               </div>
             </div>
 
-            {/* Log Out */}
-            <div className="py-4 border-b border-gray-100">
-              <button className="text-black font-medium w-full text-left">
-                Log Out
-              </button>
-            </div>
+            {/* 版本号 */}
+            <div className="py-6 text-center text-gray-400 text-sm">v1.1.0</div>
           </div>
-
-          {/* 版本号 */}
-          <div className="py-6 text-center text-gray-400 text-sm">v1.1.0</div>
         </div>
       </div>
-    </div>
+      <HelpModal
+        helpvisible={showSetting}
+        helponClose={() => setShowSetting(false)}
+      />
+    </>
   );
-}
+}

+ 4 - 2
src/components/customer/accountSwiper.tsx

@@ -1,5 +1,6 @@
 "use client";
 import { Swiper, SwiperSlide } from 'swiper/react';
+import Image from 'next/image';
 import 'swiper/css';
 // 模拟商品数据(和图片内容匹配)
 const productList = [
@@ -41,10 +42,11 @@ const AccountSwiper = () => {
           <SwiperSlide key={product.id} className="rounded-lg overflow-hidden">
             <div className="flex flex-col h-full">
               {/* 商品图片(带标签) */}
-              <div className="relative">
-                <img
+              <div className="relative w-full aspect-[3/4]">
+                <Image
                   src={product.imgUrl}
                   alt={product.title}
+                  fill
                   className="w-full h-auto object-cover rounded-t-lg"
                 />
                 {/* 促销标签(有内容才显示) */}

+ 40 - 0
src/components/head/HeadBack.tsx

@@ -0,0 +1,40 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+
+export default function HeadBack() {
+  const router = useRouter();
+
+  // 页面回退
+  const handleBack = () => {
+    router.back();
+  };
+
+  return (
+    <div className="relative flex items-center bg-[#eee] justify-between px-4 py-4  border-t-0">
+      <svg
+        className=" leading-none focus:outline-none w-6 h-6"
+        onClick={handleBack}
+        viewBox="0 0 1024 1024"
+        version="1.1"
+        xmlns="http://www.w3.org/2000/svg"
+        p-id="111033"
+        width="200"
+        height="200"
+      >
+        <path
+          d="M391.466667 512l339.882666-339.882667a46.592 46.592 0 1 0-65.877333-65.877333L292.693333 479.061333a46.592 46.592 0 0 0 0 65.877334l372.821334 372.821333a46.592 46.592 0 1 0 65.877333-65.877333L391.466667 512z"
+          p-id="111034"
+        ></path>
+      </svg>
+
+      {/* 标题文字 */}
+      <h1 className="absolute left-1/2 -translate-x-1/2 text-black text-xl font-bold tracking-wider">
+        ASTERIA
+      </h1>
+
+      {/* 占位,保证标题居中 */}
+      <div className="w-12"></div>
+    </div>
+  );
+}

+ 1 - 1
src/types/checkout/type.ts

@@ -295,7 +295,7 @@ export interface CreateCheckoutAddressOperation {
 export interface Country {
   code: string;
   name: string;
-  _id: number;
+  // _id: number;
   id: string;
 }
 

+ 3 - 3
src/utils/bagisto/index.ts

@@ -59,7 +59,7 @@ export async function restApiFetch<T>({
   revalidate = 60,
 }: {
   api: string;
-  method: "POST" | "GET";
+  method: "POST" | "GET" | "PUT" | "DELETE";
   cache?: RequestCache;
   headers?: HeadersInit | Record<string, string>;
   tags?: string[];
@@ -104,7 +104,7 @@ export async function restApiFetch<T>({
         Object.assign(baseHeaders, headers);
       }
     }
-console.log('restApiFetch --- baseHeaders:', baseHeaders)
+
     const param: RequestInit = {
       method: method,
       headers: baseHeaders,
@@ -117,7 +117,7 @@ console.log('restApiFetch --- baseHeaders:', baseHeaders)
     if(variables) {
       param.body = JSON.stringify({...variables});
     }
-
+console.log('restApiFetch --- baseHeaders:', baseHeaders,apiUrl, param)
     const result = await fetch(apiUrl, param);
     console.log('restApiFetch --- result:', result)
     const body = await result.json();