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"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tanstack/react-query": "^5.73.3",
|
||||||
|
"@tanstack/react-query-devtools": "^5.73.3",
|
||||||
|
"axios": "^1.8.4",
|
||||||
"iconsax-react": "^0.0.8",
|
"iconsax-react": "^0.0.8",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"next": "15.2.3",
|
"next": "15.2.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@@ -21,11 +25,14 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.2.3",
|
"eslint-config-next": "15.2.3",
|
||||||
|
"i": "^0.3.7",
|
||||||
|
"npm": "^11.3.0",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { ArrowLeft, Calendar2, Profile } from 'iconsax-react'
|
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 (
|
return (
|
||||||
<div className='w-full rounded-4xl bg-white p-7 flex xl:flex-row flex-col gap-6'>
|
<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'>
|
<div className='flex-1'>
|
||||||
<h4 className='font-bold text-sm leading-6'>
|
<h4 className='font-bold text-sm leading-6'>
|
||||||
سفارش نرمافزار اختصاصی: سرمایهای برای آینده یا فقط هزینهای برای امروز؟
|
{item.title}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div className='mt-3 flex gap-3 text-xs'>
|
<div className='mt-3 flex gap-3 text-xs'>
|
||||||
@@ -17,7 +24,7 @@ const BigBlogItem: FC = () => {
|
|||||||
color='#7D818C'
|
color='#7D818C'
|
||||||
/>
|
/>
|
||||||
<h6 className='text-sm'>
|
<h6 className='text-sm'>
|
||||||
نویسنده
|
{item.author.firstName} {item.author.lastName}
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex text-[#7D818C] items-center gap-1'>
|
<div className='flex text-[#7D818C] items-center gap-1'>
|
||||||
@@ -26,13 +33,13 @@ const BigBlogItem: FC = () => {
|
|||||||
color='#7D818C'
|
color='#7D818C'
|
||||||
/>
|
/>
|
||||||
<h6 className='text-sm'>
|
<h6 className='text-sm'>
|
||||||
۱۴۰۳/۱۲/۰۱
|
{toJalaliDate(item.createdAt)}
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className='mt-3 text-sm leading-5'>
|
<p className='mt-3 text-sm leading-5'>
|
||||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه...
|
{item.previewContent}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className='mt-3 flex gap-1 text-sm text-[#8C90A3]'>
|
<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 { HydrationBoundary, dehydrate } from '@tanstack/react-query'
|
||||||
import HeroSection from './components/HeroSection'
|
import { QueryClient } from '@tanstack/react-query'
|
||||||
import Category from './components/Category'
|
import BlogList from './components/BlogList'
|
||||||
import BigBlogItem from './components/BigBlogItem'
|
import { blogsQueryOptions } from './hooks/useBlogsData'
|
||||||
import MostVisited from './components/MostVisited'
|
|
||||||
import ContactUs from './components/ContactUs'
|
|
||||||
const Blogs: FC = () => {
|
|
||||||
return (
|
|
||||||
<div className=''>
|
|
||||||
<HeroSection />
|
|
||||||
|
|
||||||
<h1 className='text-2xl font-bold mt-10'>
|
export const metadata = {
|
||||||
مجله داناک
|
title: 'مجله داناک | مقالات و آموزشها',
|
||||||
</h1>
|
description: 'مقالات جذاب، نکات آموزشی و اخبار دنیای نرمافزار در مجله داناک',
|
||||||
|
|
||||||
<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 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 {
|
.rowTwoInput {
|
||||||
@apply flex flex-col sm:flex-row sm:gap-5 gap-5;
|
@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 BlogItem from '@/components/BlogItem'
|
||||||
import SectionHeader from '@/components/SectionHeader'
|
import SectionHeader from '@/components/SectionHeader'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
import { useGetLanding } from './hooks/useHomeData'
|
||||||
|
|
||||||
const Blog: FC = () => {
|
const Blog: FC = () => {
|
||||||
|
|
||||||
|
const { data } = useGetLanding()
|
||||||
return (
|
return (
|
||||||
<div className='xl:mt-24 mt-14 max-w-maxWidth mx-auto'>
|
<div className='xl:mt-24 mt-14 max-w-maxWidth mx-auto'>
|
||||||
<SectionHeader
|
<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'>
|
<div className='flex xl:flex-row flex-col gap-8 xl:mt-16 mt-10 items-center'>
|
||||||
<BlogItem />
|
{
|
||||||
<BlogItem />
|
data?.data?.pinnedBlogs?.map((item) => {
|
||||||
<BlogItem />
|
return (
|
||||||
|
<BlogItem key={item.id} item={item} />
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import ServiceItem from '@/components/ServiceItem'
|
|||||||
import { clx } from '@/helpers/utils'
|
import { clx } from '@/helpers/utils'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||||
|
import { useGetLanding } from './hooks/useHomeData'
|
||||||
const DanakServices: FC = () => {
|
const DanakServices: FC = () => {
|
||||||
|
|
||||||
|
const { data } = useGetLanding()
|
||||||
const [categoryId, setCategoryId] = useState<string>('')
|
const [categoryId, setCategoryId] = useState<string>('')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -49,6 +50,7 @@ const DanakServices: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='xl:mt-16 mt-10 flex flex-wrap gap-8'>
|
<div className='xl:mt-16 mt-10 flex flex-wrap gap-8'>
|
||||||
|
|
||||||
<div className='xl:min-w-[30%] min-w-[70%] flex-1'>
|
<div className='xl:min-w-[30%] min-w-[70%] flex-1'>
|
||||||
<ServiceItem
|
<ServiceItem
|
||||||
isShort
|
isShort
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
'use client'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import ServiceItem from '@/components/ServiceItem'
|
import ServiceItem from '@/components/ServiceItem'
|
||||||
|
import { useGetLanding } from './hooks/useHomeData'
|
||||||
|
|
||||||
const DanakSuggestedService: FC = () => {
|
const DanakSuggestedService: FC = () => {
|
||||||
|
|
||||||
|
const { data } = useGetLanding()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='max-w-maxWidth mx-auto xl:mt-20 mt-12'>
|
<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'>
|
<h2 className='xl:text-2xl text-sm font-bold text-center xl:text-right'>
|
||||||
@@ -9,8 +14,14 @@ const DanakSuggestedService: FC = () => {
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className='xl:mt-14 mt-10 flex xl:flex-row flex-col gap-8'>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,30 +1,47 @@
|
|||||||
|
import 'swiper/css/navigation';
|
||||||
import Button from '@/components/Button'
|
import Button from '@/components/Button'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
import { Navigation } from 'swiper/modules'
|
||||||
|
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||||
|
import { useGetLanding } from './hooks/useHomeData';
|
||||||
|
|
||||||
const HeroSection: FC = () => {
|
const HeroSection: FC = () => {
|
||||||
|
|
||||||
|
const { data } = useGetLanding()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<Swiper navigation={true} modules={[Navigation]} className="mySwiper">
|
||||||
<Image
|
{
|
||||||
src={'/images/banner.webp'}
|
data?.data?.sliders?.map((item) => {
|
||||||
alt='banner'
|
return (
|
||||||
width={1920}
|
<SwiperSlide key={item.id}>
|
||||||
height={1080}
|
<div className='relative'>
|
||||||
className='w-full rounded-4xl object-cover xl:mt-14 mt-8 min-h-[210px]'
|
<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='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'>
|
<div className='max-w-maxWidth w-full mx-auto px-10 xl:text-4xl text-lg text-white'>
|
||||||
<p>داناک،</p>
|
<p>{item.title}</p>
|
||||||
<p className='xl:mt-5 font-bold'>تلفیق هنر و تکنولوژی</p>
|
<p className='xl:mt-5 font-bold'>{item.description}</p>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className='xl:mt-12 mt-5 !w-fit px-10 bg-white !text-black'
|
className='xl:mt-12 mt-5 !w-fit px-10 bg-white !text-black'
|
||||||
label='درباره ی ما'
|
label='درباره ی ما'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 Header from "@/shared/Header";
|
||||||
import Footer from "@/shared/Footer";
|
import Footer from "@/shared/Footer";
|
||||||
import SideBar from "@/shared/SideBar";
|
import SideBar from "@/shared/SideBar";
|
||||||
|
import QueryProvider from "@/config/QueryProvider";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@@ -32,12 +33,14 @@ export default function RootLayout({
|
|||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
<div className="font-irancell xl:p-16 p-4">
|
<QueryProvider>
|
||||||
<SideBar />
|
<div className="font-irancell xl:p-16 p-4">
|
||||||
<Header />
|
<SideBar />
|
||||||
{children}
|
<Header />
|
||||||
<Footer />
|
{children}
|
||||||
</div>
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</QueryProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
+11
-25
@@ -1,31 +1,17 @@
|
|||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import HeroSection from './home/HeroSection'
|
import { HydrationBoundary } from '@tanstack/react-query'
|
||||||
import DanakSuggestedService from './home/DanakSuggestedService'
|
import { dehydrate } from '@tanstack/react-query'
|
||||||
import WhyDanak from './home/WhyDanak'
|
import { QueryClient } from '@tanstack/react-query'
|
||||||
import DanakServices from './home/DanakServices'
|
import { landingQueryOptions } from './home/hooks/useHomeData'
|
||||||
import Blog from './home/Blog'
|
import LangingPage from './home/LangingPage'
|
||||||
import Collaboration from './home/Collaboration'
|
const Home: NextPage = async () => {
|
||||||
|
const queryClient = new QueryClient()
|
||||||
|
await queryClient.prefetchQuery(landingQueryOptions)
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||||
|
<LangingPage />
|
||||||
|
</HydrationBoundary>
|
||||||
<HeroSection />
|
|
||||||
|
|
||||||
<DanakSuggestedService />
|
|
||||||
|
|
||||||
<WhyDanak />
|
|
||||||
|
|
||||||
<DanakServices />
|
|
||||||
|
|
||||||
<Blog />
|
|
||||||
|
|
||||||
<Collaboration />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,32 @@
|
|||||||
|
import { Blog } from '@/app/blogs/types/BlogTypes'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
|
||||||
const BlogItem: FC = () => {
|
interface Props {
|
||||||
|
item: Blog
|
||||||
|
}
|
||||||
|
|
||||||
|
const BlogItem: FC<Props> = ({ item }) => {
|
||||||
return (
|
return (
|
||||||
<div className='flex-1 w-full min-h-[390px] rounded-4xl overflow-hidden relative'>
|
<div className='flex-1 w-full min-h-[390px] rounded-4xl overflow-hidden relative'>
|
||||||
<Image
|
<Image
|
||||||
src='https://picsum.photos/seed/picsum/200/300'
|
src={item.imageUrl}
|
||||||
layout='fill'
|
layout='fill'
|
||||||
objectFit='cover'
|
objectFit='cover'
|
||||||
alt='blog'
|
alt='blog'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='absolute top-6 left-6 bg-black text-white h-8 px-5 rounded-xl flex items-center text-sm'>
|
<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>
|
||||||
|
|
||||||
<div className='absolute bottom-0 left-0 w-full modalGlass3 h-[180px] rounded-b-3xl text-white p-6'>
|
<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'>
|
<h6 className='font-bold text-sm'>
|
||||||
سفارش نرمافزار اختصاصی: سرمایهای برای آینده یا فقط هزینهای برای امروز؟
|
{item.title}
|
||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
<p className='mt-4 text-sm'>
|
<p className='mt-4 text-sm'>
|
||||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه...
|
{item.previewContent}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,31 @@
|
|||||||
|
import { DanakSuggestService } from '@/app/home/types/HomeTypes'
|
||||||
import { ArrowLeft } from 'iconsax-react'
|
import { ArrowLeft } from 'iconsax-react'
|
||||||
|
import Image from 'next/image'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isShort?: boolean
|
isShort?: boolean
|
||||||
|
item: DanakSuggestService
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServiceItem: FC<Props> = (props) => {
|
const ServiceItem: FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className='flex-1 bg-white rounded-4xl xl:p-7 p-4'>
|
<div className='flex-1 bg-white rounded-4xl xl:p-7 p-4'>
|
||||||
<div className='flex justify-between'>
|
<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 className='h-8 bg-primary text-white px-4 rounded-2xl text-sm flex items-center'>
|
||||||
دسته بندی
|
دسته بندی
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className='mt-6 font-bold'>
|
<h3 className='mt-6 font-bold'>
|
||||||
دی منو
|
{props.item?.name}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className='text-xs font-light mt-1 flex justify-between'>
|
<div className='text-xs font-light mt-1 flex justify-between'>
|
||||||
<p>منو دیجیتال رستوران</p>
|
<p>{props.item.title}</p>
|
||||||
{
|
{
|
||||||
props.isShort &&
|
props.isShort &&
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
@@ -35,7 +40,7 @@ const ServiceItem: FC<Props> = (props) => {
|
|||||||
{
|
{
|
||||||
!props.isShort &&
|
!props.isShort &&
|
||||||
<p className='mt-6 text-sm leading-7'>
|
<p className='mt-6 text-sm leading-7'>
|
||||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم استلورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است
|
{props.item.description}
|
||||||
</p>
|
</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>
|
</li>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link href='/blog'>
|
<Link href='/blogs'>
|
||||||
<li>
|
<li>
|
||||||
مجله
|
مجله
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user