This commit is contained in:
hamid zarghami
2025-04-14 16:41:25 +03:30
parent 74c47893dd
commit 6320cc0784
10 changed files with 176 additions and 50 deletions
+28 -8
View File
@@ -1,7 +1,14 @@
import { clx } from '@/helpers/utils' import { clx } from '@/helpers/utils'
import { FC } from 'react' import { FC } from 'react'
import { useGetBlogCategories } from '../hooks/useBlogsData'
import Image from 'next/image'
import { useBlogStore } from '../store/BlogStore'
const Category: FC = () => { const Category: FC = () => {
const { data } = useGetBlogCategories()
const { selectedCategory, setSelectedCategory } = useBlogStore()
console.log(data);
return ( return (
<div className='w-full rounded-4xl bg-white p-4'> <div className='w-full rounded-4xl bg-white p-4'>
<h4> <h4>
@@ -10,16 +17,29 @@ const Category: FC = () => {
<div className='flex flex-col gap-4 mt-7 text-sm'> <div className='flex flex-col gap-4 mt-7 text-sm'>
<div className={clx( <div className={clx(
'h-10 bg-[#ECEFF6] rounded-xl p-2.5 flex items-center gap-2', 'h-10 bg-[#ECEFF6] rounded-xl p-2.5 flex items-center gap-2 ',
'bg-black text-white' selectedCategory === "" ? 'bg-black text-white' : ''
)}> )} onClick={() => setSelectedCategory("")}>
<div className='size-5 bg-white rounded-full'></div> <div className='size-5 bg-white rounded-full'></div>
<h6>همه</h6> <h6>همه</h6>
</div> </div>
<div className='h-10 bg-[#ECEFF6] rounded-xl p-2.5 flex items-center gap-2'> {
<div className='size-5 bg-black rounded-full'></div> data?.data?.categories?.map((item) => {
<h6>تکنولوژی</h6> return (
</div> <div key={item.id} className={clx(
'h-10 bg-[#ECEFF6] rounded-xl p-2.5 flex items-center gap-2 cursor-pointer',
selectedCategory === item.id ? 'bg-black text-white' : ''
)} onClick={() => setSelectedCategory(item.id)}>
<div className='size-5 rounded-full overflow-hidden'>
<Image src={item.iconUrl} alt={item.title} width={20} height={20} />
</div>
<h6>{item.title}</h6>
</div>
)
})
}
</div> </div>
</div> </div>
) )
+39 -20
View File
@@ -1,34 +1,53 @@
import BlogItem from '@/components/BlogItem' import BlogItem from '@/components/BlogItem'
import Image from 'next/image' import Image from 'next/image'
import { FC } from 'react' import { FC } from 'react'
import { useGetBlogCombined } from '../hooks/useBlogsData'
const HeroSection: FC = () => { const HeroSection: FC = () => {
const { data } = useGetBlogCombined()
return ( return (
<div> <div>
<div className='relative rounded-4xl overflow-hidden'> {
<Image data?.data?.pinnedBlogs?.map((item, index: number) => {
src={'/images/banner.webp'} if (index === 0) {
alt='banner' return (
width={1920} <div key={item.id} className='relative rounded-4xl overflow-hidden'>
height={1080} <Image
className='w-full rounded-4xl object-cover xl:mt-14 mt-8 xl:min-h-[210px] min-h-[350px]' src={item.imageUrl}
/> alt={item.title}
width={1920}
height={1080}
className='w-full rounded-4xl object-cover xl:mt-14 mt-8 xl:min-h-[210px] min-h-[350px]'
/>
<div className='absolute bottom-0 h-[140px] modalGlass3 w-full p-6 text-white'> <div className='absolute bottom-0 h-[140px] modalGlass3 w-full p-6 text-white'>
<h4 className='font-bold xl:text-base text-sm'> <h4 className='font-bold xl:text-base text-sm'>
سفارش نرمافزار اختصاصی: سرمایه‌ای برای آینده یا فقط هزینهای برای امروز؟ {item.title}
</h4> </h4>
<p className="text-xs xl:text-sm mt-4 leading-7 truncate xl:text-clip xl:whitespace-normal">
{item.previewContent}
</p>
</div>
</div>
)
}
})
}
<p className="text-xs xl:text-sm mt-4 leading-7 truncate xl:text-clip xl:whitespace-normal">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه...لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه...
</p>
</div>
</div>
<div className='flex xl:flex-row flex-col gap-8 mt-10'> <div className='flex xl:flex-row flex-col gap-8 mt-10'>
<BlogItem /> {
<BlogItem /> data?.data?.mostVisitedBlogs?.map((item, index: number) => {
<BlogItem /> if (index !== 0) {
return (
<BlogItem key={item.id} item={item} />
)
}
})
}
</div> </div>
</div> </div>
) )
+5 -5
View File
@@ -1,18 +1,18 @@
import Image from 'next/image' import Image from 'next/image'
import { FC } from 'react' import { FC } from 'react'
import { Blog } from '../types/BlogTypes'
const MiniBlogItem: FC = () => { const MiniBlogItem: FC<{ item: Blog }> = ({ item }) => {
return ( return (
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Image <Image
src='https://picsum.photos/seed/picsum/200/300' src={item.imageUrl}
alt='blog' alt={item.title}
width={72} width={72}
height={72} height={72}
className='rounded-xl size-[72px] min-w-[72px] object-cover' className='rounded-xl size-[72px] min-w-[72px] object-cover'
/> />
<h6 className='text-xs leading-5 font-bold'> <h6 className='text-xs leading-5 font-bold'>
سفارش نرمافزار اختصاصی: سرمایه‌ای برای آینده یا فقط هزینهای برای امروز؟ {item.title}
</h6> </h6>
</div> </div>
) )
+7 -3
View File
@@ -1,7 +1,9 @@
import { FC } from 'react' import { FC } from 'react'
import MiniBlogItem from './MiniBlogItem' import MiniBlogItem from './MiniBlogItem'
import { useGetBlogCombined } from '../hooks/useBlogsData'
const MostVisited: FC = () => { const MostVisited: FC = () => {
const { data } = useGetBlogCombined()
return ( return (
<div className='w-full rounded-4xl bg-white p-4'> <div className='w-full rounded-4xl bg-white p-4'>
<h4> <h4>
@@ -9,9 +11,11 @@ const MostVisited: FC = () => {
</h4> </h4>
<div className='flex flex-col gap-6 mt-7 text-sm'> <div className='flex flex-col gap-6 mt-7 text-sm'>
<MiniBlogItem /> {
<MiniBlogItem /> data?.data?.mostVisitedBlogs?.map((item) => {
<MiniBlogItem /> return <MiniBlogItem key={item.id} item={item} />
})
}
</div> </div>
</div> </div>
) )
+36 -3
View File
@@ -1,15 +1,48 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { getBlogs } from "../service/BlogService"; import * as api from "../service/BlogService";
// برای client // برای client
export const useGetBlogs = () => export const useGetBlogs = () =>
useQuery({ useQuery({
queryKey: ["blogs"], queryKey: ["blogs"],
queryFn: getBlogs, queryFn: api.getBlogs,
});
export const useGetBlogsPinned = () =>
useQuery({
queryKey: ["blogs-pinned"],
queryFn: api.getBlogsPinned,
});
export const useGetBlogCategories = () =>
useQuery({
queryKey: ["blog-categories"],
queryFn: api.getBlogCategories,
});
export const useGetBlogCombined = () =>
useQuery({
queryKey: ["blog-combined"],
queryFn: api.getCombinedBlogs,
}); });
// برای server prefetch // برای server prefetch
export const blogsQueryOptions = { export const blogsQueryOptions = {
queryKey: ["blogs"], queryKey: ["blogs"],
queryFn: getBlogs, queryFn: api.getBlogs,
};
export const blogsPinnedQueryOptions = {
queryKey: ["blogs-pinned"],
queryFn: api.getBlogsPinned,
};
export const blogCategoriesQueryOptions = {
queryKey: ["blog-categories"],
queryFn: api.getBlogCategories,
};
export const blogCombinedQueryOptions = {
queryKey: ["blog-combined"],
queryFn: api.getCombinedBlogs,
}; };
+4 -1
View File
@@ -1,7 +1,7 @@
import { HydrationBoundary, dehydrate } from '@tanstack/react-query' import { HydrationBoundary, dehydrate } from '@tanstack/react-query'
import { QueryClient } from '@tanstack/react-query' import { QueryClient } from '@tanstack/react-query'
import BlogList from './components/BlogList' import BlogList from './components/BlogList'
import { blogsQueryOptions } from './hooks/useBlogsData' import { blogCategoriesQueryOptions, blogsQueryOptions, blogCombinedQueryOptions } from './hooks/useBlogsData'
export const metadata = { export const metadata = {
title: 'مجله داناک | مقالات و آموزش‌ها', title: 'مجله داناک | مقالات و آموزش‌ها',
@@ -9,8 +9,11 @@ export const metadata = {
} }
export default async function BlogsPage() { export default async function BlogsPage() {
const queryClient = new QueryClient() const queryClient = new QueryClient()
await queryClient.prefetchQuery(blogsQueryOptions) await queryClient.prefetchQuery(blogsQueryOptions)
await queryClient.prefetchQuery(blogCategoriesQueryOptions)
await queryClient.prefetchQuery(blogCombinedQueryOptions)
return ( return (
<HydrationBoundary state={dehydrate(queryClient)}> <HydrationBoundary state={dehydrate(queryClient)}>
+18 -2
View File
@@ -1,5 +1,9 @@
import axios from "@/config/axios"; import axios from "@/config/axios";
import { BlogResponse } from "../types/BlogTypes"; import {
BlogCategoryResponse,
BlogCombinedResponse,
BlogResponse,
} from "../types/BlogTypes";
export const getBlogs = async () => { export const getBlogs = async () => {
const { data } = await axios.get<BlogResponse>(`/blogs`); const { data } = await axios.get<BlogResponse>(`/blogs`);
@@ -7,6 +11,18 @@ export const getBlogs = async () => {
}; };
export const getBlogCategories = async () => { export const getBlogCategories = async () => {
const { data } = await axios.get<BlogResponse>(`/blogs/categories/public`); const { data } = await axios.get<BlogCategoryResponse>(
`/blogs/categories/public`
);
return data;
};
export const getBlogsPinned = async () => {
const { data } = await axios.get<BlogResponse>(`/blogs/pinned`);
return data;
};
export const getCombinedBlogs = async () => {
const { data } = await axios.get<BlogCombinedResponse>(`/blogs/combined`);
return data; return data;
}; };
+9
View File
@@ -0,0 +1,9 @@
import { create } from "zustand";
import { BlogStoreType } from "../types/BlogTypes";
export const useBlogStore = create<BlogStoreType>((set) => ({
selectedCategory: "",
setSelectedCategory(value) {
set({ selectedCategory: value });
},
}));
+24 -8
View File
@@ -1,3 +1,5 @@
import { ApiResponse } from "@/shared/types/SharedTypes";
export interface Author { export interface Author {
id: string; id: string;
firstName: string; firstName: string;
@@ -33,11 +35,25 @@ export interface Pager {
nextPage: boolean; nextPage: boolean;
} }
export interface BlogResponse { export type BlogResponse = ApiResponse<{
statusCode: number; pager: Pager;
success: boolean; blogs: Blog[];
data: { }>;
pager: Pager;
blogs: Blog[]; export type BlogPinnedResponse = ApiResponse<{
}; blogs: Blog[];
} }>;
export type BlogCategoryResponse = ApiResponse<{
categories: Category[];
}>;
export type BlogCombinedResponse = ApiResponse<{
pinnedBlogs: Blog[];
mostVisitedBlogs: Blog[];
}>;
export type BlogStoreType = {
selectedCategory: string;
setSelectedCategory: (category: string) => void;
};
+6
View File
@@ -2,3 +2,9 @@ export type SharedStoreType = {
openSidebar: boolean; openSidebar: boolean;
setOpenSidebar: (value: boolean) => void; setOpenSidebar: (value: boolean) => void;
}; };
export interface ApiResponse<T> {
statusCode: number;
success: boolean;
data: T;
}