ads + blog + ...

This commit is contained in:
hamid zarghami
2025-04-19 15:35:22 +03:30
parent cdc611a65d
commit 1bf0b56a63
20 changed files with 236 additions and 73 deletions
+13
View File
@@ -0,0 +1,13 @@
<svg width="39" height="54" viewBox="0 0 39 54" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M31.1559 34.8412C31.1559 34.8412 24.6651 41.094 19.2416 46.3804V53.5924C18.6935 54.0179 38.4905 34.8845 38.4905 34.8845L19.2416 14.6909V22.3573L31.1559 34.8412Z" fill="#1D1D1B"/>
<path d="M19.1479 39.0818L0 18.643L0.237992 18.405L19.3931 38.8583L19.1479 39.0818Z" fill="#1D1D1B"/>
<path d="M19.1407 37.1635L1.75977 18.6719L1.99776 18.4412L19.3787 36.9399L19.1407 37.1635Z" fill="#1D1D1B"/>
<path d="M19.1334 35.2523L3.52661 18.708L3.7574 18.47L19.3786 35.0215L19.1334 35.2523Z" fill="#1D1D1B"/>
<path d="M19.1262 33.3337L5.28638 18.7366L5.50995 18.5059L19.3714 33.1029L19.1262 33.3337Z" fill="#1D1D1B"/>
<path d="M19.1191 31.4155L7.05347 18.7729L7.26982 18.5349L19.3571 31.1848L19.1191 31.4155Z" fill="#1D1D1B"/>
<path d="M0.375071 18.7368L0.144287 18.4988L19.1407 0L19.3715 0.237996L0.375071 18.7368Z" fill="#1D1D1B"/>
<path d="M2.15645 18.7442L1.91846 18.5134L19.1263 1.73828L19.3571 1.97628L2.15645 18.7442Z" fill="#1D1D1B"/>
<path d="M3.93049 18.7587L3.69971 18.5279L19.1117 3.48364L19.3425 3.71443L3.93049 18.7587Z" fill="#1D1D1B"/>
<path d="M5.71187 18.7658L5.47388 18.5422L19.0973 5.22168L19.3281 5.45968L5.71187 18.7658Z" fill="#1D1D1B"/>
<path d="M7.49326 18.7799L7.24805 18.5564L19.0901 6.95947L19.3137 7.19747L7.49326 18.7799Z" fill="#1D1D1B"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+10
View File
@@ -0,0 +1,10 @@
import * as api from "../service/AdsService";
import { useQuery } from "@tanstack/react-query";
export const useGetAds = (location: string) => {
return useQuery({
queryKey: ["ads", location],
queryFn: () => api.getAds(location),
enabled: !!location,
});
};
+8
View File
@@ -0,0 +1,8 @@
import axios from "@/config/axios";
export const getAds = async (location: string) => {
const { data } = await axios.get(
`/advertise/by-location?location=${location}`
);
return data;
};
+23
View File
@@ -0,0 +1,23 @@
export enum AdsDisplayLocation {
HOMEPAGE_TOP = "HOMEPAGE_TOP",
SINGLE_SERVICE_PAGE = "SINGLE_SERVICE_PAGE",
MY_SERVICES_PAGE = "MY_SERVICES_PAGE",
OTHER_SERVICES_TOP = "OTHER_SERVICES_TOP",
OTHER_SERVICES_LEFT = "OTHER_SERVICES_LEFT",
SERVICE = "SERVICE",
BLOG_PAGE_TOP_RIGHT = "BLOG_PAGE_TOP_RIGHT",
BLOG_PAGE_BOTTOM_RIGHT = "BLOG_PAGE_BOTTOM_RIGHT",
BLOG_PAGE_TOP_LEFT = "BLOG_PAGE_TOP_LEFT",
}
export type AdsItemType = {
id: string;
title: string;
imageUrl: string;
link: string;
isActive: boolean;
createdAt: string;
startDate: string;
endDate: string;
displayLocation: AdsDisplayLocation;
};
+45
View File
@@ -7,10 +7,15 @@ import MostVisited from './MostVisited'
import ContactUs from './ContactUs'
import { useGetBlogs } from '../hooks/useBlogsData'
import { useBlogStore } from '../store/BlogStore'
import { useGetAds } from '@/app/ads/hooks/useAdsData'
import { AdsDisplayLocation } from '@/app/ads/types/AdsTypes'
import Image from 'next/image'
const BlogList: FC = () => {
const { selectedCategory } = useBlogStore()
const { data } = useGetBlogs(selectedCategory)
const getAds = useGetAds(AdsDisplayLocation.BLOG_PAGE_TOP_RIGHT)
const getAdsBottom = useGetAds(AdsDisplayLocation.BLOG_PAGE_BOTTOM_RIGHT)
return (
<div className=''>
@@ -23,6 +28,46 @@ const BlogList: FC = () => {
<div className='mt-10 flex xl:flex-row flex-col gap-8'>
<div className='xl:w-[261px] w-full'>
<Category />
{
getAds.isSuccess && getAds.data.data.ads[0] &&
<a href={getAds.data?.data?.ads[0].link} target='_blank' className='mt-11 relative flex w-full aspect-square bg-white rounded-4xl overflow-hidden'>
<Image
alt={getAds.data.data.ads[0].title}
width={261}
height={550}
src={getAds.data.data.ads[0].imageUrl}
className='w-full backdrop-blur-[100px] aspect-square object-cover'
/>
<div className='absolute flex items-center px-4 w-full bottom-0 h-[86px] bg-black bg-opacity-5 backdrop-blur-[14px]'>
<div className='max-w-[80%] text-sm leading-6 text-white'>
{getAds.data.data.ads[0].title}
</div>
</div>
</a>
}
{
getAdsBottom.isSuccess && getAdsBottom.data.data.ads[0] &&
<a href={getAdsBottom.data?.data?.ads[0].link} target='_blank' className='flex-1 mt-11 bg-white relative rounded-4xl min-h-[500px] overflow-hidden flex'>
<Image
alt={getAdsBottom.data.data.ads[0].title}
width={261}
height={550}
src={getAdsBottom.data.data.ads[0].imageUrl}
className='w-full backdrop-blur-[100px] aspect-square object-cover'
/>
<div className='absolute flex items-center px-4 w-full bottom-0 h-[86px] bg-black bg-opacity-5 backdrop-blur-[14px]'>
<div className='max-w-[80%] text-sm leading-6 text-white'>
{getAdsBottom.data.data.ads[0].title}
</div>
</div>
</a>
}
</div>
<div className='flex-1 flex flex-col gap-8'>
+26 -4
View File
@@ -2,24 +2,27 @@ import BlogItem from '@/components/BlogItem'
import Image from 'next/image'
import { FC } from 'react'
import { useGetBlogCombined } from '../hooks/useBlogsData'
import { AdsDisplayLocation } from '@/app/ads/types/AdsTypes'
import { useGetAds } from '@/app/ads/hooks/useAdsData'
const HeroSection: FC = () => {
const { data } = useGetBlogCombined()
const getAdsTopLeft = useGetAds(AdsDisplayLocation.BLOG_PAGE_TOP_LEFT)
return (
<div>
<div className='flex gap-8 xl:mt-14 mt-8'>
{
data?.data?.pinnedBlogs?.map((item, index: number) => {
if (index === 0) {
return (
<div key={item.id} className='relative rounded-4xl overflow-hidden'>
<div key={item.id} className='relative flex-[2] rounded-4xl overflow-hidden h-fit bg-red-50'>
<Image
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] max-h-[450px]'
className='w-full rounded-4xl object-cover xl:min-h-[210px] min-h-[350px] max-h-[450px]'
/>
<div className='absolute bottom-0 h-[140px] modalGlass3 w-full p-6 text-white'>
@@ -27,7 +30,7 @@ const HeroSection: FC = () => {
{item.title}
</h4>
<div className="text-xs xl:text-sm mt-4 leading-7 truncate xl:text-clip xl:whitespace-normal">
<div className="text-xs xl:text-sm mt-4 leading-7 truncate xl:text-clip xl:whitespace-normal line-clamp-2">
{item.previewContent.replace(/<\/?[^>]+(>|$)/g, "")}
</div>
</div>
@@ -36,6 +39,25 @@ const HeroSection: FC = () => {
}
})
}
{
getAdsTopLeft.isSuccess && getAdsTopLeft.data.data.ads[0] &&
<a href={getAdsTopLeft.data?.data?.ads[0].link} target='_blank' className='flex-1 overflow-hidden relative hidden xl:flex bg-gray-100 rounded-4xl items-center justify-center'>
<Image
alt={getAdsTopLeft.data.data.ads[0].title}
width={261}
height={550}
src={getAdsTopLeft.data.data.ads[0].imageUrl}
className='w-full h-full backdrop-blur-[100px] aspect-square object-cover'
/>
<div className='absolute flex items-center px-4 w-full bottom-0 h-[86px] bg-black bg-opacity-5 backdrop-blur-[14px]'>
<div className='max-w-[80%] text-sm leading-6 text-white'>
{getAdsTopLeft.data.data.ads[0].title}
</div>
</div>
</a>
}
</div>
<div className='flex xl:flex-row flex-col gap-8 mt-10'>
+6 -1
View File
@@ -100,11 +100,13 @@ const Contact: NextPage = () => {
placeholder='نام و نام خانوادگی'
{...formik.getFieldProps('fullName')}
error_text={formik.touched.fullName && formik.errors.fullName ? formik.errors.fullName : undefined}
className='border-t-0 border-x-0 rounded-none place-black'
/>
<Input
placeholder='نام شرکت '
{...formik.getFieldProps('businessName')}
error_text={formik.touched.businessName && formik.errors.businessName ? formik.errors.businessName : undefined}
className='border-t-0 border-x-0 rounded-none place-black'
/>
</div>
<div className='rowTwoInput mt-8'>
@@ -112,19 +114,22 @@ const Contact: NextPage = () => {
placeholder='شماره تماس'
{...formik.getFieldProps('phone')}
error_text={formik.touched.phone && formik.errors.phone ? formik.errors.phone : undefined}
className='border-t-0 border-x-0 rounded-none place-black'
/>
<Input
placeholder='ایمیل'
{...formik.getFieldProps('email')}
error_text={formik.touched.email && formik.errors.email ? formik.errors.email : undefined}
className='border-t-0 border-x-0 rounded-none place-black'
/>
</div>
<div className='mt-8'>
<Textarea
label='پیام'
placeholder='پیام'
{...formik.getFieldProps('content')}
error_text={formik.touched.content && formik.errors.content ? formik.errors.content : undefined}
className='border-t-0 border-x-0 place-black rounded-none min-h-[40px]'
/>
</div>
@@ -38,7 +38,7 @@ const DanakServicesContent: FC = () => {
<div className='flex-1 xl:p-4 p-2 bg-white/23 rounded-4xl border-[3px] border-white'>
<Image
src='https://picsum.photos/seed/picsum/200/300'
src='/images/consulting.webp'
alt='service-1'
width={300}
height={300}
@@ -51,7 +51,7 @@ const DanakServicesContent: FC = () => {
<div className='max-w-maxWidth px-4 xl:px-0 mx-auto flex xl:flex-row flex-col items-center xl:gap-32 gap-14'>
<div className='flex-1 xl:p-4 p-2 bg-white/23 rounded-4xl border-[3px] border-white'>
<Image
src='https://picsum.photos/seed/picsum/200/300'
src='/images/security.webp'
alt='service-1'
width={300}
height={300}
@@ -102,7 +102,7 @@ const DanakServicesContent: FC = () => {
<div className='flex-1 xl:p-4 p-2 bg-white/23 rounded-4xl border-[3px] border-white'>
<Image
src='https://picsum.photos/seed/picsum/200/300'
src='/images/solutions.webp'
alt='service-1'
width={300}
height={300}
@@ -115,7 +115,7 @@ const DanakServicesContent: FC = () => {
<div className='max-w-maxWidth px-4 xl:px-0 mx-auto flex xl:flex-row flex-col items-center xl:gap-32 gap-14'>
<div className='flex-1 xl:p-4 p-2 bg-white/23 rounded-4xl border-[3px] border-white'>
<Image
src='https://picsum.photos/seed/picsum/200/300'
src='/images/support.webp'
alt='service-1'
width={300}
height={300}
+8
View File
@@ -35,6 +35,14 @@ textarea::placeholder {
color: #888888;
}
input.place-black::placeholder {
color: black;
}
textarea.place-black::placeholder {
color: black;
}
@theme {
--color-primary: black;
--color-description: #888888;
+3
View File
@@ -34,6 +34,9 @@ export default function RootLayout({
}>) {
return (
<html lang="fa">
<head>
<link rel="icon" href="/favicon.svg" sizes="any" />
</head>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
+31 -31
View File
@@ -29,60 +29,60 @@ const Representatives: NextPage = () => {
</h6>
<div className='flex gap-8 flex-wrap mt-14'>
<div className='xl:min-w-[23%] min-w-[60%] flex-1'>
<div className='xl:min-w-[23%] min-w-[60%] flex-1 flex'>
<InfoBox
icon={<TickCircle color='#FF00B8' size={24} />}
title='درامد خوب'
description='فروش نرم افزار یکی از مشاغل جدید و بسیار پر درآمد در بسیاری از کشورهاست. در کشور ما نیز اخیرا مشتریان نرم افزاری همچون سیلی در مقابل تولیدکنندگان و فروشندگان نرم افزاری ظهور پیدا کرده اند. به طوری که هیچ سقفی برای درآمد مدیران توانمند در این حرفه وجود ندارد.'
/>
</div>
<div className='xl:min-w-[23%] min-w-[60%] flex-1'>
<InfoBox
icon={<TickCircle color='#FF00B8' size={24} />}
title='درامد خوب'
description='فروش نرم افزار یکی از مشاغل جدید و بسیار پر درآمد در بسیاری از کشورهاست. در کشور ما نیز اخیرا مشتریان نرم افزاری همچون سیلی در مقابل تولیدکنندگان و فروشندگان نرم افزاری ظهور پیدا کرده اند. به طوری که هیچ سقفی برای درآمد مدیران توانمند در این حرفه وجود ندارد.'
/>
</div>
<div className='xl:min-w-[23%] min-w-[60%] flex-1'>
<InfoBox
icon={<TickCircle color='#FF00B8' size={24} />}
title='درامد خوب'
description='فروش نرم افزار یکی از مشاغل جدید و بسیار پر درآمد در بسیاری از کشورهاست. در کشور ما نیز اخیرا مشتریان نرم افزاری همچون سیلی در مقابل تولیدکنندگان و فروشندگان نرم افزاری ظهور پیدا کرده اند. به طوری که هیچ سقفی برای درآمد مدیران توانمند در این حرفه وجود ندارد.'
/>
</div>
<div className='xl:min-w-[23%] min-w-[60%] flex-1'>
<div className='xl:min-w-[23%] min-w-[60%] flex-1 flex'>
<InfoBox
icon={<TickCircle color='#0028FF' size={24} />}
title='محصولات داناک'
description='متفاوت و قدرتمند و به اعتراف مشتریان در نظر سنجی های انجام شده فوق العاده راحت است. لذا خود محصولات می توانند مشتری های بعدی خود را به سرعت جذب نماید.'
/>
</div>
<div className='xl:min-w-[23%] min-w-[60%] flex-1'>
<div className='xl:min-w-[23%] min-w-[60%] flex-1 flex'>
<InfoBox
icon={<TickCircle color='#FF00B8' size={24} />}
title='درامد خوب'
description='فروش نرم افزار یکی از مشاغل جدید و بسیار پر درآمد در بسیاری از کشورهاست. در کشور ما نیز اخیرا مشتریان نرم افزاری همچون سیلی در مقابل تولیدکنندگان و فروشندگان نرم افزاری ظهور پیدا کرده اند. به طوری که هیچ سقفی برای درآمد مدیران توانمند در این حرفه وجود ندارد.'
icon={<TickCircle color='#FF7B00' size={24} />}
title='مهارت'
description='روش های موفق پیشنهادی برای چگونگی افزایش مشتریان بالقوه، نحوه ارائه نرم افزار، نحوه قطعی کردن فروش و دیگر مهارت های فروش که در اختیار نمایندگان جدید قرار خواهد گرفت.'
/>
</div>
<div className='xl:min-w-[23%] min-w-[60%] flex-1'>
<div className='xl:min-w-[23%] min-w-[60%] flex-1 flex'>
<InfoBox
icon={<TickCircle color='#FF00B8' size={24} />}
title='درامد خوب'
description='فروش نرم افزار یکی از مشاغل جدید و بسیار پر درآمد در بسیاری از کشورهاست. در کشور ما نیز اخیرا مشتریان نرم افزاری همچون سیلی در مقابل تولیدکنندگان و فروشندگان نرم افزاری ظهور پیدا کرده اند. به طوری که هیچ سقفی برای درآمد مدیران توانمند در این حرفه وجود ندارد.'
icon={<TickCircle color='#00980F' size={24} />}
title='رشد فردی'
description='برگزاری دوره ای همایش هایی سراسری برای نمایندگان کل کشور و ایجاد شرایطی برای رشد و بروز شدن اطلاعات نمایندگان در زمینه های مختلف فروش و بازاریابی با حضور اساتید مطرح این علوم.'
/>
</div>
<div className='xl:min-w-[23%] min-w-[60%] flex-1'>
<div className='xl:min-w-[23%] min-w-[60%] flex-1 flex'>
<InfoBox
icon={<TickCircle color='#FF00B8' size={24} />}
title='درامد خوب'
description='فروش نرم افزار یکی از مشاغل جدید و بسیار پر درآمد در بسیاری از کشورهاست. در کشور ما نیز اخیرا مشتریان نرم افزاری همچون سیلی در مقابل تولیدکنندگان و فروشندگان نرم افزاری ظهور پیدا کرده اند. به طوری که هیچ سقفی برای درآمد مدیران توانمند در این حرفه وجود ندارد.'
icon={<TickCircle color='#FFB800' size={24} />}
title='ارتقا شغلی'
description='نمایندگانی که با انرژی فعالیت میکنند، درآمد نسبتا بالایی دارند به طوری که نمایندگان میتوانند برای ارتقای شغلی و درآمد زایی خود در این شرکت، اهداف دراز مدت تعیین کنند.'
/>
</div>
<div className='xl:min-w-[23%] min-w-[60%] flex-1'>
<div className='xl:min-w-[23%] min-w-[60%] flex-1 flex'>
<InfoBox
icon={<TickCircle color='#E63EF5' size={24} />}
title='سیاست ها'
description='سیاست ها و استراتژی های اعطای نمایندگی در شرکت داناک منحصر به فرد، نماینده محور می باشد.'
/>
</div>
<div className='xl:min-w-[23%] min-w-[60%] flex-1 flex'>
<InfoBox
icon={<TickCircle color='#FF2E00' size={24} />}
title='روابط عمومی'
description='کسانی که دارای روابط عمومی بالایی هستند و یا دوست دارند که روابط گسترده ای ایجاد کنند، بهترین ها خواهند بود.'
/>
</div>
<div className='xl:min-w-[23%] min-w-[60%] flex-1 flex'>
<InfoBox
icon={<TickCircle color='#0028FF' size={24} />}
title='محصولات داناک'
description='متفاوت و قدرتمند و به اعتراف مشتریان در نظر سنجی های انجام شده فوق العاده راحت است. لذا خود محصولات می توانند مشتری های بعدی خود را به سرعت جذب نماید.'
title='یکپارچگی مجموعه'
description='ارتباط نزدیک و صمیمانه دفتر مرکزی با نمایندگان و همراهی آن ها.'
/>
</div>
</div>
+1 -1
View File
@@ -26,7 +26,7 @@ const BlogItem: FC<Props> = ({ item }) => {
{item.title}
</h6>
<p id='blog-preview-content' className='mt-4 text-sm text-white' dangerouslySetInnerHTML={{ __html: item.previewContent }}>
<p id='blog-preview-content' className='mt-4 text-sm text-white line-clamp-3 leading-6' dangerouslySetInnerHTML={{ __html: item.previewContent }}>
</p>
</div>
</Link>
+1 -1
View File
@@ -14,7 +14,7 @@ const Button: FC<Props> = memo((props) => {
const { isLoading, percentage, label, children, className, ...rest } = props;
const buttonClass = clx(
'flex rounded-xl items-center justify-center text-center h-10 text-sm bg-primary text-white w-full',
'flex rounded-xl items-center justify-center text-center h-10 text-sm bg-primary text-white w-full cursor-pointer',
rest.disabled && 'cursor-not-allowed opacity-60',
className
);
+11
View File
@@ -96,3 +96,14 @@ export function toJalaliDate(
return formatter.format(parsedDate);
}
export function limitHtmlWords(htmlString: string, wordLimit: number): string {
// پاک کردن تگ‌ها
const plainText = htmlString.replace(/<[^>]*>/g, "");
// تقسیم و محدود کردن کلمات
const words = plainText.split(/\s+/).slice(0, wordLimit);
const result = words.join(" ") + "...";
return result;
}
+13
View File
@@ -1,6 +1,7 @@
import Input from '@/components/Input'
import { ArrowLeft } from 'iconsax-react'
import Image from 'next/image'
import Link from 'next/link'
import { FC } from 'react'
const Footer: FC = () => {
@@ -12,12 +13,14 @@ const Footer: FC = () => {
<div className='flex xl:flex-row flex-col xl:gap-24 gap-7 w-full justify-between xl:px-10'>
<div className='xl:max-w-[203px] flex flex-col items-center text-right xl:items-start xl:text-right'>
<Link href='/'>
<Image
src={'/images/logo.svg'}
width={140}
height={58}
alt='لوگو داناک'
/>
</Link>
<p className='mt-6 text-sm xl:text-justify text-right'>
ما در داناک هم تولیدکننده نرمافزارهای پیشرفته هستیم و هم بستری برای فروش محصولات نرمافزاری دیگران فراهم کردهایم. با ما هوشمندانه بسازید و حرفهای بفروشید.
@@ -31,13 +34,19 @@ const Footer: FC = () => {
</h6>
<ul className='xl:text-sm text-xs mt-4 flex flex-col gap-4 text-right'>
<li>
<Link href='/products'>
محصولات
</Link>
</li>
<li>
<Link href='/danak-services'>
خدمات
</Link>
</li>
<li>
<Link href='/blogs'>
مجله
</Link>
</li>
</ul>
</div>
@@ -65,10 +74,14 @@ const Footer: FC = () => {
</h6>
<ul className='xl:text-sm text-xs mt-4 flex flex-col gap-4 text-right'>
<li>
<Link href='/developers'>
توسعه دهندگان
</Link>
</li>
<li>
<Link href='/representatives'>
نمایندگان
</Link>
</li>
</ul>
</div>
+2
View File
@@ -12,7 +12,9 @@ const Header: FC = () => {
<Fragment>
<div className='w-full h-[100px] bg-white/26 rounded-4xl border-[3px] border-white px-10 xl:block hidden'>
<div className='flex items-center justify-between h-full'>
<Link href='/'>
<Image src='/images/logo.svg' alt='logo' width={150} height={40} />
</Link>
<ul className='flex items-center gap-8 text-sm'>
<Link href='/'>