blog
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)}>
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user