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-slot": "^1.2.3",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@tanstack/react-query-devtools": "^5.85.5",
|
||||
"axios": "^1.11.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -1711,9 +1712,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.83.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.1.tgz",
|
||||
"integrity": "sha512-OG69LQgT7jSp+5pPuCfzltq/+7l2xoweggjme9vlbCPa/d7D7zaqv5vN/S82SzSYZ4EDLTxNO1PWrv49RAS64Q==",
|
||||
"version": "5.85.5",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz",
|
||||
"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",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -1721,12 +1732,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.84.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.84.1.tgz",
|
||||
"integrity": "sha512-zo7EUygcWJMQfFNWDSG7CBhy8irje/XY0RDVKKV4IQJAysb+ZJkkJPcnQi+KboyGUgT+SQebRFoTqLuTtfoDLw==",
|
||||
"version": "5.85.5",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz",
|
||||
"integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.83.1"
|
||||
"@tanstack/query-core": "5.85.5"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -1736,6 +1747,23 @@
|
||||
"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": {
|
||||
"version": "0.10.0",
|
||||
"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-slot": "^1.2.3",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@tanstack/react-query-devtools": "^5.85.5",
|
||||
"axios": "^1.11.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
@@ -2,7 +2,7 @@ import Image from 'next/image'
|
||||
import { Calendar, Eye, User } from 'iconsax-react'
|
||||
import LastPost from '../components/LastPost'
|
||||
import { NextPage } from 'next'
|
||||
import withLayout from '@/hoc/withLayout'
|
||||
import Layout from '@/hoc/Layout'
|
||||
|
||||
const BlogDetail: NextPage = () => {
|
||||
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 Layout from '@/hoc/Layout'
|
||||
import Categories from './components/Categories'
|
||||
import withLayout from '@/hoc/withLayout'
|
||||
import BlogItem from './components/BlogItem'
|
||||
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 CartItem from "@/app/cart/components/CartItem"
|
||||
import CartSummary from "@/components/CartSummary"
|
||||
import withLayout from "@/hoc/withLayout"
|
||||
import Layout from "@/hoc/Layout"
|
||||
import { Trash } from "iconsax-react"
|
||||
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 withLayout from '@/hoc/withLayout'
|
||||
import Layout from '@/hoc/Layout'
|
||||
import { NextPage } from 'next'
|
||||
import TitleBack from '../components/TitleBack'
|
||||
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 withLayout from '@/hoc/withLayout'
|
||||
import Layout from '@/hoc/Layout'
|
||||
import { NextPage } from 'next'
|
||||
import CartItem from '../components/CartItem'
|
||||
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 { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Pagination } from 'swiper/modules';
|
||||
import Image from 'next/image';
|
||||
import { useGetLanding } from '../hooks/useHomeData';
|
||||
|
||||
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 (
|
||||
<div className='mt-5 relative' style={{ zIndex: 0 }}>
|
||||
<Swiper pagination={true} modules={[Pagination]} className="mySwiper pb-5" style={{ zIndex: 0, position: 'relative' }}>
|
||||
<SwiperSlide>
|
||||
<div className='h-[400px] bg-red-50'>
|
||||
1
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
<SwiperSlide>
|
||||
<div className='h-[400px] bg-red-50'>
|
||||
1
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
{data.results.sliders.desktop.map((slider) => (
|
||||
<SwiperSlide key={slider._id}>
|
||||
<div className='h-[400px] relative'>
|
||||
<Image
|
||||
src={slider.imageUrl}
|
||||
alt={slider.altText}
|
||||
fill
|
||||
className="object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
'use client'
|
||||
import { FC } from 'react'
|
||||
import {
|
||||
Carousel,
|
||||
@@ -7,9 +8,17 @@ import {
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel"
|
||||
import ProductCard from '@/components/ProductCard'
|
||||
import { useGetLanding } from '../hooks/useHomeData'
|
||||
|
||||
|
||||
const NewestProducts: FC = () => {
|
||||
|
||||
const { data: landing } = useGetLanding()
|
||||
|
||||
console.log('landing', landing?.results?.
|
||||
latestProducts);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className='text-[#012B4D] text-lg'>جدیدترین محصولات </h4>
|
||||
@@ -25,12 +34,14 @@ const NewestProducts: FC = () => {
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<CarouselContent className='gap-2'>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<CarouselItem key={index} className="basis-[292px]">
|
||||
<ProductCard />
|
||||
</CarouselItem>
|
||||
))}
|
||||
<CarouselContent className='gap-2 h-[380px]'>
|
||||
{
|
||||
landing?.results?.latestProducts?.map((product) => (
|
||||
<CarouselItem key={product._id} className="basis-[292px]">
|
||||
<ProductCard item={product} />
|
||||
</CarouselItem>
|
||||
))
|
||||
}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious className="left-auto -right-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 QueryProvider from "@/config/QueryProvider";
|
||||
import ToastContainer from "@/components/Toast";
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
@@ -21,6 +23,7 @@ export default function RootLayout({
|
||||
<QueryProvider>
|
||||
{children}
|
||||
<ToastContainer />
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+25
-5
@@ -1,5 +1,5 @@
|
||||
import { NextPage } from 'next'
|
||||
import withLayout from '@/hoc/withLayout'
|
||||
import Layout from '@/hoc/Layout'
|
||||
import Carousel from './home/components/Carousel'
|
||||
import HotOffer from './home/components/HotOffer'
|
||||
import Categories from './home/components/Categories'
|
||||
@@ -8,8 +8,13 @@ import Blogs from './home/components/Blogs'
|
||||
import NewestProducts from './home/components/LatestProducts'
|
||||
import Banners4 from './home/components/Banners4'
|
||||
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 (
|
||||
<div>
|
||||
<Carousel />
|
||||
@@ -19,8 +24,7 @@ const Home: NextPage = () => {
|
||||
<Categories />
|
||||
</div>
|
||||
<div className='mt-24'>
|
||||
<Banners3
|
||||
/>
|
||||
<Banners3 />
|
||||
</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 withLayout from '@/hoc/withLayout'
|
||||
import Layout from '@/hoc/Layout'
|
||||
import {
|
||||
Breadcrumb,
|
||||
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 {
|
||||
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 Menu from '../components/Menu'
|
||||
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 Menu from "../components/Menu"
|
||||
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 Select from '@/components/Select'
|
||||
import Input from '@/components/Input'
|
||||
import withLayout from '@/hoc/withLayout'
|
||||
import Layout from '@/hoc/Layout'
|
||||
import Image from 'next/image'
|
||||
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'
|
||||
import withLayout from '@/hoc/withLayout'
|
||||
import Layout from '@/hoc/Layout'
|
||||
import { NextPage } from 'next'
|
||||
import React, { useState } from 'react'
|
||||
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 Input from '@/components/Input'
|
||||
import { Edit2 } from 'lucide-react'
|
||||
import withLayout from '@/hoc/withLayout'
|
||||
import Layout from '@/hoc/Layout'
|
||||
import { Heart, ShoppingCart } from 'iconsax-react'
|
||||
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 Image from 'next/image'
|
||||
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 (
|
||||
<div className='bg-white rounded-[40px] p-6 w-full max-w-[280px] mx-auto shadow'>
|
||||
<div className='flex justify-center'>
|
||||
|
||||
@@ -21,7 +21,8 @@ export default function QueryProvider({ children }: { children: ReactNode }) {
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 1000 * 60 * 5, // دادهها برای 5 دقیقه معتبر هستند
|
||||
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -32,26 +32,44 @@ export const timeAgo = (date: string | Date): string => {
|
||||
return "همین الان";
|
||||
};
|
||||
export const getToken = async () => {
|
||||
if (typeof window === "undefined") {
|
||||
return null; // در سرور token موجود نیست
|
||||
}
|
||||
return localStorage.getItem(TOKEN_NAME);
|
||||
};
|
||||
|
||||
export const getRefreshToken = () => {
|
||||
if (typeof window === "undefined") {
|
||||
return null;
|
||||
}
|
||||
return localStorage.getItem(REFRESH_TOKEN_NAME);
|
||||
};
|
||||
|
||||
export const setToken = (token: string) => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(TOKEN_NAME, token);
|
||||
};
|
||||
|
||||
export const setRefreshToken = (refreshToken: string) => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(REFRESH_TOKEN_NAME, refreshToken);
|
||||
};
|
||||
|
||||
export const removeToken = () => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
localStorage.removeItem(TOKEN_NAME);
|
||||
};
|
||||
|
||||
export const removeRefreshToken = () => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
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 {
|
||||
data: T;
|
||||
}
|
||||
|
||||
// Landing types
|
||||
export * from "./landing.types";
|
||||
|
||||
Reference in New Issue
Block a user