api call ssr landing + latest product + ...

This commit is contained in:
hamid zarghami
2025-08-27 15:57:05 +03:30
parent b0f299fc70
commit 3f2a5878d0
27 changed files with 410 additions and 93 deletions
+35 -7
View File
@@ -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",
+1
View File
@@ -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",
+8 -2
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+24 -10
View File
@@ -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>
) )
+17 -6
View File
@@ -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" />
+14
View File
@@ -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,
};
+7
View File
@@ -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;
};
+3
View File
@@ -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
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+8 -2
View File
@@ -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>
)
}
+42 -1
View File
@@ -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'>
+2 -1
View File
@@ -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,
} }
} }
})) }))
+18
View File
@@ -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);
}; };
+21
View File
@@ -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
-39
View File
@@ -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 }
+102
View File
@@ -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;
}
+3
View File
@@ -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";