redirect url + accordion + blog + ...

This commit is contained in:
hamid zarghami
2025-04-21 12:09:01 +03:30
parent 486bf6a0a7
commit dbee916ad3
26 changed files with 452 additions and 103 deletions
+10 -42
View File
@@ -1,7 +1,8 @@
import InfoBox from '@/components/InfoBox'
import { AddCircle, TickCircle } from 'iconsax-react'
import { TickCircle } from 'iconsax-react'
import { NextPage } from 'next'
import React from 'react'
import Accordion from '@/components/Accordion'
import WhyDanak from '../home/WhyDanak'
const About: NextPage = () => {
@@ -63,19 +64,19 @@ const About: NextPage = () => {
صادرکنندگان نرم افزار
</div>
<div className='flex-1 xl:min-w-[30%] min-w-[90%] bg-[#ECEFF6] h-10 rounded-xl font-bold flex justify-center items-center'>
صادرکنندگان نرم افزار
مشاوره استقرار ISMS
</div>
<div className='flex-1 xl:min-w-[30%] min-w-[90%] bg-[#ECEFF6] h-10 rounded-xl font-bold flex justify-center items-center'>
صادرکنندگان نرم افزار
نظام صنفی
</div>
<div className='flex-1 xl:min-w-[30%] min-w-[90%] bg-[#ECEFF6] h-10 rounded-xl font-bold flex justify-center items-center'>
صادرکنندگان نرم افزار
GDPR
</div>
<div className='flex-1 xl:min-w-[30%] min-w-[90%] bg-[#ECEFF6] h-10 rounded-xl font-bold flex justify-center items-center'>
صادرکنندگان نرم افزار
ISO:27001
</div>
<div className='flex-1 min-w-[30%] bg-[#ECEFF6] h-10 rounded-xl font-bold flex justify-center items-center'>
صادرکنندگان نرم افزار
SSAE18
</div>
</div>
</div>
@@ -93,42 +94,9 @@ const About: NextPage = () => {
</div>
<div className='flex-1 flex flex-col gap-4'>
<div className='bg-white rounded-xl p-3 flex-1 h-fit'>
<div className='flex justify-between'>
<div className='font-bold'>
تعهد
</div>
<AddCircle
size={24}
color='black'
/>
</div>
</div>
<div className='bg-white rounded-xl p-3 flex-1 h-fit'>
<div className='flex justify-between'>
<div className='font-bold'>
تعهد
</div>
<AddCircle
size={24}
color='black'
/>
</div>
</div>
<div className='bg-white rounded-xl p-3 flex-1 h-fit'>
<div className='flex justify-between'>
<div className='font-bold'>
تعهد
</div>
<AddCircle
size={24}
color='black'
/>
</div>
</div>
<Accordion title='تعهد' content='ما در داناک متعهد هستیم که بهترین خدمات و محصولات را با کیفیت بالا به مشتریان خود ارائه دهیم و همواره به دنبال بهبود و ارتقای سطح خدمات خود هستیم.' />
<Accordion title='تعهد' content='ما در داناک متعهد هستیم که بهترین خدمات و محصولات را با کیفیت بالا به مشتریان خود ارائه دهیم و همواره به دنبال بهبود و ارتقای سطح خدمات خود هستیم.' />
<Accordion title='تعهد' content='ما در داناک متعهد هستیم که بهترین خدمات و محصولات را با کیفیت بالا به مشتریان خود ارائه دهیم و همواره به دنبال بهبود و ارتقای سطح خدمات خود هستیم.' />
</div>
</div>
-2
View File
@@ -14,7 +14,6 @@ interface PageProps {
const SingleBlog: NextPage<PageProps> = async ({ params }) => {
const { id } = await params
console.log("loading", id);
return (
<Suspense fallback={<Loading />}>
@@ -24,7 +23,6 @@ const SingleBlog: NextPage<PageProps> = async ({ params }) => {
}
async function BlogContent({ id }: { id: string }) {
console.log(id)
const queryClient = new QueryClient()
await Promise.all([
+112
View File
@@ -0,0 +1,112 @@
import { FC } from 'react'
import Input from '../../../components/Input'
import Textarea from '../../../components/Textarea'
import Button from '../../../components/Button'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import { ErrorType } from '../../../helpers/types'
import { toast } from 'react-toastify'
import { useCreateBlogComment } from '../hooks/useBlogsData'
import { CreateBlogCommentType } from '../types/BlogTypes'
import { useSharedStore } from '@/shared/store/sharedStore'
import Link from 'next/link'
import { LOGIN_URL } from '@/config/const'
type Props = {
refetch: () => void
id: string
}
const CreateReview: FC<Props> = ({ refetch, id }) => {
const { isLogin } = useSharedStore()
const createReview = useCreateBlogComment()
const formik = useFormik<CreateBlogCommentType>({
initialValues: {
title: '',
content: ''
},
validationSchema: Yup.object({
title: Yup.string().required('این فیلد الزامی است'),
content: Yup.string().required('این فیلد الزامی است'),
}),
onSubmit: (values) => {
createReview.mutate({ id: id, params: values }, {
onSuccess: () => {
formik.resetForm()
refetch()
},
onError: (error: ErrorType) => {
if (error.response && 'status' in error.response && error.response.status === 401) {
window.location.href = LOGIN_URL + '?redirect=' + window.location.href
} else {
toast.error(error.response?.data?.error?.message?.[0] || 'An error occurred')
}
}
})
},
})
return (
<div className='bg-white p-6 rounded-3xl xl:w-[360px] w-full'>
<div>
ارسال نظر
</div>
{
!isLogin ? (
<div className='mt-6 text-description text-sm'>
<div>
برای ارسال نظر لطفا وارد حساب کاربری خود شوید
</div>
<div className='mt-6 flex justify-center'>
<Link href={LOGIN_URL + '?redirect=' + window.location.href}>
<Button
label={'ورود | ثبت نام'}
className='w-fit px-7'
/>
</Link>
</div>
</div>
) : (
<div className='mt-6 text-description text-sm'>
<div className='mt-6'>
<Input
label={'عنوان'}
placeholder={'عنوان'}
{...formik.getFieldProps('title')}
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
/>
</div>
<div className='mt-6'>
<Textarea
label={'نظر'}
placeholder={'نظر'}
{...formik.getFieldProps('content')}
error_text={formik.touched.content && formik.errors.content ? formik.errors.content : ''}
/>
</div>
<div className='mt-6'>
<Button
label={'ارسال نظر'}
onClick={() => formik.handleSubmit()}
isLoading={createReview.isPending}
/>
</div>
</div>
)
}
</div>
)
}
export default CreateReview
+64 -3
View File
@@ -1,20 +1,21 @@
'use client'
import { Profile, Calendar } from 'iconsax-react'
import { Profile, Calendar, Tag } from 'iconsax-react'
import { FC } from 'react'
import Image from 'next/image'
// import MostVisited from '../components/MostVisited'
import ContactUs from '../components/ContactUs'
import { useGetBlogSingle } from '../hooks/useBlogsData'
import { toJalaliDate } from '@/config/func'
import { timeAgo, toJalaliDate } from '@/config/func'
import MostVisited from './MostVisited'
import { TelegramShareButton, WhatsappShareButton } from 'react-share'
import CreateReview from './CreateReview'
type Props = {
id: string
}
const SingleBlogPage: FC<Props> = ({ id }) => {
const { data } = useGetBlogSingle(id)
const { data, refetch } = useGetBlogSingle(id)
if (!data) return null
@@ -64,6 +65,20 @@ const SingleBlogPage: FC<Props> = ({ id }) => {
</p>
<div className='mt-6 flex items-center gap-2 text-description text-sm'>
<Tag size={20} color='#7D818C' />
<div>برچسب ها :</div>
{
data?.data?.blog?.tags.map((tag) => {
return (
<div key={tag} className='text-xs flex items-center h-6 rounded-md bg-[#EAEDF5] text-[#999999] px-4'>
{tag}
</div>
)
})
}
</div>
<div className='mt-6 flex gap-4 border-y border-[#E5E5E5] py-4'>
<div className='text-sm'>اشتراک گذاری</div>
@@ -84,6 +99,8 @@ const SingleBlogPage: FC<Props> = ({ id }) => {
/>
</TelegramShareButton>
</div>
</div>
<div className='xl:w-[261px]'>
@@ -96,6 +113,50 @@ const SingleBlogPage: FC<Props> = ({ id }) => {
</div>
</div>
</div>
<div className='mt-8 flex xl:flex-row flex-col gap-8 items-start'>
<CreateReview id={data?.data?.blog?.id} refetch={refetch} />
<div className='flex-1 w-full max-w-full bg-white p-6 rounded-3xl'>
<div className='text-sm'>
نظرات کاربران
</div>
{
data?.data?.comments?.length === 0 ?
<div className='flex justify-center items-center h-full mt-7'>
<div className='text-description text-sm'>
هیچ نظری ثبت نشده است
</div>
</div>
:
data?.data?.comments?.map((comment) => {
return (
<div key={comment.id} className='mt-5 border border-border rounded-3xl p-6'>
<div className='flex gap-2 items-center'>
<Image alt='avatar' width={24} height={24} src={comment.user?.profilePic || '/images/default-avatar.png'} className='size-6 rounded-full object-cover' />
<div className='text-description text-xs'>
{comment.user?.firstName + ' ' + comment.user?.lastName}
</div>
</div>
<div className='mt-4 flex justify-between'>
<div className='text-sm max-w-[70%] truncate'>
{comment.title}
</div>
<div className='text-xs text-description'>
{timeAgo(comment.createdAt)}
</div>
</div>
<div className='mt-4 text-[11px] leading-6'>
{comment.content}
</div>
</div>
)
})
}
</div>
</div>
</div>
)
}
+8 -1
View File
@@ -1,5 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { useQuery, useMutation } from "@tanstack/react-query";
import * as api from "../service/BlogService";
import { CreateBlogCommentType } from "../types/BlogTypes";
// برای client
export const useGetBlogs = (categoryId: string) =>
@@ -38,6 +39,12 @@ export const useGetBlogSingle = (id: string) =>
queryFn: () => api.getBlogSingle(id),
});
export const useCreateBlogComment = () =>
useMutation({
mutationFn: (variables: { id: string; params: CreateBlogCommentType }) =>
api.createBlogComment(variables.id, variables.params),
});
// برای server prefetch
export const blogsQueryOptions = {
queryKey: ["blogs", ""],
+12
View File
@@ -4,6 +4,7 @@ import {
BlogCombinedResponse,
BlogResponse,
BlogSingleResponse,
CreateBlogCommentType,
} from "../types/BlogTypes";
export const getBlogs = async (categoryId: string) => {
@@ -41,3 +42,14 @@ export const getBlogSingle = async (slug: string) => {
const { data } = await axios.get<BlogSingleResponse>(`/blogs/slug/${slug}`);
return data;
};
export const createBlogComment = async (
id: string,
params: CreateBlogCommentType
) => {
const { data } = await axios.post<BlogSingleResponse>(
`/blogs/${id}/comments`,
params
);
return data;
};
+23
View File
@@ -25,6 +25,7 @@ export interface Blog {
author: Author;
category: Category;
content: string;
tags: string[];
}
export interface Pager {
@@ -59,6 +60,28 @@ export type BlogStoreType = {
setSelectedCategory: (category: string) => void;
};
export type Comment = {
id: string;
createdAt: string;
title: string;
content: string;
status: string;
user: User;
};
export type User = {
id: string;
firstName: string;
lastName: string;
profilePic: string;
};
export type BlogSingleResponse = ApiResponse<{
blog: Blog;
comments: Comment[];
}>;
export type CreateBlogCommentType = {
title: string;
content: string;
};
+2 -2
View File
@@ -59,7 +59,7 @@ const Contact: NextPage = () => {
color='black'
/>
<div className='text-sm'>
۰۲۱-۲۱۲۳۲۱۲۳ | ۰۸۶-۲۱۰۰۲۰۰۱
۰۲۱-۹۱۶۹۳۵۳۳
</div>
</div>
@@ -85,7 +85,7 @@ const Contact: NextPage = () => {
color='black'
/>
<div className='text-sm'>
یوسف آباد، فتجی شقایق، پلاک ۵ ، واحد ۱۳
تهران - یوسف آباد - فتحی شقاقی - پلاک ۵ - واحد ۱۳
</div>
</div>
</div>
@@ -12,7 +12,7 @@ const DanakServicesContent: FC = () => {
</h1>
<div className='max-w-[300px] text-sm leading-6'>
ما در داناک با ترکیب خلاقیت، تخصص و تکنولوژیهای روز، راهکارهای نرمافزاری منحصربهفردی ارائه میدهیم که کسبوکار شما را به سطحی بالاتر میبرد.
داناک از ابتدای مسیر انتخاب تا استفاده از راهکارهای نرمافزاری در کنار شما خواهد بود.
</div>
</div>
+1 -1
View File
@@ -12,7 +12,7 @@ const Developers: NextPage = () => {
</h1>
<div className='flex xl:flex-row flex-col xl:gap-24 gap-10 mt-14'>
<div className='flex-[2] font-bold'>
<div className='flex-[2] leading-6 font-bold'>
چه در قالب فرد و یا شرکت ایده منحصر به فرد و خلاقانه ای دارید که نیاز به مشتری و گسترش بازار دارید؟
</div>
<div className='flex-[3] text-sm'>
+40 -33
View File
@@ -110,40 +110,47 @@ const DetailService: FC<{ id: string }> = ({ id }) => {
</div>
{
getDetailService.data?.data.danakService?.reviews?.map((item: ReviewItem) => {
return (
<div key={item.id} className='mt-5 border border-border rounded-3xl p-6'>
<div className='flex gap-2 items-center'>
<Image alt='avatar' width={24} height={24} src={item.user?.profilePic || defaultAvatar} className='size-6 rounded-full object-cover' />
<div className='text-description text-xs'>
{item.user?.firstName + ' ' + item.user?.lastName}
</div>
</div>
<div className='mt-4 flex justify-between'>
<div className='text-sm max-w-[70%] truncate'>
{item.title}
</div>
<div className='text-xs text-description'>
{moment(item.createdAt).fromNow()}
</div>
</div>
<div className='mt-2'>
<Rate
disabled
value={item.rating}
count={5}
style={{ fontSize: 14 }}
/>
</div>
<div className='mt-4 text-[11px] leading-6'>
{item.comment}
</div>
getDetailService.data?.data.danakService?.reviews?.length === 0 ?
<div className='flex justify-center items-center h-full mt-7'>
<div className='text-description text-sm'>
هیچ نظری ثبت نشده است
</div>
)
})
</div>
:
getDetailService.data?.data.danakService?.reviews?.map((item: ReviewItem) => {
return (
<div key={item.id} className='mt-5 border border-border rounded-3xl p-6'>
<div className='flex gap-2 items-center'>
<Image alt='avatar' width={24} height={24} src={item.user?.profilePic || defaultAvatar} className='size-6 rounded-full object-cover' />
<div className='text-description text-xs'>
{item.user?.firstName + ' ' + item.user?.lastName}
</div>
</div>
<div className='mt-4 flex justify-between'>
<div className='text-sm max-w-[70%] truncate'>
{item.title}
</div>
<div className='text-xs text-description'>
{moment(item.createdAt).fromNow()}
</div>
</div>
<div className='mt-2'>
<Rate
disabled
value={item.rating}
count={5}
style={{ fontSize: 14 }}
/>
</div>
<div className='mt-4 text-[11px] leading-6'>
{item.comment}
</div>
</div>
)
})
}
</div>
</div>
@@ -8,7 +8,7 @@ import Link from 'next/link'
import { DanakService } from '../types/ProductTypes'
import RadioGroup from '@/components/RadioGroup'
import Image from 'next/image'
import { CONSOLE_URL } from '@/config/const'
type Props = {
data: DanakService,
}
@@ -25,8 +25,8 @@ const ServiceHeader: FC<Props> = (props: Props) => {
const plan: any = data?.subscriptionPlans.find((item) => item.id === planSelected)
setFinalPrice(plan.finalPrice)
setPrice(plan?.price)
setFinalPrice(plan.price)
setPrice(plan?.originalPrice)
}, [data?.subscriptionPlans, planSelected])
@@ -82,11 +82,11 @@ const ServiceHeader: FC<Props> = (props: Props) => {
{NumberFormat(Number(price))}
</div>
}
<div>{NumberFormat(Number(price))}</div>
<div>{NumberFormat(Number(finalPrice))}</div>
</div>
<span>تومان</span>
</div>
<Link target='_blank' href={`https://console.danakcorp.com/services/detail/${data?.id}`}>
<Link target='_blank' href={`${CONSOLE_URL}/other-service/buy/${data?.id}/${planSelected}`}>
<Button
// onClick={handleBuy}
// isLoading={buyService.isPending}