slug website

This commit is contained in:
hamid zarghami
2025-05-05 14:45:04 +03:30
parent 4a16d560d9
commit 78b02085c5
12 changed files with 229 additions and 12 deletions
+139
View File
@@ -153,3 +153,142 @@ tbody tr {
.animate-slide-up { .animate-slide-up {
animation: slideUp 1s ease-out forwards; animation: slideUp 1s ease-out forwards;
} }
@keyframes fade-in {
0% {
opacity: 0;
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes pattern-fade-in {
0% {
opacity: 0;
}
50% {
opacity: 0.05;
}
100% {
opacity: 0.05;
}
}
@keyframes logo-float {
0% {
transform: scale(0.9);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes logo-glow {
0% {
filter: drop-shadow(0 0 0 rgba(0, 0, 0, 0));
}
50% {
filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.1));
}
100% {
filter: drop-shadow(0 0 0 rgba(0, 0, 0, 0));
}
}
@keyframes progress-bar {
0% {
width: 0%;
}
100% {
width: 100%;
}
}
.animate-fade-in {
animation: fade-in 0.8s ease-out forwards;
}
.animate-pattern-fade-in {
animation: pattern-fade-in 1.5s ease-out forwards;
}
.animate-logo-float {
animation: logo-float 0.8s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.animate-logo-glow {
animation: logo-glow 3s ease-in-out infinite;
}
.animate-progress-bar {
animation: progress-bar 2.5s ease-out forwards;
}
@keyframes circle-pulse {
0% {
transform: scale(0.8);
opacity: 0;
}
50% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(1.2);
opacity: 0;
}
}
@keyframes circle-pulse-delayed {
0% {
transform: scale(0.8);
opacity: 0;
}
25% {
transform: scale(0.9);
opacity: 0.5;
}
75% {
transform: scale(1.1);
opacity: 0.5;
}
100% {
transform: scale(1.3);
opacity: 0;
}
}
@keyframes wave {
0% {
transform: translateX(0) translateY(0);
}
50% {
transform: translateX(-25%) translateY(-10px);
}
100% {
transform: translateX(-50%) translateY(0);
}
}
.animate-circle-pulse {
animation: circle-pulse 3s ease-out infinite;
}
.animate-circle-pulse-delayed {
animation: circle-pulse-delayed 3s ease-out infinite;
}
.animate-wave {
background: linear-gradient(
90deg,
transparent,
rgba(0, 0, 0, 0.05),
transparent
);
animation: wave 10s linear infinite;
}
+1
View File
@@ -41,4 +41,5 @@ export interface DanakSuggestService {
icon: string; icon: string;
deletedAt: string | null; deletedAt: string | null;
category: ServiceCategoryType; category: ServiceCategoryType;
slug: string;
} }
+2
View File
@@ -12,6 +12,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import QueryProvider from "@/config/QueryProvider"; import QueryProvider from "@/config/QueryProvider";
import ToastContainer from "@/components/Toast"; import ToastContainer from "@/components/Toast";
// import SplashChatgpt from "@/components/SplashChatgpt";
// import Splash from "@/components/Splash"; // import Splash from "@/components/Splash";
const geistSans = Geist({ const geistSans = Geist({
@@ -53,6 +54,7 @@ export default function RootLayout({
{/* <ToastContainer /> */} {/* <ToastContainer /> */}
<ToastContainer /> <ToastContainer />
{/* <Splash /> */} {/* <Splash /> */}
{/* <SplashChatgpt /> */}
</QueryProvider> </QueryProvider>
</body> </body>
</html> </html>
+2 -2
View File
@@ -1,6 +1,6 @@
'use client' 'use client'
import { FC, Fragment } from 'react' import { FC, Fragment } from 'react'
import { useGetDetailService } from './hooks/useProductData' import { useGetProductBySlug } from './hooks/useProductData'
import ServiceHeader from './components/ServiceHeader' import ServiceHeader from './components/ServiceHeader'
import moment from 'moment-jalaali' import moment from 'moment-jalaali'
import ServiceImages from './components/ServiceImages' import ServiceImages from './components/ServiceImages'
@@ -18,7 +18,7 @@ const DetailService: FC<{ id: string }> = ({ id }) => {
// const { t } = useTranslation('global') // const { t } = useTranslation('global')
// const getAdsLeft = useGetAds(AdsDisplayLocation.SINGLE_SERVICE_PAGE) // const getAdsLeft = useGetAds(AdsDisplayLocation.SINGLE_SERVICE_PAGE)
const getDetailService = useGetDetailService(id) const getDetailService = useGetProductBySlug(id)
// Show the skeleton loading component while data is loading // Show the skeleton loading component while data is loading
if (getDetailService.isLoading) { if (getDetailService.isLoading) {
+2 -2
View File
@@ -34,7 +34,7 @@ const Products: FC = () => {
return ( return (
<SwiperSlide key={item.id} className='xl:max-w-[45%] max-w-[90%]'> <SwiperSlide key={item.id} className='xl:max-w-[45%] max-w-[90%]'>
<div className='rounded-4xl overflow-hidden relative'> <div className='rounded-4xl overflow-hidden relative'>
<Link href={`/products/${item.id}`}> <Link href={`/products/${item.slug}`}>
<Image <Image
src={item.coverUrl} src={item.coverUrl}
alt='product' alt='product'
@@ -70,7 +70,7 @@ const Products: FC = () => {
</div> </div>
</div> </div>
</div> </div>
<Link href={`/products/${item.id}`}> <Link href={`/products/${item.slug}`}>
<div className='flex gap-1.5 text-sm items-center'> <div className='flex gap-1.5 text-sm items-center'>
<div>جزییات</div> <div>جزییات</div>
<ArrowLeft <ArrowLeft
+5 -4
View File
@@ -1,6 +1,6 @@
import { Suspense } from 'react' import { Suspense } from 'react'
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query' import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { detailServiceQueryOptions } from '../hooks/useProductData' import { getProductBySlugQueryOptions } from '../hooks/useProductData'
import { Metadata, NextPage } from 'next' import { Metadata, NextPage } from 'next'
import DetailService from '../DetailService' import DetailService from '../DetailService'
import Loading from './loading' import Loading from './loading'
@@ -19,8 +19,9 @@ export async function generateMetadata(
const { id } = await params const { id } = await params
const queryClient = new QueryClient() const queryClient = new QueryClient()
await queryClient.prefetchQuery(detailServiceQueryOptions(id)) await queryClient.prefetchQuery(getProductBySlugQueryOptions(id))
const service = queryClient.getQueryData(detailServiceQueryOptions(id).queryKey) as ServiceDetailResponse const service = queryClient.getQueryData(getProductBySlugQueryOptions(id).queryKey) as ServiceDetailResponse
console.log('service', service);
return { return {
title: 'محصولات داناک' + ' | ' + service.data.danakService.name, title: 'محصولات داناک' + ' | ' + service.data.danakService.name,
@@ -43,7 +44,7 @@ async function SingleServicePage({ id }: { id: string }) {
const queryClient = new QueryClient() const queryClient = new QueryClient()
await Promise.all([ await Promise.all([
queryClient.prefetchQuery(detailServiceQueryOptions(id)), queryClient.prefetchQuery(getProductBySlugQueryOptions(id)),
]) ])
return ( return (
+11
View File
@@ -51,7 +51,18 @@ export const useGetDetailService = (serviceId: string) =>
queryFn: () => api.getDetailService(serviceId), queryFn: () => api.getDetailService(serviceId),
}); });
export const useGetProductBySlug = (slug: string) =>
useQuery({
queryKey: ["product-detail", slug],
queryFn: () => api.getProductBySlug(slug),
});
export const detailServiceQueryOptions = (serviceId: string) => ({ export const detailServiceQueryOptions = (serviceId: string) => ({
queryKey: ["service-detail", serviceId], queryKey: ["service-detail", serviceId],
queryFn: () => api.getDetailService(serviceId), queryFn: () => api.getDetailService(serviceId),
}); });
export const getProductBySlugQueryOptions = (slug: string) => ({
queryKey: ["product-detail", slug],
queryFn: () => api.getProductBySlug(slug),
});
@@ -45,3 +45,10 @@ export const getSliderProducts = async () => {
); );
return data; return data;
}; };
export const getProductBySlug = async (slug: string) => {
const { data } = await axios.get<ServiceDetailResponse>(
`/danak-services/slug/${slug}/public`
);
return data;
};
+1
View File
@@ -59,6 +59,7 @@ export interface DanakService {
link: string; link: string;
icon: string; icon: string;
coverUrl: string; coverUrl: string;
slug: string;
category: { category: {
id: string; id: string;
title: string; title: string;
+1 -1
View File
@@ -11,7 +11,7 @@ type Props = {
const ServiceItem: FC<Props> = (props) => { const ServiceItem: FC<Props> = (props) => {
return ( return (
<Link href={`/products/${props.item.id}`} className='flex-1 bg-white rounded-4xl xl:p-7 p-4 flex flex-col'> <Link href={`/products/${props.item.slug}`} className='flex-1 bg-white rounded-4xl xl:p-7 p-4 flex flex-col'>
<div className='flex justify-between'> <div className='flex justify-between'>
<div className='xl:size-[65px] size-[50px] rounded-2xl overflow-hidden'> <div className='xl:size-[65px] size-[50px] rounded-2xl overflow-hidden'>
<Image src={props.item.icon} className='size-full object-cover' alt={props.item.name} width={65} height={65} /> <Image src={props.item.icon} className='size-full object-cover' alt={props.item.name} width={65} height={65} />
+55
View File
@@ -0,0 +1,55 @@
'use client';
import { useEffect, useState } from 'react';
import Image from 'next/image';
const SplashChatgpt = () => {
const [fadeOut, setFadeOut] = useState(false);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
const timer = setTimeout(() => {
setFadeOut(true);
}, 3000);
return () => clearTimeout(timer);
}, []);
useEffect(() => {
if (fadeOut) {
document.body.style.overflow = 'auto';
} else {
document.body.style.overflow = 'hidden';
}
}, [fadeOut]);
if (!mounted) return null;
return (
<div
className={`fixed inset-0 bg-white top-0 h-screen flex items-center justify-center z-50 transition-all duration-700 ${fadeOut ? 'opacity-0 pointer-events-none' : 'opacity-100'
}`}
>
<div className="absolute inset-0 bg-[url('/images/grid-pattern.svg')] opacity-5 animate-pattern-fade-in" />
{/* Decorative circles */}
<div className="absolute w-[200px] h-[200px] rounded-full bg-primary/5 animate-circle-pulse" />
<div className="absolute w-[180px] h-[180px] rounded-full bg-primary/10 animate-circle-pulse-delayed" />
<div className="relative z-10">
<div className="w-[140px] h-[140px] relative animate-logo-float animate-logo-glow">
<Image
src="/images/logo.svg"
alt="Danak Logo"
fill
className="object-contain"
priority
/>
</div>
</div>
</div>
);
};
export default SplashChatgpt;
+3 -3
View File
@@ -58,17 +58,17 @@ const Footer: FC = () => {
</h6> </h6>
<ul className='xl:text-sm text-xs mt-4 flex flex-col gap-4 text-right'> <ul className='xl:text-sm text-xs mt-4 flex flex-col gap-4 text-right'>
<li> <li>
<Link href='/products/63462bf5-2a34-4466-819f-43d0593e999a'> <Link href='/products/Dmenu'>
دی منو دی منو
</Link> </Link>
</li> </li>
<li> <li>
<Link href='/products/e51afdc3-ea0b-49cf-8f49-2a6f131b024e'> <Link href='/products/Dmail'>
دی میل دی میل
</Link> </Link>
</li> </li>
<li> <li>
<Link href='/products/d9fd428b-72a5-4ecf-a701-f1c0fb332f63'> <Link href='/products/Dpage'>
دی پیج دی پیج
</Link> </Link>
</li> </li>