landing api
This commit is contained in:
Generated
+2859
-14
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,11 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.73.3",
|
||||
"@tanstack/react-query-devtools": "^5.73.3",
|
||||
"axios": "^1.8.4",
|
||||
"iconsax-react": "^0.0.8",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next": "15.2.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
@@ -21,11 +25,14 @@
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.2.3",
|
||||
"i": "^0.3.7",
|
||||
"npm": "^11.3.0",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import { FC } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { ArrowLeft, Calendar2, Profile } from 'iconsax-react'
|
||||
const BigBlogItem: FC = () => {
|
||||
import { Blog } from '../types/BlogTypes'
|
||||
import { toJalaliDate } from '@/config/func'
|
||||
|
||||
interface BigBlogItemProps {
|
||||
item: Blog
|
||||
}
|
||||
|
||||
const BigBlogItem: FC<BigBlogItemProps> = ({ item }) => {
|
||||
return (
|
||||
<div className='w-full rounded-4xl bg-white p-7 flex xl:flex-row flex-col gap-6'>
|
||||
<Image src='https://picsum.photos/seed/picsum/200/300' alt='blog' width={300} height={300} className='size-[150px] object-cover mx-auto xl:mx-0 rounded-4xl' />
|
||||
<Image src={item.imageUrl} alt='blog' width={300} height={300} className='size-[150px] object-cover mx-auto xl:mx-0 rounded-4xl' />
|
||||
<div className='flex-1'>
|
||||
<h4 className='font-bold text-sm leading-6'>
|
||||
سفارش نرمافزار اختصاصی: سرمایهای برای آینده یا فقط هزینهای برای امروز؟
|
||||
{item.title}
|
||||
</h4>
|
||||
|
||||
<div className='mt-3 flex gap-3 text-xs'>
|
||||
@@ -17,7 +24,7 @@ const BigBlogItem: FC = () => {
|
||||
color='#7D818C'
|
||||
/>
|
||||
<h6 className='text-sm'>
|
||||
نویسنده
|
||||
{item.author.firstName} {item.author.lastName}
|
||||
</h6>
|
||||
</div>
|
||||
<div className='flex text-[#7D818C] items-center gap-1'>
|
||||
@@ -26,13 +33,13 @@ const BigBlogItem: FC = () => {
|
||||
color='#7D818C'
|
||||
/>
|
||||
<h6 className='text-sm'>
|
||||
۱۴۰۳/۱۲/۰۱
|
||||
{toJalaliDate(item.createdAt)}
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className='mt-3 text-sm leading-5'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه...
|
||||
{item.previewContent}
|
||||
</p>
|
||||
|
||||
<div className='mt-3 flex gap-1 text-sm text-[#8C90A3]'>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
'use client'
|
||||
import { FC } from 'react'
|
||||
import HeroSection from './HeroSection'
|
||||
import Category from './Category'
|
||||
import BigBlogItem from './BigBlogItem'
|
||||
import MostVisited from './MostVisited'
|
||||
import ContactUs from './ContactUs'
|
||||
import { useGetBlogs } from '../hooks/useBlogsData'
|
||||
const BlogList: FC = () => {
|
||||
|
||||
const { data } = useGetBlogs()
|
||||
|
||||
return (
|
||||
<div className=''>
|
||||
<HeroSection />
|
||||
|
||||
<h1 className='text-2xl font-bold mt-10'>
|
||||
مجله داناک
|
||||
</h1>
|
||||
|
||||
<div className='mt-10 flex xl:flex-row flex-col gap-8'>
|
||||
<div className='xl:w-[261px] w-full'>
|
||||
<Category />
|
||||
</div>
|
||||
|
||||
<div className='flex-1 flex flex-col gap-8'>
|
||||
{data?.data.blogs.map((item) => (
|
||||
<BigBlogItem key={item.id} item={item} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='xl:w-[261px] w-full'>
|
||||
<MostVisited />
|
||||
<div className='mt-6'>
|
||||
<ContactUs />
|
||||
</div>
|
||||
<div className='mt-6'>
|
||||
<MostVisited />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogList
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getBlogs } from "../service/BlogService";
|
||||
|
||||
// برای client
|
||||
export const useGetBlogs = () =>
|
||||
useQuery({
|
||||
queryKey: ["blogs"],
|
||||
queryFn: getBlogs,
|
||||
});
|
||||
|
||||
// برای server prefetch
|
||||
export const blogsQueryOptions = {
|
||||
queryKey: ["blogs"],
|
||||
queryFn: getBlogs,
|
||||
};
|
||||
+17
-38
@@ -1,41 +1,20 @@
|
||||
import { FC } from 'react'
|
||||
import HeroSection from './components/HeroSection'
|
||||
import Category from './components/Category'
|
||||
import BigBlogItem from './components/BigBlogItem'
|
||||
import MostVisited from './components/MostVisited'
|
||||
import ContactUs from './components/ContactUs'
|
||||
const Blogs: FC = () => {
|
||||
return (
|
||||
<div className=''>
|
||||
<HeroSection />
|
||||
import { HydrationBoundary, dehydrate } from '@tanstack/react-query'
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import BlogList from './components/BlogList'
|
||||
import { blogsQueryOptions } from './hooks/useBlogsData'
|
||||
|
||||
<h1 className='text-2xl font-bold mt-10'>
|
||||
مجله داناک
|
||||
</h1>
|
||||
|
||||
<div className='mt-10 flex xl:flex-row flex-col gap-8'>
|
||||
<div className='xl:w-[261px] w-full'>
|
||||
<Category />
|
||||
</div>
|
||||
|
||||
<div className='flex-1 flex flex-col gap-8'>
|
||||
<BigBlogItem />
|
||||
<BigBlogItem />
|
||||
<BigBlogItem />
|
||||
</div>
|
||||
|
||||
<div className='xl:w-[261px] w-full'>
|
||||
<MostVisited />
|
||||
<div className='mt-6'>
|
||||
<ContactUs />
|
||||
</div>
|
||||
<div className='mt-6'>
|
||||
<MostVisited />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
export const metadata = {
|
||||
title: 'مجله داناک | مقالات و آموزشها',
|
||||
description: 'مقالات جذاب، نکات آموزشی و اخبار دنیای نرمافزار در مجله داناک',
|
||||
}
|
||||
|
||||
export default Blogs
|
||||
export default async function BlogsPage() {
|
||||
const queryClient = new QueryClient()
|
||||
await queryClient.prefetchQuery(blogsQueryOptions)
|
||||
|
||||
return (
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<BlogList />
|
||||
</HydrationBoundary>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import axios from "@/config/axios";
|
||||
import { BlogResponse } from "../types/BlogTypes";
|
||||
|
||||
export const getBlogs = async () => {
|
||||
const { data } = await axios.get<BlogResponse>(`/blogs`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getBlogCategories = async () => {
|
||||
const { data } = await axios.get<BlogResponse>(`/blogs/categories/public`);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
export interface Author {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
id: string;
|
||||
title: string;
|
||||
iconUrl: string;
|
||||
}
|
||||
|
||||
export interface Blog {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
previewContent: string;
|
||||
imageUrl: string;
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
viewCount: number;
|
||||
author: Author;
|
||||
category: Category;
|
||||
}
|
||||
|
||||
export interface Pager {
|
||||
page: number;
|
||||
limit: number;
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
prevPage: boolean;
|
||||
nextPage: boolean;
|
||||
}
|
||||
|
||||
export interface BlogResponse {
|
||||
statusCode: number;
|
||||
success: boolean;
|
||||
data: {
|
||||
pager: Pager;
|
||||
blogs: Blog[];
|
||||
};
|
||||
}
|
||||
@@ -86,3 +86,11 @@ tbody tr {
|
||||
.rowTwoInput {
|
||||
@apply flex flex-col sm:flex-row sm:gap-5 gap-5;
|
||||
}
|
||||
|
||||
.swiper-button-next {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.swiper-button-prev {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
+11
-3
@@ -1,8 +1,11 @@
|
||||
import BlogItem from '@/components/BlogItem'
|
||||
import SectionHeader from '@/components/SectionHeader'
|
||||
import { FC } from 'react'
|
||||
import { useGetLanding } from './hooks/useHomeData'
|
||||
|
||||
const Blog: FC = () => {
|
||||
|
||||
const { data } = useGetLanding()
|
||||
return (
|
||||
<div className='xl:mt-24 mt-14 max-w-maxWidth mx-auto'>
|
||||
<SectionHeader
|
||||
@@ -10,9 +13,14 @@ const Blog: FC = () => {
|
||||
/>
|
||||
|
||||
<div className='flex xl:flex-row flex-col gap-8 xl:mt-16 mt-10 items-center'>
|
||||
<BlogItem />
|
||||
<BlogItem />
|
||||
<BlogItem />
|
||||
{
|
||||
data?.data?.pinnedBlogs?.map((item) => {
|
||||
return (
|
||||
<BlogItem key={item.id} item={item} />
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,9 +4,10 @@ import ServiceItem from '@/components/ServiceItem'
|
||||
import { clx } from '@/helpers/utils'
|
||||
import { FC, useState } from 'react'
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
|
||||
import { useGetLanding } from './hooks/useHomeData'
|
||||
const DanakServices: FC = () => {
|
||||
|
||||
const { data } = useGetLanding()
|
||||
const [categoryId, setCategoryId] = useState<string>('')
|
||||
|
||||
return (
|
||||
@@ -49,6 +50,7 @@ const DanakServices: FC = () => {
|
||||
</div>
|
||||
|
||||
<div className='xl:mt-16 mt-10 flex flex-wrap gap-8'>
|
||||
|
||||
<div className='xl:min-w-[30%] min-w-[70%] flex-1'>
|
||||
<ServiceItem
|
||||
isShort
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
'use client'
|
||||
import { FC } from 'react'
|
||||
import ServiceItem from '@/components/ServiceItem'
|
||||
import { useGetLanding } from './hooks/useHomeData'
|
||||
|
||||
const DanakSuggestedService: FC = () => {
|
||||
|
||||
const { data } = useGetLanding()
|
||||
|
||||
return (
|
||||
<div className='max-w-maxWidth mx-auto xl:mt-20 mt-12'>
|
||||
<h2 className='xl:text-2xl text-sm font-bold text-center xl:text-right'>
|
||||
@@ -9,8 +14,14 @@ const DanakSuggestedService: FC = () => {
|
||||
</h2>
|
||||
|
||||
<div className='xl:mt-14 mt-10 flex xl:flex-row flex-col gap-8'>
|
||||
<ServiceItem />
|
||||
<ServiceItem />
|
||||
{
|
||||
data?.data?.danakSuggestServices?.map((item) => {
|
||||
return (
|
||||
<ServiceItem key={item.id} item={item} />
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,30 +1,47 @@
|
||||
import 'swiper/css/navigation';
|
||||
import Button from '@/components/Button'
|
||||
import Image from 'next/image'
|
||||
import { FC } from 'react'
|
||||
import { Navigation } from 'swiper/modules'
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
import { useGetLanding } from './hooks/useHomeData';
|
||||
|
||||
const HeroSection: FC = () => {
|
||||
|
||||
const { data } = useGetLanding()
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<Image
|
||||
src={'/images/banner.webp'}
|
||||
alt='banner'
|
||||
width={1920}
|
||||
height={1080}
|
||||
className='w-full rounded-4xl object-cover xl:mt-14 mt-8 min-h-[210px]'
|
||||
/>
|
||||
<Swiper navigation={true} modules={[Navigation]} className="mySwiper">
|
||||
{
|
||||
data?.data?.sliders?.map((item) => {
|
||||
return (
|
||||
<SwiperSlide key={item.id}>
|
||||
<div className='relative'>
|
||||
<Image
|
||||
src={item.imageUrl}
|
||||
alt='banner'
|
||||
width={1920}
|
||||
height={1080}
|
||||
className='w-full rounded-4xl object-cover xl:mt-14 mt-8 min-h-[210px]'
|
||||
/>
|
||||
|
||||
<div className='absolute flex items-center z-1 top-0 left-0 w-full h-full bg-black/10 rounded-4xl'>
|
||||
<div className='max-w-maxWidth w-full mx-auto px-10 xl:text-4xl text-lg text-white'>
|
||||
<p>داناک،</p>
|
||||
<p className='xl:mt-5 font-bold'>تلفیق هنر و تکنولوژی</p>
|
||||
<div className='absolute flex items-center z-1 top-0 left-0 w-full h-full bg-black/10 rounded-4xl'>
|
||||
<div className='max-w-maxWidth w-full mx-auto px-10 xl:text-4xl text-lg text-white'>
|
||||
<p>{item.title}</p>
|
||||
<p className='xl:mt-5 font-bold'>{item.description}</p>
|
||||
|
||||
<Button
|
||||
className='xl:mt-12 mt-5 !w-fit px-10 bg-white !text-black'
|
||||
label='درباره ی ما'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className='xl:mt-12 mt-5 !w-fit px-10 bg-white !text-black'
|
||||
label='درباره ی ما'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Swiper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client'
|
||||
import { NextPage } from 'next'
|
||||
import HeroSection from './HeroSection'
|
||||
import DanakSuggestedService from './DanakSuggestedService'
|
||||
import WhyDanak from './WhyDanak'
|
||||
import Blog from './Blog'
|
||||
import Collaboration from './Collaboration'
|
||||
|
||||
const Home: NextPage = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
|
||||
<HeroSection />
|
||||
|
||||
<DanakSuggestedService />
|
||||
|
||||
<WhyDanak />
|
||||
|
||||
{/* <DanakServices /> */}
|
||||
|
||||
<Blog />
|
||||
|
||||
<Collaboration />
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/HomeService";
|
||||
|
||||
// برای client
|
||||
export const useGetLanding = () =>
|
||||
useQuery({
|
||||
queryKey: ["landing"],
|
||||
queryFn: api.getLanding,
|
||||
});
|
||||
|
||||
// برای server prefetch
|
||||
export const landingQueryOptions = {
|
||||
queryKey: ["landing"],
|
||||
queryFn: api.getLanding,
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import axios from "@/config/axios";
|
||||
import { LandingResponse } from "../types/HomeTypes";
|
||||
|
||||
export const getLanding = async () => {
|
||||
const { data } = await axios.get<LandingResponse>(`/landing`);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Blog } from "@/app/blogs/types/BlogTypes";
|
||||
|
||||
export interface LandingResponse {
|
||||
statusCode: number;
|
||||
success: boolean;
|
||||
data: {
|
||||
sliders: SliderItemType[];
|
||||
pinnedBlogs: Blog[];
|
||||
danakSuggestServices: DanakSuggestService[];
|
||||
};
|
||||
}
|
||||
|
||||
export type SliderItemType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
title: string;
|
||||
description: string;
|
||||
imageUrl: string;
|
||||
link: string;
|
||||
order: number;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
export interface DanakSuggestService {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
name: string;
|
||||
title: string;
|
||||
isDanakSuggest: boolean;
|
||||
description: string;
|
||||
author: string;
|
||||
createDate: string;
|
||||
userCount: number;
|
||||
serviceLanguage: string;
|
||||
softwareLanguage: string;
|
||||
metaDescription: string;
|
||||
isActive: boolean;
|
||||
link: string;
|
||||
icon: string;
|
||||
deletedAt: string | null;
|
||||
}
|
||||
+9
-6
@@ -6,6 +6,7 @@ import "@/assets/fonts/irancell/style.css";
|
||||
import Header from "@/shared/Header";
|
||||
import Footer from "@/shared/Footer";
|
||||
import SideBar from "@/shared/SideBar";
|
||||
import QueryProvider from "@/config/QueryProvider";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@@ -32,12 +33,14 @@ export default function RootLayout({
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<div className="font-irancell xl:p-16 p-4">
|
||||
<SideBar />
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
<QueryProvider>
|
||||
<div className="font-irancell xl:p-16 p-4">
|
||||
<SideBar />
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
</QueryProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
+11
-25
@@ -1,31 +1,17 @@
|
||||
import { NextPage } from 'next'
|
||||
import HeroSection from './home/HeroSection'
|
||||
import DanakSuggestedService from './home/DanakSuggestedService'
|
||||
import WhyDanak from './home/WhyDanak'
|
||||
import DanakServices from './home/DanakServices'
|
||||
import Blog from './home/Blog'
|
||||
import Collaboration from './home/Collaboration'
|
||||
import { HydrationBoundary } from '@tanstack/react-query'
|
||||
import { dehydrate } from '@tanstack/react-query'
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { landingQueryOptions } from './home/hooks/useHomeData'
|
||||
import LangingPage from './home/LangingPage'
|
||||
const Home: NextPage = async () => {
|
||||
const queryClient = new QueryClient()
|
||||
await queryClient.prefetchQuery(landingQueryOptions)
|
||||
|
||||
const Home: NextPage = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
|
||||
<HeroSection />
|
||||
|
||||
<DanakSuggestedService />
|
||||
|
||||
<WhyDanak />
|
||||
|
||||
<DanakServices />
|
||||
|
||||
<Blog />
|
||||
|
||||
<Collaboration />
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<LangingPage />
|
||||
</HydrationBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
import { Blog } from '@/app/blogs/types/BlogTypes'
|
||||
import Image from 'next/image'
|
||||
import { FC } from 'react'
|
||||
|
||||
const BlogItem: FC = () => {
|
||||
interface Props {
|
||||
item: Blog
|
||||
}
|
||||
|
||||
const BlogItem: FC<Props> = ({ item }) => {
|
||||
return (
|
||||
<div className='flex-1 w-full min-h-[390px] rounded-4xl overflow-hidden relative'>
|
||||
<Image
|
||||
src='https://picsum.photos/seed/picsum/200/300'
|
||||
src={item.imageUrl}
|
||||
layout='fill'
|
||||
objectFit='cover'
|
||||
alt='blog'
|
||||
/>
|
||||
|
||||
<div className='absolute top-6 left-6 bg-black text-white h-8 px-5 rounded-xl flex items-center text-sm'>
|
||||
دسته بندی
|
||||
{item.category?.title}
|
||||
</div>
|
||||
|
||||
<div className='absolute bottom-0 left-0 w-full modalGlass3 h-[180px] rounded-b-3xl text-white p-6'>
|
||||
<h6 className='font-bold text-sm'>
|
||||
سفارش نرمافزار اختصاصی: سرمایهای برای آینده یا فقط هزینهای برای امروز؟
|
||||
{item.title}
|
||||
</h6>
|
||||
|
||||
<p className='mt-4 text-sm'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه...
|
||||
{item.previewContent}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
import { DanakSuggestService } from '@/app/home/types/HomeTypes'
|
||||
import { ArrowLeft } from 'iconsax-react'
|
||||
import Image from 'next/image'
|
||||
import { FC } from 'react'
|
||||
|
||||
type Props = {
|
||||
isShort?: boolean
|
||||
item: DanakSuggestService
|
||||
}
|
||||
|
||||
const ServiceItem: FC<Props> = (props) => {
|
||||
return (
|
||||
<div className='flex-1 bg-white rounded-4xl xl:p-7 p-4'>
|
||||
<div className='flex justify-between'>
|
||||
<div className='xl:size-[65px] size-[50px] rounded-2xl bg-orange-400'></div>
|
||||
<div className='xl:size-[65px] size-[50px] rounded-2xl overflow-hidden'>
|
||||
<Image src={props.item.icon} className='size-full object-cover' alt={props.item.name} width={65} height={65} />
|
||||
</div>
|
||||
<div className='h-8 bg-primary text-white px-4 rounded-2xl text-sm flex items-center'>
|
||||
دسته بندی
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className='mt-6 font-bold'>
|
||||
دی منو
|
||||
{props.item?.name}
|
||||
</h3>
|
||||
|
||||
<div className='text-xs font-light mt-1 flex justify-between'>
|
||||
<p>منو دیجیتال رستوران</p>
|
||||
<p>{props.item.title}</p>
|
||||
{
|
||||
props.isShort &&
|
||||
<div className='flex'>
|
||||
@@ -35,7 +40,7 @@ const ServiceItem: FC<Props> = (props) => {
|
||||
{
|
||||
!props.isShort &&
|
||||
<p className='mt-6 text-sm leading-7'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم استلورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است
|
||||
{props.item.description}
|
||||
</p>
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
'use client'
|
||||
|
||||
import { QueryClient, QueryClientProvider, HydrationBoundary } from '@tanstack/react-query'
|
||||
import { ReactNode, useState } from 'react'
|
||||
|
||||
export default function QueryProvider({ children }: { children: ReactNode }) {
|
||||
const [client] = useState(() => new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 1000 * 60 * 5, // دادهها برای 5 دقیقه معتبر هستند
|
||||
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={client}>
|
||||
<HydrationBoundary state={{}}>
|
||||
{children}
|
||||
</HydrationBoundary>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import axios from "axios";
|
||||
import { getToken } from "./func";
|
||||
// import { Pages } from "./Pages";
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_BASE_URL,
|
||||
});
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
axiosInstance.interceptors.request.use(async function (config) {
|
||||
const tokenValue = await getToken();
|
||||
const token = tokenValue ? tokenValue : "";
|
||||
|
||||
config.headers.Authorization = "Bearer " + token;
|
||||
return config;
|
||||
});
|
||||
|
||||
export default axiosInstance;
|
||||
@@ -0,0 +1,97 @@
|
||||
import Cookie from "js-cookie";
|
||||
|
||||
export function isEmail(input: string): boolean {
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
return emailRegex.test(input);
|
||||
}
|
||||
|
||||
export function NumberFormat(number: number): string {
|
||||
return Intl.NumberFormat("fa-IR").format(number);
|
||||
// return Intl.NumberFormat("fa-IR").format(number);
|
||||
}
|
||||
|
||||
export const timeAgo = (date: string | Date): string => {
|
||||
const now = new Date();
|
||||
const past = new Date(date);
|
||||
const diff = Math.floor((now.getTime() - past.getTime()) / 1000); // اختلاف بر حسب ثانیه
|
||||
|
||||
const units = [
|
||||
{ name: "سال", value: 60 * 60 * 24 * 365 },
|
||||
{ name: "ماه", value: 60 * 60 * 24 * 30 },
|
||||
{ name: "روز", value: 60 * 60 * 24 },
|
||||
{ name: "ساعت", value: 60 * 60 },
|
||||
{ name: "دقیقه", value: 60 },
|
||||
{ name: "ثانیه", value: 1 },
|
||||
];
|
||||
|
||||
for (const unit of units) {
|
||||
const amount = Math.floor(diff / unit.value);
|
||||
if (amount >= 1) return `${amount} ${unit.name} پیش`;
|
||||
}
|
||||
|
||||
return "همین الان";
|
||||
};
|
||||
export const getToken = () => {
|
||||
return Cookie.get(process.env.NEXT_PUBLIC_BASE_URL || "");
|
||||
};
|
||||
|
||||
export const getRefreshToken = () => {
|
||||
return Cookie.get(process.env.VITE_REFRESH_TOKEN_NAME || "");
|
||||
};
|
||||
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
const domain = isProduction ? ".danakcorp.com" : undefined;
|
||||
|
||||
export const setToken = (token: string) => {
|
||||
Cookie.set(process.env.NEXT_PUBLIC_TOKEN_NAME || "", token, {
|
||||
domain: domain || "",
|
||||
});
|
||||
};
|
||||
|
||||
export const setRefreshToken = (refreshToken: string) => {
|
||||
Cookie.set(process.env.NEXT_PUBLIC_REFRESH_TOKEN_NAME || "", refreshToken, {
|
||||
domain: domain || "",
|
||||
});
|
||||
};
|
||||
|
||||
export const removeToken = () => {
|
||||
Cookie.remove(process.env.NEXT_PUBLIC_TOKEN_NAME || "", {
|
||||
domain,
|
||||
path: "/",
|
||||
});
|
||||
};
|
||||
|
||||
export const removeRefreshToken = () => {
|
||||
Cookie.remove(process.env.NEXT_PUBLIC_REFRESH_TOKEN_NAME || "", {
|
||||
domain,
|
||||
path: "/",
|
||||
});
|
||||
};
|
||||
|
||||
export function toJalaliDate(
|
||||
date: Date | string | number,
|
||||
options?: Intl.DateTimeFormatOptions,
|
||||
locale: string = "fa-IR-u-ca-persian"
|
||||
): string {
|
||||
const parsedDate = new Date(date);
|
||||
|
||||
if (isNaN(parsedDate.getTime())) {
|
||||
throw new Error("Invalid date provided to toJalaliDate");
|
||||
}
|
||||
|
||||
const defaultOptions: Intl.DateTimeFormatOptions = {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
// weekday: "long",
|
||||
// hour: "numeric",
|
||||
// minute: "numeric",
|
||||
};
|
||||
|
||||
const formatter = new Intl.DateTimeFormat(locale, {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
});
|
||||
|
||||
return formatter.format(parsedDate);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ const Header: FC = () => {
|
||||
</li>
|
||||
</Link>
|
||||
|
||||
<Link href='/blog'>
|
||||
<Link href='/blogs'>
|
||||
<li>
|
||||
مجله
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user