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
+26 -6
View File
@@ -1,7 +1,14 @@
import { clx } from '@/helpers/utils'
import { FC } from 'react'
import { useGetBlogCategories } from '../hooks/useBlogsData'
import Image from 'next/image'
import { useBlogStore } from '../store/BlogStore'
const Category: FC = () => {
const { data } = useGetBlogCategories()
const { selectedCategory, setSelectedCategory } = useBlogStore()
console.log(data);
return (
<div className='w-full rounded-4xl bg-white p-4'>
<h4>
@@ -11,15 +18,28 @@ const Category: FC = () => {
<div className='flex flex-col gap-4 mt-7 text-sm'>
<div className={clx(
'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>
<h6>همه</h6>
</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>
<h6>تکنولوژی</h6>
{
data?.data?.categories?.map((item) => {
return (
<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>
)
+27 -8
View File
@@ -1,14 +1,22 @@
import BlogItem from '@/components/BlogItem'
import Image from 'next/image'
import { FC } from 'react'
import { useGetBlogCombined } from '../hooks/useBlogsData'
const HeroSection: FC = () => {
const { data } = useGetBlogCombined()
return (
<div>
<div className='relative rounded-4xl overflow-hidden'>
{
data?.data?.pinnedBlogs?.map((item, index: number) => {
if (index === 0) {
return (
<div key={item.id} className='relative rounded-4xl overflow-hidden'>
<Image
src={'/images/banner.webp'}
alt='banner'
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]'
@@ -16,19 +24,30 @@ const HeroSection: FC = () => {
<div className='absolute bottom-0 h-[140px] modalGlass3 w-full p-6 text-white'>
<h4 className='font-bold xl:text-base text-sm'>
سفارش نرمافزار اختصاصی: سرمایه‌ای برای آینده یا فقط هزینهای برای امروز؟
{item.title}
</h4>
<p className="text-xs xl:text-sm mt-4 leading-7 truncate xl:text-clip xl:whitespace-normal">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه...لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه...
{item.previewContent}
</p>
</div>
</div>
)
}
})
}
<div className='flex xl:flex-row flex-col gap-8 mt-10'>
<BlogItem />
<BlogItem />
<BlogItem />
{
data?.data?.mostVisitedBlogs?.map((item, index: number) => {
if (index !== 0) {
return (
<BlogItem key={item.id} item={item} />
)
}
})
}
</div>
</div>
)
+5 -5
View File
@@ -1,18 +1,18 @@
import Image from 'next/image'
import { FC } from 'react'
const MiniBlogItem: FC = () => {
import { Blog } from '../types/BlogTypes'
const MiniBlogItem: FC<{ item: Blog }> = ({ item }) => {
return (
<div className='flex items-center gap-2'>
<Image
src='https://picsum.photos/seed/picsum/200/300'
alt='blog'
src={item.imageUrl}
alt={item.title}
width={72}
height={72}
className='rounded-xl size-[72px] min-w-[72px] object-cover'
/>
<h6 className='text-xs leading-5 font-bold'>
سفارش نرمافزار اختصاصی: سرمایه‌ای برای آینده یا فقط هزینهای برای امروز؟
{item.title}
</h6>
</div>
)
+7 -3
View File
@@ -1,7 +1,9 @@
import { FC } from 'react'
import MiniBlogItem from './MiniBlogItem'
import { useGetBlogCombined } from '../hooks/useBlogsData'
const MostVisited: FC = () => {
const { data } = useGetBlogCombined()
return (
<div className='w-full rounded-4xl bg-white p-4'>
<h4>
@@ -9,9 +11,11 @@ const MostVisited: FC = () => {
</h4>
<div className='flex flex-col gap-6 mt-7 text-sm'>
<MiniBlogItem />
<MiniBlogItem />
<MiniBlogItem />
{
data?.data?.mostVisitedBlogs?.map((item) => {
return <MiniBlogItem key={item.id} item={item} />
})
}
</div>
</div>
)
+36 -3
View File
@@ -1,15 +1,48 @@
import { useQuery } from "@tanstack/react-query";
import { getBlogs } from "../service/BlogService";
import * as api from "../service/BlogService";
// برای client
export const useGetBlogs = () =>
useQuery({
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
export const blogsQueryOptions = {
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 { QueryClient } from '@tanstack/react-query'
import BlogList from './components/BlogList'
import { blogsQueryOptions } from './hooks/useBlogsData'
import { blogCategoriesQueryOptions, blogsQueryOptions, blogCombinedQueryOptions } from './hooks/useBlogsData'
export const metadata = {
title: 'مجله داناک | مقالات و آموزش‌ها',
@@ -9,8 +9,11 @@ export const metadata = {
}
export default async function BlogsPage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery(blogsQueryOptions)
await queryClient.prefetchQuery(blogCategoriesQueryOptions)
await queryClient.prefetchQuery(blogCombinedQueryOptions)
return (
<HydrationBoundary state={dehydrate(queryClient)}>
+18 -2
View File
@@ -1,5 +1,9 @@
import axios from "@/config/axios";
import { BlogResponse } from "../types/BlogTypes";
import {
BlogCategoryResponse,
BlogCombinedResponse,
BlogResponse,
} from "../types/BlogTypes";
export const getBlogs = async () => {
const { data } = await axios.get<BlogResponse>(`/blogs`);
@@ -7,6 +11,18 @@ export const getBlogs = 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;
};
+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 });
},
}));
+21 -5
View File
@@ -1,3 +1,5 @@
import { ApiResponse } from "@/shared/types/SharedTypes";
export interface Author {
id: string;
firstName: string;
@@ -33,11 +35,25 @@ export interface Pager {
nextPage: boolean;
}
export interface BlogResponse {
statusCode: number;
success: boolean;
data: {
export type BlogResponse = ApiResponse<{
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;
setOpenSidebar: (value: boolean) => void;
};
export interface ApiResponse<T> {
statusCode: number;
success: boolean;
data: T;
}