about page dymanic

This commit is contained in:
Samandar Turgunboyev
2026-02-26 12:24:18 +05:00
parent a2be8ef125
commit 18cfa5cdf9
7 changed files with 215 additions and 118 deletions

View File

@@ -1,3 +1,6 @@
// app/[locale]/about/page.tsx
import { partner_api } from '@/features/about/lib/api';
import { AboutData } from '@/features/about/lib/type';
import { AboutContent } from '@/features/about/ui/AboutContent';
import { AboutHero } from '@/features/about/ui/AboutHero';
import { PartnershipForm } from '@/features/about/ui/AboutPage';
@@ -7,55 +10,72 @@ interface Props {
params: { locale: 'uz' | 'ru' };
}
// Dynamic SEO uchun server-side API chaqiruvi
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const { locale } = params;
const titles = {
uz: 'Biz haqimizda | GASTRO',
ru: 'О нас | Магазин товаров',
};
try {
const res = await partner_api.getAbout();
const data: AboutData = res.data;
const descriptions = {
uz: 'Bizning onlayn dokonimizda sifatli mahsulotlarni toping. Tez yetkazib berish va qulay tolov imkoniyatlari mavjud.',
ru: 'В нашем онлайн-магазине вы найдете качественные товары. Быстрая доставка и удобная оплата.',
};
const banner = data.banner;
const keywords = {
uz: 'mahsulot, onlayn dokon, xarid, yetkazib berish',
ru: 'товары, онлайн-магазин, покупка, доставка',
};
const title =
locale === 'uz'
? banner.title_uz
: locale === 'ru'
? banner.title_ru
: banner.title_en;
return {
title: titles[locale],
description: descriptions[locale],
keywords: keywords[locale],
openGraph: {
title: titles[locale],
description: descriptions[locale],
siteName: 'GASTRO',
images: [
{
url: '/logos/logo.png',
width: 1200,
height: 1200,
alt: titles[locale],
},
],
locale: locale === 'uz' ? 'uz_UZ' : 'ru_RU',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: titles[locale],
description: descriptions[locale],
images: ['/logos/logo.png'],
},
};
const description =
locale === 'uz'
? banner.description_uz
: locale === 'ru'
? banner.description_ru
: banner.description_en;
return {
title: title || 'Gastro Market',
description:
description || 'Gastro Market gastronomiya va kulinariya olami',
openGraph: {
title: title || 'Gastro Market',
description:
description || 'Gastro Market gastronomiya va kulinariya olami',
siteName: 'GASTRO',
images: [
{
url: banner.image ? '/' + banner.image : '/logos/logo.png',
width: 1200,
height: 1200,
alt: title || 'Gastro Market',
},
],
locale: locale === 'uz' ? 'uz_UZ' : 'ru_RU',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: title || 'Gastro Market',
description:
description || 'Gastro Market gastronomiya va kulinariya olami',
images: [banner.image ? '/' + banner.image : '/logos/logo.png'],
},
};
} catch (err) {
console.error('About metadata error:', err);
return {
title: 'Gastro Market',
description: 'Gastro Market gastronomiya va kulinariya olami',
};
}
}
const page = () => {
// Page component
const page = async () => {
return (
<div className="custom-container">
{/* Hero va Content ichida React Query ishlatilgan loading + dynamic data */}
<AboutHero />
<AboutContent />
<PartnershipForm />

View File

@@ -1,9 +1,16 @@
import httpClient from '@/shared/config/api/httpClient';
import { API_URLS } from '@/shared/config/api/URLs';
import { AxiosResponse } from 'axios';
import { AboutData } from './type';
export const partner_api = {
async send(body: FormData) {
const res = httpClient.post(API_URLS.Partners, body);
return res;
},
async getAbout(): Promise<AxiosResponse<AboutData>> {
const res = httpClient.get(API_URLS.About);
return res;
},
};

View File

@@ -4,3 +4,35 @@ export interface PartnerSendBody {
phone_number: string;
file: File;
}
export interface AboutData {
banner: {
id: number;
created_at: string;
updated_at: string;
title_uz: string;
title_ru: string;
title_en: string;
description_uz: string;
description_ru: string;
description_en: string;
image: string;
};
images: {
id: number;
created_at: string;
updated_at: string;
image: string;
}[];
text: {
id: number;
created_at: string;
updated_at: string;
title_uz: string;
title_ru: string;
title_en: string;
description_uz: string;
description_ru: string;
description_en: string;
}[];
}

View File

@@ -1,85 +1,80 @@
import { Card } from '@/shared/ui/card';
'use client';
import { BASE_URL } from '@/shared/config/api/URLs';
import { Skeleton } from '@/shared/ui/skeleton';
import { useQuery } from '@tanstack/react-query';
import { useTranslations } from 'next-intl';
import Image from 'next/image';
import { useParams } from 'next/navigation';
import { partner_api } from '../lib/api';
export function AboutContent() {
const t = useTranslations();
const features = [
{
number: '1',
title: 'Sifatli Kontent',
description:
"Jahon oshpazlik san'ati va zamonaviy gastronomiya tendentsiyalari haqida chuqur maqolalar va tahlillar",
},
{
number: '2',
title: 'Professional Jamoa',
description:
'Tajribali kulinariya mutaxassislari va oshpazlar tomonidan tayyorlangan kontent',
},
{
number: '3',
title: 'Yangiliklar',
description:
"Gastronomiya sohasidagi so'nggi yangiliklar va eng yangi trendlar haqida xabarlar",
},
];
const { locale } = useParams();
const images = [
{
url: '/professional-chef-cooking-gourmet-food.jpg',
alt: 'Professional Oshpaz',
const { data: about, isLoading: loadingText } = useQuery({
queryKey: ['aboutText'],
queryFn: async () => {
return partner_api.getAbout();
},
{
url: '/fine-dining-restaurant-plating.jpg',
alt: 'Fine Dining',
select(data) {
return data.data;
},
{
url: '/fresh-ingredients-culinary-market.jpg',
alt: 'Fresh Ingredients',
},
];
});
const aboutText = about?.text;
const images = about?.images;
// Loading holati
if (loadingText) {
return (
<section className="py-20 px-4">
<div className="max-w-7xl mx-auto">
{/* Text Skeleton */}
<div className="mb-20 text-center space-y-6">
<Skeleton className="h-12 md:h-16 w-3/4 mx-auto rounded-lg" />
<Skeleton className="h-6 md:h-8 w-5/6 mx-auto rounded-lg" />
<Skeleton className="h-6 md:h-8 w-5/6 mx-auto rounded-lg" />
</div>
{/* Image Gallery Skeleton */}
<div className="mb-20">
<Skeleton className="h-8 w-1/3 mx-auto rounded-lg mb-12" />
<div className="grid md:grid-cols-3 gap-6">
{[...Array(3)].map((_, idx) => (
<Skeleton
key={idx}
className="aspect-[4/3] w-full rounded-lg"
/>
))}
</div>
</div>
</div>
</section>
);
}
return (
<section className="py-20 px-4">
<div className="max-w-7xl mx-auto">
{/* Mission Section */}
<div className="mb-20">
<h2 className="text-4xl md:text-5xl font-bold text-center mb-16 text-balance">
{t('Bizning maqsadimiz')}
</h2>
<div className="grid md:grid-cols-3 gap-8">
{features.map((feature) => (
<Card
key={feature.number}
className="p-8 hover:shadow-lg transition-shadow"
>
<div className="text-6xl font-bold text-primary mb-4">
{feature.number}
</div>
<h3 className="text-2xl font-semibold mb-4">
{t(feature.title)}
</h3>
<p className="text-muted-foreground leading-relaxed">
{t(feature.description)}
</p>
</Card>
))}
</div>
</div>
{/* About Text */}
<div className="mb-20 max-w-4xl mx-auto text-center">
<div className="mb-20 text-center">
<h2 className="text-3xl md:text-4xl font-bold mb-8 text-balance">
{t('Innovatsiya, sifat va professionallik')}
{locale === 'uz'
? aboutText?.[0]?.title_uz || 'Gastro Market'
: locale === 'ru'
? aboutText?.[0]?.title_ru || 'Gastro Market'
: aboutText?.[0]?.title_en || 'Gastro Market'}
</h2>
<p className="text-lg text-muted-foreground leading-relaxed mb-6">
{t(
`Gastro Market bu gastronomiya dunyosidagi eng so'nggi yangiliklarni`,
)}
</p>
<p className="text-lg text-muted-foreground leading-relaxed">
{t(`Bizning jamoamiz tajribali kulinariya mutaxassislari`)}
{locale === 'uz'
? aboutText?.[0]?.description_uz ||
"Gastronomiya va kulinariya san'ati haqidagi yetakchi onlayn magazin"
: locale === 'ru'
? aboutText?.[0]?.description_ru ||
'Ведущий интернет-магазин по гастрономии и кулинарному искусству'
: aboutText?.[0]?.description_en ||
"Gastronomiya va kulinariya san'ati haqidagi yetakchi onlayn magazin"}
</p>
</div>
@@ -89,7 +84,7 @@ export function AboutContent() {
{t('Bizning dunyo')}
</h3>
<div className="grid md:grid-cols-3 gap-6">
{images.map((image, idx) => (
{images?.map((image, idx) => (
<div
key={idx}
className="relative aspect-[4/3] overflow-hidden rounded-lg group"
@@ -97,8 +92,8 @@ export function AboutContent() {
<Image
width={500}
height={500}
src={image.url || '/placeholder.svg'}
alt={image.alt}
src={BASE_URL + image.image || '/placeholder.svg'}
alt={image.id.toString()}
unoptimized
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
/>

View File

@@ -1,8 +1,40 @@
import { useTranslations } from 'next-intl';
'use client';
import { BASE_URL } from '@/shared/config/api/URLs';
import { Skeleton } from '@/shared/ui/skeleton';
import { useQuery } from '@tanstack/react-query';
import Image from 'next/image';
import { useParams } from 'next/navigation';
import { partner_api } from '../lib/api';
export function AboutHero() {
const t = useTranslations();
const { locale } = useParams();
const { data: banner, isLoading } = useQuery({
queryKey: ['about'],
queryFn: async () => {
return partner_api.getAbout();
},
select(data) {
return data.data.banner;
},
});
if (isLoading) {
return (
<section className="relative h-[60vh] min-h-[500px] flex items-center rounded-lg justify-center overflow-hidden bg-gray-200">
{/* Background skeleton */}
<Skeleton className="absolute inset-0 w-full h-full" />
{/* Content skeleton */}
<div className="relative z-10 text-center px-4 max-w-4xl mx-auto space-y-6">
<Skeleton className="h-16 md:h-24 w-3/4 mx-auto rounded-lg" />
<Skeleton className="h-6 md:h-10 w-5/6 mx-auto rounded-lg" />
<Skeleton className="h-6 md:h-10 w-4/6 mx-auto rounded-lg" />
</div>
</section>
);
}
return (
<section className="relative h-[60vh] min-h-[500px] flex items-center justify-center overflow-hidden">
<div className="absolute inset-0 z-0">
@@ -10,19 +42,31 @@ export function AboutHero() {
width={500}
height={500}
unoptimized
src="/gourmet-food-culinary-magazine-hero-image.jpg"
src={
BASE_URL + banner?.image ||
'/gourmet-food-culinary-magazine-hero-image.jpg'
}
alt="Gastro Market"
className="w-full h-full object-cover brightness-50"
/>
</div>
<div className="relative z-10 text-center px-4 max-w-4xl mx-auto">
<h1 className="text-5xl md:text-7xl font-bold text-white mb-6 text-balance">
Gastro Market
{locale === 'uz'
? banner?.title_uz || 'Gastro Market'
: locale === 'ru'
? banner?.title_ru || 'Gastro Market'
: banner?.title_en || 'Gastro Market'}
</h1>
<p className="text-xl md:text-2xl text-white/90 font-light leading-relaxed text-balance">
{t(
"Gastronomiya va kulinariya san'ati haqidagi yetakchi onlayn magazin",
)}
{locale === 'uz'
? banner?.description_uz ||
"Gastronomiya va kulinariya san'ati haqidagi yetakchi onlayn magazin"
: locale === 'ru'
? banner?.description_ru ||
'Ведущий интернет-магазин по гастрономии и кулинарному искусству'
: banner?.description_en ||
"Gastronomiya va kulinariya san'ati haqidagi yetakchi onlayn magazin"}
</p>
</div>
</section>

View File

@@ -27,8 +27,6 @@ const SubCategory = () => {
router.push(`/category/${categoryId}/${subCategory.id}`);
};
console.log(categorys);
return (
<div className="custom-container">
<>

View File

@@ -24,4 +24,5 @@ export const API_URLS = {
OrderList: `${API_V}orders/order/list/`,
Refresh_Token: `${API_V}accounts/refresh/token/`,
Get_Me: `${API_V}accounts/me/`,
About: `${API_V}shared/aboutus/`,
};