api call ssr landing + latest product + ...
This commit is contained in:
Generated
+35
-7
@@ -14,6 +14,7 @@
|
|||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@tanstack/react-query": "^5.84.1",
|
"@tanstack/react-query": "^5.84.1",
|
||||||
|
"@tanstack/react-query-devtools": "^5.85.5",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -1711,9 +1712,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/query-core": {
|
"node_modules/@tanstack/query-core": {
|
||||||
"version": "5.83.1",
|
"version": "5.85.5",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz",
|
||||||
"integrity": "sha512-OG69LQgT7jSp+5pPuCfzltq/+7l2xoweggjme9vlbCPa/d7D7zaqv5vN/S82SzSYZ4EDLTxNO1PWrv49RAS64Q==",
|
"integrity": "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tanstack/query-devtools": {
|
||||||
|
"version": "5.84.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.84.0.tgz",
|
||||||
|
"integrity": "sha512-fbF3n+z1rqhvd9EoGp5knHkv3p5B2Zml1yNRjh7sNXklngYI5RVIWUrUjZ1RIcEoscarUb0+bOvIs5x9dwzOXQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -1721,12 +1732,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/react-query": {
|
"node_modules/@tanstack/react-query": {
|
||||||
"version": "5.84.1",
|
"version": "5.85.5",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.84.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz",
|
||||||
"integrity": "sha512-zo7EUygcWJMQfFNWDSG7CBhy8irje/XY0RDVKKV4IQJAysb+ZJkkJPcnQi+KboyGUgT+SQebRFoTqLuTtfoDLw==",
|
"integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/query-core": "5.83.1"
|
"@tanstack/query-core": "5.85.5"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -1736,6 +1747,23 @@
|
|||||||
"react": "^18 || ^19"
|
"react": "^18 || ^19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/react-query-devtools": {
|
||||||
|
"version": "5.85.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.85.5.tgz",
|
||||||
|
"integrity": "sha512-6Ol6Q+LxrCZlQR4NoI5181r+ptTwnlPG2t7H9Sp3klxTBhYGunONqcgBn2YKRPsaKiYM8pItpKMdMXMEINntMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/query-devtools": "5.84.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tanstack/react-query": "^5.85.5",
|
||||||
|
"react": "^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tybys/wasm-util": {
|
"node_modules/@tybys/wasm-util": {
|
||||||
"version": "0.10.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@tanstack/react-query": "^5.84.1",
|
"@tanstack/react-query": "^5.84.1",
|
||||||
|
"@tanstack/react-query-devtools": "^5.85.5",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Image from 'next/image'
|
|||||||
import { Calendar, Eye, User } from 'iconsax-react'
|
import { Calendar, Eye, User } from 'iconsax-react'
|
||||||
import LastPost from '../components/LastPost'
|
import LastPost from '../components/LastPost'
|
||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import withLayout from '@/hoc/withLayout'
|
import Layout from '@/hoc/Layout'
|
||||||
|
|
||||||
const BlogDetail: NextPage = () => {
|
const BlogDetail: NextPage = () => {
|
||||||
return (
|
return (
|
||||||
@@ -127,4 +127,10 @@ const BlogDetail: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(BlogDetail)
|
export default function BlogDetailWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<BlogDetail />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
|
import Layout from '@/hoc/Layout'
|
||||||
import Categories from './components/Categories'
|
import Categories from './components/Categories'
|
||||||
import withLayout from '@/hoc/withLayout'
|
|
||||||
import BlogItem from './components/BlogItem'
|
import BlogItem from './components/BlogItem'
|
||||||
import LastPost from './components/LastPost'
|
import LastPost from './components/LastPost'
|
||||||
|
|
||||||
@@ -21,4 +21,10 @@ const BlogsPage: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(BlogsPage)
|
export default function BlogsPageWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<BlogsPage />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import CartItem from "@/app/cart/components/CartItem"
|
import CartItem from "@/app/cart/components/CartItem"
|
||||||
import CartSummary from "@/components/CartSummary"
|
import CartSummary from "@/components/CartSummary"
|
||||||
import withLayout from "@/hoc/withLayout"
|
import Layout from "@/hoc/Layout"
|
||||||
import { Trash } from "iconsax-react"
|
import { Trash } from "iconsax-react"
|
||||||
import { NextPage } from "next"
|
import { NextPage } from "next"
|
||||||
|
|
||||||
@@ -47,4 +47,10 @@ const Cart: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(Cart)
|
export default function CartWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Cart />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import CartSummary from '@/components/CartSummary'
|
import CartSummary from '@/components/CartSummary'
|
||||||
import withLayout from '@/hoc/withLayout'
|
import Layout from '@/hoc/Layout'
|
||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import TitleBack from '../components/TitleBack'
|
import TitleBack from '../components/TitleBack'
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||||
@@ -45,4 +45,10 @@ const CartPayment: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(CartPayment)
|
export default function CartPaymentWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<CartPayment />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import CartSummary from '@/components/CartSummary'
|
import CartSummary from '@/components/CartSummary'
|
||||||
import withLayout from '@/hoc/withLayout'
|
import Layout from '@/hoc/Layout'
|
||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import CartItem from '../components/CartItem'
|
import CartItem from '../components/CartItem'
|
||||||
import TitleBack from '../components/TitleBack'
|
import TitleBack from '../components/TitleBack'
|
||||||
@@ -46,4 +46,10 @@ const CartShipping: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(CartShipping)
|
export default function CartShippingWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<CartShipping />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,21 +2,35 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
import { Pagination } from 'swiper/modules';
|
import { Pagination } from 'swiper/modules';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { useGetLanding } from '../hooks/useHomeData';
|
||||||
|
|
||||||
const Carousel: FC = () => {
|
const Carousel: FC = () => {
|
||||||
|
const { data } = useGetLanding()
|
||||||
|
|
||||||
|
if (!data?.results?.sliders?.desktop || data.results.sliders.desktop.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className='mt-5 h-[400px] bg-gray-200 rounded-lg flex items-center justify-center'>
|
||||||
|
<p className="text-gray-500">اسلایدری موجود نیست</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-5 relative' style={{ zIndex: 0 }}>
|
<div className='mt-5 relative' style={{ zIndex: 0 }}>
|
||||||
<Swiper pagination={true} modules={[Pagination]} className="mySwiper pb-5" style={{ zIndex: 0, position: 'relative' }}>
|
<Swiper pagination={true} modules={[Pagination]} className="mySwiper pb-5" style={{ zIndex: 0, position: 'relative' }}>
|
||||||
<SwiperSlide>
|
{data.results.sliders.desktop.map((slider) => (
|
||||||
<div className='h-[400px] bg-red-50'>
|
<SwiperSlide key={slider._id}>
|
||||||
1
|
<div className='h-[400px] relative'>
|
||||||
</div>
|
<Image
|
||||||
</SwiperSlide>
|
src={slider.imageUrl}
|
||||||
<SwiperSlide>
|
alt={slider.altText}
|
||||||
<div className='h-[400px] bg-red-50'>
|
fill
|
||||||
1
|
className="object-cover rounded-lg"
|
||||||
</div>
|
/>
|
||||||
</SwiperSlide>
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
'use client'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import {
|
import {
|
||||||
Carousel,
|
Carousel,
|
||||||
@@ -7,9 +8,17 @@ import {
|
|||||||
CarouselPrevious,
|
CarouselPrevious,
|
||||||
} from "@/components/ui/carousel"
|
} from "@/components/ui/carousel"
|
||||||
import ProductCard from '@/components/ProductCard'
|
import ProductCard from '@/components/ProductCard'
|
||||||
|
import { useGetLanding } from '../hooks/useHomeData'
|
||||||
|
|
||||||
|
|
||||||
const NewestProducts: FC = () => {
|
const NewestProducts: FC = () => {
|
||||||
|
|
||||||
|
const { data: landing } = useGetLanding()
|
||||||
|
|
||||||
|
console.log('landing', landing?.results?.
|
||||||
|
latestProducts);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4 className='text-[#012B4D] text-lg'>جدیدترین محصولات </h4>
|
<h4 className='text-[#012B4D] text-lg'>جدیدترین محصولات </h4>
|
||||||
@@ -25,12 +34,14 @@ const NewestProducts: FC = () => {
|
|||||||
}}
|
}}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<CarouselContent className='gap-2'>
|
<CarouselContent className='gap-2 h-[380px]'>
|
||||||
{Array.from({ length: 5 }).map((_, index) => (
|
{
|
||||||
<CarouselItem key={index} className="basis-[292px]">
|
landing?.results?.latestProducts?.map((product) => (
|
||||||
<ProductCard />
|
<CarouselItem key={product._id} className="basis-[292px]">
|
||||||
</CarouselItem>
|
<ProductCard item={product} />
|
||||||
))}
|
</CarouselItem>
|
||||||
|
))
|
||||||
|
}
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
<CarouselPrevious className="left-auto -right-12 [&_svg]:-scale-x-100" />
|
<CarouselPrevious className="left-auto -right-12 [&_svg]:-scale-x-100" />
|
||||||
<CarouselNext className="right-auto -left-12 [&_svg]:-scale-x-100" />
|
<CarouselNext className="right-auto -left-12 [&_svg]:-scale-x-100" />
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import * as api from "../service/Service";
|
||||||
|
|
||||||
|
export const useGetLanding = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["landing"],
|
||||||
|
queryFn: api.getLanding,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLandingQueryOptions = {
|
||||||
|
queryKey: ["landing"],
|
||||||
|
queryFn: api.getLanding,
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import axios from "@/config/axios";
|
||||||
|
import { ILandingResponse } from "@/types/landing.types";
|
||||||
|
|
||||||
|
export const getLanding = async (): Promise<ILandingResponse> => {
|
||||||
|
const { data } = await axios.get<ILandingResponse>("/landing");
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -4,6 +4,8 @@ import "../../public/css/fontiran.css";
|
|||||||
import 'swiper/css';
|
import 'swiper/css';
|
||||||
import QueryProvider from "@/config/QueryProvider";
|
import QueryProvider from "@/config/QueryProvider";
|
||||||
import ToastContainer from "@/components/Toast";
|
import ToastContainer from "@/components/Toast";
|
||||||
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||||
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Create Next App",
|
||||||
@@ -21,6 +23,7 @@ export default function RootLayout({
|
|||||||
<QueryProvider>
|
<QueryProvider>
|
||||||
{children}
|
{children}
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
</QueryProvider>
|
</QueryProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+25
-5
@@ -1,5 +1,5 @@
|
|||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import withLayout from '@/hoc/withLayout'
|
import Layout from '@/hoc/Layout'
|
||||||
import Carousel from './home/components/Carousel'
|
import Carousel from './home/components/Carousel'
|
||||||
import HotOffer from './home/components/HotOffer'
|
import HotOffer from './home/components/HotOffer'
|
||||||
import Categories from './home/components/Categories'
|
import Categories from './home/components/Categories'
|
||||||
@@ -8,8 +8,13 @@ import Blogs from './home/components/Blogs'
|
|||||||
import NewestProducts from './home/components/LatestProducts'
|
import NewestProducts from './home/components/LatestProducts'
|
||||||
import Banners4 from './home/components/Banners4'
|
import Banners4 from './home/components/Banners4'
|
||||||
import PopularProducts from './home/components/PopularProducts'
|
import PopularProducts from './home/components/PopularProducts'
|
||||||
|
import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query'
|
||||||
|
import { Suspense } from 'react'
|
||||||
|
import PageLoading from '@/components/PageLoading'
|
||||||
|
import { getLandingQueryOptions } from './home/hooks/useHomeData'
|
||||||
|
|
||||||
|
const HomePage: NextPage = () => {
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Carousel />
|
<Carousel />
|
||||||
@@ -19,8 +24,7 @@ const Home: NextPage = () => {
|
|||||||
<Categories />
|
<Categories />
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-24'>
|
<div className='mt-24'>
|
||||||
<Banners3
|
<Banners3 />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -41,4 +45,20 @@ const Home: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(Home)
|
export default async function HomePageWithLayout() {
|
||||||
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
|
await queryClient.prefetchQuery(getLandingQueryOptions)
|
||||||
|
|
||||||
|
const dehydratedState = dehydrate(queryClient)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Suspense fallback={<PageLoading />}>
|
||||||
|
<HydrationBoundary state={dehydratedState}>
|
||||||
|
<HomePage />
|
||||||
|
</HydrationBoundary>
|
||||||
|
</Suspense>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import withLayout from '@/hoc/withLayout'
|
import Layout from '@/hoc/Layout'
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
@@ -60,4 +60,10 @@ const SingleProduct: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(SingleProduct)
|
export default function SingleProductWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<SingleProduct />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import withLayout from '@/hoc/withLayout'
|
import Layout from '@/hoc/Layout'
|
||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
@@ -51,4 +51,10 @@ const Products: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(Products)
|
export default function ProductsWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Products />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import withLayout from '@/hoc/withLayout'
|
import Layout from '@/hoc/Layout'
|
||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import Menu from '../components/Menu'
|
import Menu from '../components/Menu'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
@@ -86,4 +86,10 @@ const Comments: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(Comments)
|
export default function CommentsWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Comments />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import withLayout from "@/hoc/withLayout"
|
import Layout from "@/hoc/Layout"
|
||||||
import { NextPage } from "next"
|
import { NextPage } from "next"
|
||||||
import Menu from "../components/Menu"
|
import Menu from "../components/Menu"
|
||||||
import GridWrapper from "@/components/GridWrapper"
|
import GridWrapper from "@/components/GridWrapper"
|
||||||
@@ -20,4 +20,10 @@ const FavoritePage: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(FavoritePage)
|
export default function FavoritePageWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<FavoritePage />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import { Button } from '@/components/ui/button'
|
|||||||
import Modal from '@/components/Modal'
|
import Modal from '@/components/Modal'
|
||||||
import Select from '@/components/Select'
|
import Select from '@/components/Select'
|
||||||
import Input from '@/components/Input'
|
import Input from '@/components/Input'
|
||||||
import withLayout from '@/hoc/withLayout'
|
import Layout from '@/hoc/Layout'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import Menu from '../../components/Menu'
|
import Menu from '../../components/Menu'
|
||||||
|
|
||||||
@@ -418,4 +418,10 @@ const OrderDetail: FC = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(OrderDetail)
|
export default function OrderDetailWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<OrderDetail />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import withLayout from '@/hoc/withLayout'
|
import Layout from '@/hoc/Layout'
|
||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import Menu from '../components/Menu'
|
import Menu from '../components/Menu'
|
||||||
@@ -143,4 +143,10 @@ const Orders: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(Orders)
|
export default function OrdersWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Orders />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import React, { useState } from 'react'
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import Input from '@/components/Input'
|
import Input from '@/components/Input'
|
||||||
import { Edit2 } from 'lucide-react'
|
import { Edit2 } from 'lucide-react'
|
||||||
import withLayout from '@/hoc/withLayout'
|
import Layout from '@/hoc/Layout'
|
||||||
import { Heart, ShoppingCart } from 'iconsax-react'
|
import { Heart, ShoppingCart } from 'iconsax-react'
|
||||||
import Menu from './components/Menu'
|
import Menu from './components/Menu'
|
||||||
|
|
||||||
@@ -177,4 +177,10 @@ const ProfilePage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLayout(ProfilePage)
|
export default function ProfilePageWithLayout() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<ProfilePage />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,8 +1,49 @@
|
|||||||
|
import { IProduct } from '@/types/landing.types'
|
||||||
|
import { NumberFormat } from '@/config/func'
|
||||||
import { Heart } from 'iconsax-react'
|
import { Heart } from 'iconsax-react'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
|
||||||
const ProductCard: FC = () => {
|
type Props = {
|
||||||
|
item?: IProduct
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const ProductCard: FC<Props> = ({ item }) => {
|
||||||
|
if (item) return (
|
||||||
|
<div className='bg-white rounded-[40px] p-6 w-full max-w-[280px] mx-auto shadow'>
|
||||||
|
<div className='flex justify-center'>
|
||||||
|
<Image
|
||||||
|
src={item.imagesUrl.cover}
|
||||||
|
width={170}
|
||||||
|
height={170}
|
||||||
|
alt=''
|
||||||
|
className='max-w-[150px] max-h-[150px] object-contain'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 className='line-clamp-2 text-[#383E43] mt-6 text-sm text-center leading-5 h-12'>
|
||||||
|
{item.title_fa}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div className='flex justify-end items-center gap-1.5 mt-4'>
|
||||||
|
<div className='size-2 rounded-full bg-red-500'></div>
|
||||||
|
<div className='size-2 rounded-full bg-red-500'></div>
|
||||||
|
<div className='size-2 rounded-full bg-red-500'></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-5 text-sm'>
|
||||||
|
{NumberFormat(item.default_variant.price.selling_price)} تومان
|
||||||
|
</div>
|
||||||
|
<div className='mt-1 text-sm text-[#7F7F7F] line-through'>
|
||||||
|
{NumberFormat(item.default_variant.price.retailPrice)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Heart size={20} color='#000' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<div className='bg-white rounded-[40px] p-6 w-full max-w-[280px] mx-auto shadow'>
|
<div className='bg-white rounded-[40px] p-6 w-full max-w-[280px] mx-auto shadow'>
|
||||||
<div className='flex justify-center'>
|
<div className='flex justify-center'>
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export default function QueryProvider({ children }: { children: ReactNode }) {
|
|||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
staleTime: 1000 * 60 * 5, // دادهها برای 5 دقیقه معتبر هستند
|
staleTime: 1000 * 60 * 5, // دادهها برای 5 دقیقه معتبر هستند
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -32,26 +32,44 @@ export const timeAgo = (date: string | Date): string => {
|
|||||||
return "همین الان";
|
return "همین الان";
|
||||||
};
|
};
|
||||||
export const getToken = async () => {
|
export const getToken = async () => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return null; // در سرور token موجود نیست
|
||||||
|
}
|
||||||
return localStorage.getItem(TOKEN_NAME);
|
return localStorage.getItem(TOKEN_NAME);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRefreshToken = () => {
|
export const getRefreshToken = () => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return localStorage.getItem(REFRESH_TOKEN_NAME);
|
return localStorage.getItem(REFRESH_TOKEN_NAME);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setToken = (token: string) => {
|
export const setToken = (token: string) => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
localStorage.setItem(TOKEN_NAME, token);
|
localStorage.setItem(TOKEN_NAME, token);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setRefreshToken = (refreshToken: string) => {
|
export const setRefreshToken = (refreshToken: string) => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
localStorage.setItem(REFRESH_TOKEN_NAME, refreshToken);
|
localStorage.setItem(REFRESH_TOKEN_NAME, refreshToken);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeToken = () => {
|
export const removeToken = () => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
localStorage.removeItem(TOKEN_NAME);
|
localStorage.removeItem(TOKEN_NAME);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeRefreshToken = () => {
|
export const removeRefreshToken = () => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
localStorage.removeItem(REFRESH_TOKEN_NAME);
|
localStorage.removeItem(REFRESH_TOKEN_NAME);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import Footer from '@/share/Footer'
|
||||||
|
import Header from '@/share/Header'
|
||||||
|
import { FC } from 'react'
|
||||||
|
|
||||||
|
interface LayoutProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const Layout: FC<LayoutProps> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex flex-col">
|
||||||
|
<Header />
|
||||||
|
<main className="flex-1">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
|
|
||||||
import Footer from '@/share/Footer'
|
|
||||||
import Header from '@/share/Header'
|
|
||||||
import { FC, ComponentType } from 'react'
|
|
||||||
// import Header from '@/share/Header'
|
|
||||||
|
|
||||||
interface LayoutProps {
|
|
||||||
children: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const Layout: FC<LayoutProps> = ({ children }) => {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen flex flex-col">
|
|
||||||
<Header />
|
|
||||||
<main className="flex-1">
|
|
||||||
{children}
|
|
||||||
</main>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HOC function
|
|
||||||
function withLayout<P extends object>(WrappedComponent: ComponentType<P>) {
|
|
||||||
const WithLayoutComponent: FC<P> = (props) => {
|
|
||||||
return (
|
|
||||||
<Layout>
|
|
||||||
<WrappedComponent {...props} />
|
|
||||||
</Layout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
WithLayoutComponent.displayName = `withLayout(${WrappedComponent.displayName || WrappedComponent.name})`
|
|
||||||
|
|
||||||
return WithLayoutComponent
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withLayout
|
|
||||||
export { Layout }
|
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
export interface IShop {
|
||||||
|
_id: string;
|
||||||
|
shopName: string;
|
||||||
|
shopDescription: string;
|
||||||
|
logo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPrice {
|
||||||
|
order_limit: number;
|
||||||
|
retailPrice: number;
|
||||||
|
is_specialSale: boolean;
|
||||||
|
discount_percent: number;
|
||||||
|
specialSale_order_limit: number | null;
|
||||||
|
specialSale_quantity: number | null;
|
||||||
|
specialSale_endDate: string | null;
|
||||||
|
selling_price: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDefaultVariant {
|
||||||
|
_id: string;
|
||||||
|
market_status: string;
|
||||||
|
price: IPrice;
|
||||||
|
stock: number;
|
||||||
|
isFreeShip: boolean;
|
||||||
|
isWholeSale: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IImagesUrl {
|
||||||
|
cover: string;
|
||||||
|
list: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProduct {
|
||||||
|
_id: number;
|
||||||
|
title_fa: string;
|
||||||
|
title_en: string;
|
||||||
|
seoTitle: string | null;
|
||||||
|
seoDescription: string | null;
|
||||||
|
model: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
shop: IShop;
|
||||||
|
status: string;
|
||||||
|
imagesUrl: IImagesUrl;
|
||||||
|
url: string;
|
||||||
|
default_variant: IDefaultVariant;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICategory {
|
||||||
|
_id: string;
|
||||||
|
title_fa: string;
|
||||||
|
title_en: string;
|
||||||
|
icon: string;
|
||||||
|
imageUrl: string;
|
||||||
|
description: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISlider {
|
||||||
|
_id: string;
|
||||||
|
displayLocation: "desktop" | "mobile";
|
||||||
|
imageUrl: string;
|
||||||
|
altText: string;
|
||||||
|
title: string;
|
||||||
|
linkUrl: string;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
order: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISliders {
|
||||||
|
desktop: ISlider[];
|
||||||
|
mobile: ISlider[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBanner {
|
||||||
|
_id: string;
|
||||||
|
imageUrl: string;
|
||||||
|
altText: string;
|
||||||
|
title: string;
|
||||||
|
linkUrl: string;
|
||||||
|
isActive: boolean;
|
||||||
|
order: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILandingResults {
|
||||||
|
popularProducts: IProduct[];
|
||||||
|
incredibleOffers: IProduct[];
|
||||||
|
latestProducts: IProduct[];
|
||||||
|
topCategory: ICategory[];
|
||||||
|
sliders: ISliders;
|
||||||
|
banners: IBanner[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILandingResponse {
|
||||||
|
status: number;
|
||||||
|
success: boolean;
|
||||||
|
results: ILandingResults;
|
||||||
|
}
|
||||||
@@ -5,3 +5,6 @@ export interface IBaseResponse {
|
|||||||
export interface IResponse<T> extends IBaseResponse {
|
export interface IResponse<T> extends IBaseResponse {
|
||||||
data: T;
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Landing types
|
||||||
|
export * from "./landing.types";
|
||||||
|
|||||||
Reference in New Issue
Block a user