Compare commits

..

6 Commits

Author SHA1 Message Date
BunyodL
6dc26611c3 fix: fix text display 2025-04-30 18:13:25 +05:00
BunyodL
ec2cd2cf56 Merge branch 'dev' into render-main-page-data 2025-04-30 18:09:12 +05:00
Umar Adilov
83451cf358 integrated text control provider 2025-04-30 17:00:52 +05:00
Umar Adilov
e0592eb581 added text endpoint 2025-04-30 02:13:54 +05:00
khadiatullo
0f5be58093 update: update ru.json 2025-04-29 20:40:00 +03:00
khadiatullo
bd08e69065 update: update ru.json 2025-04-28 22:58:39 +03:00
52 changed files with 915 additions and 1338 deletions

View File

@ -33,6 +33,7 @@
"lodash": "^4.17.21",
"lucide-react": "^0.501.0",
"next": "15.3.1",
"next-redux-wrapper": "^8.1.0",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-day-picker": "8.10.1",

16
pnpm-lock.yaml generated
View File

@ -77,6 +77,9 @@ importers:
next:
specifier: 15.3.1
version: 15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next-redux-wrapper:
specifier: ^8.1.0
version: 8.1.0(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-redux@9.2.0(@types/react@19.1.2)(react@19.1.0)(redux@5.0.1))(react@19.1.0)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -2129,6 +2132,13 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
next-redux-wrapper@8.1.0:
resolution: {integrity: sha512-2hIau0hcI6uQszOtrvAFqgc0NkZegKYhBB7ZAKiG3jk7zfuQb4E7OV9jfxViqqojh3SEHdnFfPkN9KErttUKuw==}
peerDependencies:
next: '>=9'
react: '*'
react-redux: '*'
next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
@ -4739,6 +4749,12 @@ snapshots:
natural-compare@1.4.0: {}
next-redux-wrapper@8.1.0(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-redux@9.2.0(@types/react@19.1.2)(react@19.1.0)(redux@5.0.1))(react@19.1.0):
dependencies:
next: 15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react: 19.1.0
react-redux: 9.2.0(@types/react@19.1.2)(react@19.1.0)(redux@5.0.1)
next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0

View File

@ -1,308 +1,5 @@
'use client';
import { CorporateDashboard } from "@/pages-templates/(dashboard)/corporate-dashboard";
import { format, subMonths } from 'date-fns';
import { ru } from 'date-fns/locale';
import { Building2, CalendarIcon, LogOut, Wallet } from 'lucide-react';
import { useState } from 'react';
import { Button } from '@/shared/shadcn-ui/button';
import { Calendar } from '@/shared/shadcn-ui/calendar';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/shared/shadcn-ui/card';
import { Label } from '@/shared/shadcn-ui/label';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/shared/shadcn-ui/popover';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/shared/shadcn-ui/table';
// import { CardsList } from '@/widgets/cards-list';
// Sample company data
const companyData = {
companyName: 'ООО «ТаджикТранс»',
numberOfCards: 12,
fund: 25000,
overdraft: 5000,
totalFund: 30000,
registrationDate: '10.03.2019',
};
// Sample transaction data
const generateTransactions = () => {
const stations = [
'АЗС Душанбе-Центр',
'АЗС Душанбе-Запад',
'АЗС Душанбе-Восток',
'АЗС Худжанд',
'АЗС Куляб',
];
const products = [
{ name: 'ДТ', price: 8.5 },
{ name: 'АИ-92', price: 9.2 },
{ name: 'АИ-95', price: 10.5 },
{ name: 'Z-100 Power', price: 11.8 },
{ name: 'Пропан', price: 6.3 },
];
const transactions = [];
// Generate 50 random transactions over the last 6 months
for (let i = 0; i < 50; i++) {
const date = subMonths(new Date(), Math.random() * 6);
const station = stations[Math.floor(Math.random() * stations.length)];
const product = products[Math.floor(Math.random() * products.length)];
const quantity = Math.floor(Math.random() * 40) + 10; // 10-50 liters
const cost = product.price;
const total = quantity * cost;
transactions.push({
id: i + 1,
date,
station,
product: product.name,
quantity,
cost,
total,
});
}
// Sort by date (newest first)
return transactions.sort((a, b) => b.date.getTime() - a.date.getTime());
};
const transactions = generateTransactions();
export default function CorporateDashboard() {
const [startDate, setStartDate] = useState<Date | undefined>(
subMonths(new Date(), 1),
);
const [endDate, setEndDate] = useState<Date | undefined>(new Date());
const [filteredTransactions, setFilteredTransactions] =
useState(transactions);
// Filter transactions by date range
const filterTransactions = () => {
if (!startDate || !endDate) return;
const filtered = transactions.filter((transaction) => {
const transactionDate = new Date(transaction.date);
return transactionDate >= startDate && transactionDate <= endDate;
});
setFilteredTransactions(filtered);
};
return (
<div className='flex min-h-screen flex-col'>
<main className='flex-1 py-10'>
<div className='container mx-auto max-w-6xl'>
<div className='mb-8 flex items-center justify-between'>
<h1 className='text-3xl font-bold'>Корпоративный кабинет</h1>
<Button variant='outline' className='gap-2'>
<LogOut className='h-4 w-4' />
Выйти
</Button>
</div>
<div className='mb-10 grid gap-3 md:grid-cols-3 md:gap-6'>
{/* Company Card */}
<Card className='md:col-span-2'>
<CardHeader className='pb-2'>
<CardTitle className='flex items-center gap-2'>
<Building2 className='h-5 w-5 text-red-600' />
Информация о компании
</CardTitle>
</CardHeader>
<CardContent>
<div className='grid gap-6 md:grid-cols-2'>
<div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>Название компании</p>
<p className='font-medium'>{companyData.companyName}</p>
</div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>Количество карт</p>
<p className='font-medium'>{companyData.numberOfCards}</p>
</div>
<div className='space-y-1'>
<p className='text-sm text-gray-500'>Дата регистрации</p>
<p className='font-medium'>
{companyData.registrationDate}
</p>
</div>
</div>
<div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>Фонд</p>
<p className='font-medium'>
{companyData.fund.toLocaleString()} сомони
</p>
</div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>Овердрафт</p>
<p className='font-medium'>
{companyData.overdraft.toLocaleString()} сомони
</p>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Fund Card */}
<Card className='bg-gradient-to-br from-red-600 to-red-800 text-white'>
<CardHeader>
<CardTitle className='flex items-center gap-2'>
<Wallet className='h-5 w-5' />
Общий фонд
</CardTitle>
<CardDescription className='text-white/80'>
Доступные средства
</CardDescription>
</CardHeader>
<CardContent>
<div className='text-center'>
<p className='mb-2 text-4xl font-bold'>
{companyData.totalFund.toLocaleString()}
</p>
<p className='text-white/80'>сомони</p>
</div>
</CardContent>
</Card>
</div>
{/* <CardsList totalCards={companyData.numberOfCards} /> */}
{/* Transactions */}
<div className='space-y-6'>
<div className='flex flex-col items-start justify-between gap-4 md:flex-row md:items-center'>
<h2 className='text-2xl font-bold'>История операций</h2>
<div className='flex w-full flex-col gap-4 md:w-auto md:flex-row'>
<div className='grid grid-cols-2 gap-2'>
<div className='flex items-center gap-2'>
<Label htmlFor='start-date'>От</Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant='outline'
className='w-full justify-start text-left font-normal'
>
<CalendarIcon className='mr-2 h-4 w-4' />
{startDate
? format(startDate, 'PP', { locale: ru })
: 'Выберите дату'}
</Button>
</PopoverTrigger>
<PopoverContent className='w-auto p-0'>
<Calendar
mode='single'
selected={startDate}
onSelect={setStartDate}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
<div className='flex items-center gap-2'>
<Label htmlFor='end-date'>До</Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant='outline'
className='w-full justify-start text-left font-normal'
>
<CalendarIcon className='mr-2 h-4 w-4' />
{endDate
? format(endDate, 'PP', { locale: ru })
: 'Выберите дату'}
</Button>
</PopoverTrigger>
<PopoverContent className='w-auto p-0'>
<Calendar
mode='single'
selected={endDate}
onSelect={setEndDate}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
</div>
<Button
className='mt-auto bg-red-600 hover:bg-red-700'
onClick={filterTransactions}
>
Применить
</Button>
</div>
</div>
<div className='rounded-md border'>
<Table>
<TableHeader>
<TableRow>
<TableHead>Дата</TableHead>
<TableHead>Станция</TableHead>
<TableHead>Продукт</TableHead>
<TableHead className='text-right'>Кол-во (л)</TableHead>
<TableHead className='text-right'>Стоимость</TableHead>
<TableHead className='text-right'>Сумма</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredTransactions.length > 0 ? (
filteredTransactions.map((transaction) => (
<TableRow key={transaction.id}>
<TableCell>
{format(transaction.date, 'dd.MM.yyyy')}
</TableCell>
<TableCell>{transaction.station}</TableCell>
<TableCell>{transaction.product}</TableCell>
<TableCell className='text-right'>
{transaction.quantity}
</TableCell>
<TableCell className='text-right'>
{transaction.cost.toFixed(2)} сомони
</TableCell>
<TableCell className='text-right font-medium'>
{transaction.total.toFixed(2)} сомони
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={6}
className='py-6 text-center text-gray-500'
>
Нет операций за выбранный период
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
</div>
</main>
</div>
);
export default function Corporate() {
return <CorporateDashboard/>
}

View File

@ -1,110 +1,5 @@
import { ArrowUpRight, Clock, CreditCard, LogOut, User } from 'lucide-react';
import { CustomerDashboard } from "@/pages-templates/(dashboard)/customer-dashboard";
import { Button } from '@/shared/shadcn-ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/shared/shadcn-ui/card';
import { TransactionsTable } from '@/widgets/transactions-table';
// Sample customer data
const customerData = {
firstName: 'Алишер',
lastName: 'Рахмонов',
passportNumber: 'A12345678',
bonusPoints: 1250,
cardNumber: '5678-9012-3456-7890',
expiryDate: '12/2025',
registrationDate: '15.06.2020',
};
export default function CustomerDashboard() {
return (
<div className='flex min-h-screen flex-col'>
<main className='flex-1 py-10'>
<div className='container mx-auto max-w-6xl'>
<div className='mb-8 flex items-center justify-between'>
<h1 className='text-3xl font-bold'>Личный кабинет</h1>
<Button variant='outline' className='gap-2'>
<LogOut className='h-4 w-4' />
Выйти
</Button>
</div>
<div className='mb-10 grid gap-3 md:grid-cols-3 md:gap-6'>
{/* Bonus Card */}
<Card className='bg-gradient-to-br from-red-600 to-red-800 text-white'>
<CardHeader>
<CardTitle className='flex items-center gap-2'>
<CreditCard className='h-5 w-5' />
Бонусная карта
</CardTitle>
<CardDescription className='text-white/80'>
Ваши накопленные бонусы
</CardDescription>
</CardHeader>
<CardContent>
<div className='text-center'>
<p className='mb-2 text-4xl font-bold'>
{customerData.bonusPoints}
</p>
<p className='text-white/80'>бонусных баллов</p>
</div>
<div className='mt-6 flex items-center justify-between'>
<div className='flex items-center gap-1 text-sm text-white/80'>
<Clock className='h-4 w-4' />
<span>Действует до: 31.12.2023</span>
</div>
<ArrowUpRight className='h-5 w-5 text-white/60' />
</div>
</CardContent>
</Card>
{/* Customer Card */}
<Card className='md:col-span-2'>
<CardHeader className='pb-2'>
<CardTitle className='flex items-center gap-2'>
<User className='h-5 w-5 text-red-600' />
Информация о клиенте
</CardTitle>
</CardHeader>
<CardContent>
<div className='grid gap-6 md:grid-cols-2'>
<div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>ФИО</p>
<p className='font-medium'>
{customerData.firstName} {customerData.lastName}
</p>
</div>
<div className='space-y-1'>
<p className='text-sm text-gray-500'>Дата регистрации</p>
<p className='font-medium'>
{customerData.registrationDate}
</p>
</div>
</div>
<div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>Номер карты</p>
<p className='font-medium'>{customerData.cardNumber}</p>
</div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>Срок действия</p>
<p className='font-medium'>{customerData.expiryDate}</p>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
<TransactionsTable />
</div>
</main>
</div>
);
export default function Customer() {
return <CustomerDashboard/>
}

View File

@ -48,3 +48,8 @@ export type Station = Root<{
_region: Select[];
_foto: Image[];
}>;
export type TextResponse = Root<{
_name: string;
_znachenie: string | null;
}>;

View File

@ -1,6 +1,13 @@
import { isEmpty } from 'lodash';
import { Discount, Image, Job, Partner, Station } from '../@types';
import {
Discount,
Image,
Job,
Partner,
Station,
TextResponse,
} from '../@types';
export const presentImage = (images: Image[]) =>
isEmpty(images) ? null : `${process.env.TAYLOR_MEDIA_URL}/${images[0].url}`;
@ -50,3 +57,9 @@ export const presentStations = (stations: Station) =>
region: !isEmpty(station._region) ? station._region[0].name : null,
image: presentImage(station._foto),
}));
export const presentTexts = (texts: TextResponse) =>
texts.records.map((item) => ({
key: item._name,
value: item._znachenie,
}));

View File

@ -67,3 +67,12 @@ export const discountsRequest = {
},
},
};
export const textsRequest = {
_kontentSajta: {
records: {
_name: true,
_znachenie: true,
},
},
};

14
src/app/api/text/route.ts Normal file
View File

@ -0,0 +1,14 @@
import { presentTexts } from '@/app/api-utlities/presenters';
import { textsRequest } from '@/app/api-utlities/requests/common';
import { requestTaylor } from '@/app/api-utlities/utilities/taylor.client';
export async function GET(request: Request) {
const response = await requestTaylor(textsRequest);
return new Response(
JSON.stringify(presentTexts(response.data._kontentSajta)),
{
headers: { 'Content-Type': 'application/json' },
},
);
}

View File

@ -1,304 +1,5 @@
import {
Calendar,
CheckCircle,
Heart,
Landmark,
MapPin,
Users,
} from 'lucide-react';
import Image from 'next/image';
import { CharityPage } from "@/pages-templates/charity"
import { Button } from '@/shared/shadcn-ui/button';
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from '@/shared/shadcn-ui/card';
import { CtaSection } from '@/widgets/cta-section';
export const metadata = {
title: 'Благотворительность | GasNetwork - Сеть заправок в Таджикистане',
description:
'Благотворительные проекты и инициативы GasNetwork. Мы помогаем обществу и заботимся о будущем.',
};
export default function CharityPage() {
return (
<div className='flex min-h-screen flex-col'>
<main className='flex-1'>
{/* Hero Section */}
<section className='relative'>
<div className='relative h-[400px] w-full overflow-hidden'>
<Image
src='/placeholder.svg?height=500&width=1920&text=Благотворительный+фонд+GasNetwork'
alt='Благотворительный фонд GasNetwork'
width={1920}
height={500}
className='object-cover'
priority
/>
<div className='absolute inset-0 flex items-center bg-gradient-to-r from-black/70 to-black/30'>
<div className='container mx-auto'>
<div className='max-w-2xl space-y-6 text-white'>
<div className='inline-flex items-center justify-center rounded-full bg-red-600/20 p-2'>
<Heart className='size-6 text-red-500' />
</div>
<h1 className='text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl'>
Благотворительный фонд GasNetwork
</h1>
<p className='text-xl text-gray-200'>
Мы верим, что бизнес должен быть социально ответственным.
Наш фонд поддерживает образование, здравоохранение и
экологические инициативы в Таджикистане.
</p>
</div>
</div>
</div>
</div>
</section>
{/* Mission Section */}
<section className='py-16'>
<div className='container mx-auto'>
<div className='grid items-center gap-12 md:grid-cols-2'>
<div>
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
<Heart className='h-6 w-6 text-red-600' />
</div>
<h2 className='mb-6 text-3xl font-bold tracking-tight sm:text-4xl'>
Наша миссия
</h2>
<p className='mb-6 text-gray-600'>
Благотворительный фонд GasNetwork был создан в 2020 году с
целью поддержки социально значимых проектов в Таджикистане. Мы
стремимся внести свой вклад в развитие общества и помочь тем,
кто в этом нуждается.
</p>
<p className='mb-6 text-gray-600'>
Наша миссия создавать возможности для улучшения жизни людей
через образование, здравоохранение, экологические инициативы и
поддержку уязвимых групп населения.
</p>
<div className='space-y-4'>
<div className='flex items-start'>
<CheckCircle className='mr-3 h-6 w-6 flex-shrink-0 text-red-600' />
<div>
<h3 className='text-lg font-medium'>Прозрачность</h3>
<p className='text-gray-600'>
Мы публикуем ежегодные отчеты о всех наших проектах и
расходах, обеспечивая полную прозрачность нашей
деятельности.
</p>
</div>
</div>
<div className='flex items-start'>
<CheckCircle className='mr-3 h-6 w-6 flex-shrink-0 text-red-600' />
<div>
<h3 className='text-lg font-medium'>Эффективность</h3>
<p className='text-gray-600'>
Мы тщательно выбираем проекты, которые могут принести
максимальную пользу обществу и имеют долгосрочное
влияние.
</p>
</div>
</div>
<div className='flex items-start'>
<CheckCircle className='mr-3 h-6 w-6 flex-shrink-0 text-red-600' />
<div>
<h3 className='text-lg font-medium'>Сотрудничество</h3>
<p className='text-gray-600'>
Мы сотрудничаем с местными и международными
организациями для достижения наибольшего эффекта от
наших инициатив.
</p>
</div>
</div>
</div>
</div>
<div className='relative h-[500px] overflow-hidden rounded-xl shadow-xl'>
<Image
src='/placeholder.svg?height=500&width=600&text=Наша+миссия'
alt='Наша миссия'
fill
className='object-cover'
/>
</div>
</div>
</div>
</section>
{/* Key Figures */}
<section className='bg-red-600 py-16 text-white'>
<div className='container mx-auto'>
<div className='mb-12 text-center'>
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
Наш вклад в цифрах
</h2>
<p className='mx-auto max-w-2xl text-white/80'>
За время существования нашего фонда мы достигли значительных
результатов
</p>
</div>
<div className='grid grid-cols-1 gap-8 text-center md:grid-cols-3'>
<div className='space-y-2'>
<h3 className='text-4xl font-bold'>15+</h3>
<p className='text-white/80'>Реализованных проектов</p>
</div>
<div className='space-y-2'>
<h3 className='text-4xl font-bold'>1.2M</h3>
<p className='text-white/80'>Сомони пожертвований</p>
</div>
<div className='space-y-2'>
<h3 className='text-4xl font-bold'>5000+</h3>
<p className='text-white/80'>Людей получили помощь</p>
</div>
</div>
</div>
</section>
{/* Upcoming Events */}
<section className='bg-gray-50 py-16'>
<div className='container mx-auto'>
<div className='mb-12 text-center'>
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
<Calendar className='h-6 w-6 text-red-600' />
</div>
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
Предстоящие мероприятия
</h2>
<p className='mx-auto max-w-2xl text-gray-600'>
Присоединяйтесь к нашим благотворительным мероприятиям и внесите
свой вклад в общее дело
</p>
</div>
<div className='grid gap-6 md:grid-cols-3'>
{[
{
title: 'Благотворительный марафон',
description:
'Ежегодный благотворительный марафон в поддержку детей с особыми потребностями.',
date: '15 июня 2023',
location: 'Парк Рудаки, Душанбе',
image: '/placeholder.svg?height=200&width=300&text=Марафон',
},
{
title: 'Экологическая акция',
description:
'Очистка берегов реки Варзоб от мусора и посадка деревьев.',
date: '22 июля 2023',
location: 'Река Варзоб, Душанбе',
image:
'/placeholder.svg?height=200&width=300&text=Экологическая+акция',
},
{
title: 'Сбор школьных принадлежностей',
description:
'Сбор школьных принадлежностей для детей из малообеспеченных семей к новому учебному году.',
date: '1-20 августа 2023',
location: 'Все заправки GasNetwork',
image:
'/placeholder.svg?height=200&width=300&text=Школьные+принадлежности',
},
].map((event, index) => (
<Card key={index} className='overflow-hidden'>
<div className='relative h-48 w-full'>
<Image
src={event.image || '/placeholder.svg'}
alt={event.title}
fill
className='object-cover'
/>
</div>
<CardHeader>
<CardTitle>{event.title}</CardTitle>
</CardHeader>
<CardContent className='space-y-4'>
<p className='text-gray-600'>{event.description}</p>
<div className='flex items-center text-sm text-gray-500'>
<Calendar className='mr-2 h-4 w-4' />
{event.date}
</div>
<div className='flex items-center text-sm text-gray-500'>
<MapPin className='mr-2 h-4 w-4' />
{event.location}
</div>
</CardContent>
<CardFooter>
<Button className='w-full bg-red-600 hover:bg-red-700'>
Принять участие
</Button>
</CardFooter>
</Card>
))}
</div>
</div>
</section>
{/* How to Help */}
<section className='py-16'>
<div className='container mx-auto'>
<div className='mb-12 text-center'>
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
<Users className='h-6 w-6 text-red-600' />
</div>
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
Как вы можете помочь
</h2>
<p className='mx-auto max-w-2xl text-gray-600'>
Есть много способов внести свой вклад в наши благотворительные
инициативы
</p>
</div>
<div className='grid gap-3 md:grid-cols-2 md:gap-6 lg:grid-cols-4'>
{[
{
title: 'Сделать пожертвование',
description:
'Ваше пожертвование поможет нам реализовать больше проектов и помочь большему количеству людей.',
icon: <Landmark className='h-10 w-10 text-red-600' />,
},
{
title: 'Стать волонтером',
description:
'Присоединяйтесь к нашей команде волонтеров и помогайте нам в реализации благотворительных проектов.',
icon: <Users className='h-10 w-10 text-red-600' />,
},
{
title: 'Участвовать в мероприятиях',
description:
'Принимайте участие в наших благотворительных мероприятиях и акциях.',
icon: <Calendar className='h-10 w-10 text-red-600' />,
},
{
title: 'Распространять информацию',
description:
'Расскажите о нашем фонде и его деятельности своим друзьям и знакомым.',
icon: <Heart className='h-10 w-10 text-red-600' />,
},
].map((item, index) => (
<Card key={index} className='text-center'>
<CardHeader>
<div className='mb-4 flex justify-center'>{item.icon}</div>
<CardTitle className='break-words hyphens-auto'>
{item.title}
</CardTitle>
</CardHeader>
<CardContent>
<p className='text-gray-600'>{item.description}</p>
</CardContent>
</Card>
))}
</div>
</div>
</section>
<CtaSection />
</main>
</div>
);
export default function Charity() {
return <CharityPage />
}

View File

@ -2,6 +2,7 @@ import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { Providers } from '@/shared/providers/providers';
import { TextItem } from '@/shared/types/text.types';
import { Footer } from '@/widgets/footer';
import { Header } from '@/widgets/header/ui';
@ -16,20 +17,25 @@ export const metadata: Metadata = {
'Качественное топливо, удобное расположение и отличный сервис для наших клиентов',
};
export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const response = (await fetch(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/text`,
{ method: 'GET' },
).then((res) => res.json())) as TextItem[];
return (
<html
lang='en'
lang='ru'
suppressHydrationWarning
className='scroll-smooth'
style={{ scrollBehavior: 'smooth' }}
>
<body className={`${inter.className} antialiased`}>
<Providers>
<Providers textItems={response as TextItem[]}>
<Header />
{children}
<Footer />

View File

@ -8,7 +8,7 @@ import { toast } from 'sonner';
import { useLazyLoginQuery } from '@/entities/auth';
import { SubmitButton } from '@/shared/components/submit-button';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import {
Form,
FormControl,
@ -26,7 +26,7 @@ interface LoginFormProps {
}
export const LoginForm = ({ type }: LoginFormProps) => {
const { t } = useLanguage();
const { t } = useTextController();
const router = useRouter();
const [login, { isLoading: isLoginLoading }] = useLazyLoginQuery();

View File

@ -12,7 +12,7 @@ import { useEffect, useRef, useState } from 'react';
import { Stations } from '@/app/api-utlities/@types/main';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Badge } from '@/shared/shadcn-ui/badge';
import { Button } from '@/shared/shadcn-ui/button';
import {
@ -177,7 +177,7 @@ interface GasStationMapProps {
export default function GasStationMap({
stations: _stations,
}: GasStationMapProps) {
const { t } = useLanguage();
const { t } = useTextController();
const mapRef = useRef<HTMLDivElement>(null);
const [activeFilters, setActiveFilters] = useState<string[]>([]);
const [activeCities, setActiveCities] = useState<string[]>([]);

View File

@ -0,0 +1,201 @@
'use client';
import { subMonths } from 'date-fns';
import { Building2, LogOut, Wallet } from 'lucide-react';
import { useState } from 'react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/shared/shadcn-ui/card';
import { TransactionsTable } from '@/widgets/transactions-table';
// import { CardsList } from '@/widgets/cards-list';
// Sample company data
const companyData = {
companyName: 'ООО «ТаджикТранс»',
numberOfCards: 12,
fund: 25000,
overdraft: 5000,
totalFund: 30000,
registrationDate: '10.03.2019',
};
// Sample transaction data
const generateTransactions = () => {
const stations = [
'АЗС Душанбе-Центр',
'АЗС Душанбе-Запад',
'АЗС Душанбе-Восток',
'АЗС Худжанд',
'АЗС Куляб',
];
const products = [
{ name: 'ДТ', price: 8.5 },
{ name: 'АИ-92', price: 9.2 },
{ name: 'АИ-95', price: 10.5 },
{ name: 'Z-100 Power', price: 11.8 },
{ name: 'Пропан', price: 6.3 },
];
const transactions = [];
// Generate 50 random transactions over the last 6 months
for (let i = 0; i < 50; i++) {
const date = subMonths(new Date(), Math.random() * 6);
const station = stations[Math.floor(Math.random() * stations.length)];
const product = products[Math.floor(Math.random() * products.length)];
const quantity = Math.floor(Math.random() * 40) + 10; // 10-50 liters
const cost = product.price;
const total = quantity * cost;
transactions.push({
id: i + 1,
date,
station,
product: product.name,
quantity,
cost,
total,
});
}
// Sort by date (newest first)
return transactions.sort((a, b) => b.date.getTime() - a.date.getTime());
};
const transactions = generateTransactions();
export function CorporateDashboard() {
const [startDate, setStartDate] = useState<Date | undefined>(
subMonths(new Date(), 1),
);
const [endDate, setEndDate] = useState<Date | undefined>(new Date());
const [filteredTransactions, setFilteredTransactions] =
useState(transactions);
// Filter transactions by date range
const filterTransactions = () => {
if (!startDate || !endDate) return;
const filtered = transactions.filter((transaction) => {
const transactionDate = new Date(transaction.date);
return transactionDate >= startDate && transactionDate <= endDate;
});
setFilteredTransactions(filtered);
};
const { t } = useTextController();
return (
<div className='flex min-h-screen flex-col'>
<main className='flex-1 py-10'>
<div className='container mx-auto max-w-6xl'>
<div className='mb-8 flex items-center justify-between'>
<h1 className='text-3xl font-bold'>{t('corporate.pageTitle')}</h1>
<Button variant='outline' className='gap-2'>
<LogOut className='h-4 w-4' />
{t('corporate.logoutButton')}
</Button>
</div>
<div className='mb-10 grid gap-3 md:grid-cols-3 md:gap-6'>
{/* Company Card */}
<Card className='md:col-span-2'>
<CardHeader className='pb-2'>
<CardTitle className='flex items-center gap-2'>
<Building2 className='h-5 w-5 text-red-600' />
{t('corporate.companyCard.title')}
</CardTitle>
</CardHeader>
<CardContent>
<div className='grid gap-6 md:grid-cols-2'>
<div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>
{t('corporate.companyCard.companyNameLabel')}
</p>
<p className='font-medium'>{companyData.companyName}</p>
</div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>
{t('corporate.companyCard.cardsCountLabel')}
</p>
<p className='font-medium'>{companyData.numberOfCards}</p>
</div>
<div className='space-y-1'>
<p className='text-sm text-gray-500'>
{t('corporate.companyCard.registrationDateLabel')}
</p>
<p className='font-medium'>
{companyData.registrationDate}
</p>
</div>
</div>
<div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>
{t('corporate.companyCard.fundLabel')}
</p>
<p className='font-medium'>
{companyData.fund.toLocaleString()}{' '}
{t('corporate.currency')}
</p>
</div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>
{t('corporate.companyCard.overdraftLabel')}
</p>
<p className='font-medium'>
{companyData.overdraft.toLocaleString()}{' '}
{t('corporate.currency')}
</p>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Fund Card */}
<Card className='bg-gradient-to-br from-red-600 to-red-800 text-white'>
<CardHeader>
<CardTitle className='flex items-center gap-2'>
<Wallet className='h-5 w-5' />
{t('corporate.fundCard.title')}
</CardTitle>
<CardDescription className='text-white/80'>
{t('corporate.fundCard.description')}
</CardDescription>
</CardHeader>
<CardContent>
<div className='text-center'>
<p className='mb-2 text-4xl font-bold'>
{companyData.totalFund.toLocaleString()}
</p>
<p className='text-white/80'>
{t('corporate.fundCard.currency')}
</p>
</div>
</CardContent>
</Card>
</div>
{/* <CardsList totalCards={companyData.numberOfCards} /> */}
{/* Transactions */}
<TransactionsTable />
</div>
</main>
</div>
);
}

View File

@ -0,0 +1,125 @@
'use client';
import { ArrowUpRight, Clock, CreditCard, LogOut, User } from 'lucide-react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/shared/shadcn-ui/card';
import { TransactionsTable } from '@/widgets/transactions-table';
// Sample customer data
const customerData = {
firstName: 'Алишер',
lastName: 'Рахмонов',
passportNumber: 'A12345678',
bonusPoints: 1250,
cardNumber: '5678-9012-3456-7890',
expiryDate: '12/2025',
registrationDate: '15.06.2020',
};
export function CustomerDashboard() {
const { t } = useTextController();
return (
<div className='flex min-h-screen flex-col'>
<main className='flex-1 py-10'>
<div className='container mx-auto max-w-6xl'>
<div className='mb-8 flex items-center justify-between'>
<h1 className='text-3xl font-bold'>{t('customer.pageTitle')}</h1>
<Button variant='outline' className='gap-2'>
<LogOut className='h-4 w-4' />
{t('customer.logoutButton')}
</Button>
</div>
<div className='mb-10 grid gap-3 md:grid-cols-3 md:gap-6'>
{/* Bonus Card */}
<Card className='bg-gradient-to-br from-red-600 to-red-800 text-white'>
<CardHeader>
<CardTitle className='flex items-center gap-2'>
<CreditCard className='h-5 w-5' />
{t('customer.bonusCard.title')}
</CardTitle>
<CardDescription className='text-white/80'>
{t('customer.bonusCard.description')}
</CardDescription>
</CardHeader>
<CardContent>
<div className='text-center'>
<p className='mb-2 text-4xl font-bold'>
{customerData.bonusPoints}
</p>
<p className='text-white/80'>
{t('customer.bonusCard.points')}
</p>
</div>
<div className='mt-6 flex items-center justify-between'>
<div className='flex items-center gap-1 text-sm text-white/80'>
<Clock className='h-4 w-4' />
<span>{t('customer.bonusCard.validUntil')}</span>
</div>
<ArrowUpRight className='h-5 w-5 text-white/60' />
</div>
</CardContent>
</Card>
{/* Customer Card */}
<Card className='md:col-span-2'>
<CardHeader className='pb-2'>
<CardTitle className='flex items-center gap-2'>
<User className='h-5 w-5 text-red-600' />
{t('customer.infoCard.title')}
</CardTitle>
</CardHeader>
<CardContent>
<div className='grid gap-6 md:grid-cols-2'>
<div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>
{t('customer.infoCard.regDateLabel')}
</p>
<p className='font-medium'>
{customerData.firstName} {customerData.lastName}
</p>
</div>
<div className='space-y-1'>
<p className='text-sm text-gray-500'>
{t('customer.infoCard.regDateLabel')}
</p>
<p className='font-medium'>
{customerData.registrationDate}
</p>
</div>
</div>
<div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>
{t('customer.infoCard.cardNumberLabel')}
</p>
<p className='font-medium'>{customerData.cardNumber}</p>
</div>
<div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'>
{t('customer.infoCard.expiryDateLabel')}
</p>
<p className='font-medium'>{customerData.expiryDate}</p>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
<TransactionsTable />
</div>
</main>
</div>
);
}

View File

@ -1,20 +1,12 @@
'use client';
import {
Award,
Fuel,
History,
MapPin,
Star,
Target,
Users,
} from 'lucide-react';
import { Fuel, History, MapPin, Star, Target, Users } from 'lucide-react';
import Image from 'next/image';
// import { useTranslation } from 'next-i18next';
import AnimatedCounter from '@/shared/components/animated-counter';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import { Card, CardContent } from '@/shared/shadcn-ui/card';
@ -28,7 +20,7 @@ export const metadata = {
};
export default function AboutPage() {
const { t } = useLanguage();
const { t } = useTextController();
return (
<div className='flex min-h-screen flex-col'>

View File

@ -0,0 +1,277 @@
'use client';
import {
Calendar,
CheckCircle,
Heart,
Landmark,
MapPin,
Users,
} from 'lucide-react';
import Image from 'next/image';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from '@/shared/shadcn-ui/card';
import { CtaSection } from '@/widgets/cta-section';
export const metadata = {
title: 'Благотворительность | GasNetwork - Сеть заправок в Таджикистане',
description:
'Благотворительные проекты и инициативы GasNetwork. Мы помогаем обществу и заботимся о будущем.',
};
export function CharityPage() {
const { t } = useTextController();
return (
<div className='flex min-h-screen flex-col'>
<main className='flex-1'>
{/* Hero Section */}
<section className='relative'>
<div className='relative h-[400px] w-full overflow-hidden'>
<Image
src='/placeholder.svg?height=500&width=1920&text=Благотворительный+фонд+GasNetwork'
alt={t('charity.hero.imageAlt')}
width={1920}
height={500}
className='object-cover'
priority
/>
<div className='absolute inset-0 flex items-center bg-gradient-to-r from-black/70 to-black/30'>
<div className='container mx-auto'>
<div className='max-w-2xl space-y-6 text-white'>
<div className='inline-flex items-center justify-center rounded-full bg-red-600/20 p-2'>
<Heart className='size-6 text-red-500' />
</div>
<h1 className='text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl'>
{t('charity.hero.title')}
</h1>
<p className='text-xl text-gray-200'>
{t('charity.hero.subtitle')}
</p>
</div>
</div>
</div>
</div>
</section>
{/* Mission Section */}
<section className='py-16'>
<div className='container mx-auto'>
<div className='grid items-center gap-12 md:grid-cols-2'>
<div>
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
<Heart className='h-6 w-6 text-red-600' />
</div>
<h2 className='mb-6 text-3xl font-bold tracking-tight sm:text-4xl'>
{t('charity.mission.title')}
</h2>
<p className='mb-6 text-gray-600'>
{t('charity.mission.description1')}
</p>
<p className='mb-6 text-gray-600'>
{t('charity.mission.description2')}
</p>
<div className='space-y-4'>
{[0, 1, 2].map((index) => (
<div key={index} className='flex items-start'>
<CheckCircle className='mr-3 h-6 w-6 flex-shrink-0 text-red-600' />
<div>
<h3 className='text-lg font-medium'>
{t(`charity.mission.principles.${index}.title`)}
</h3>
<p className='text-gray-600'>
{t(`charity.mission.principles.${index}.description`)}
</p>
</div>
</div>
))}
</div>
</div>
<div className='relative h-[500px] overflow-hidden rounded-xl shadow-xl'>
<Image
src='/placeholder.svg?height=500&width=600&text=Наша+миссия'
alt={t('charity.mission.imageAlt')}
fill
className='object-cover'
/>
</div>
</div>
</div>
</section>
{/* Key Figures */}
<section className='bg-red-600 py-16 text-white'>
<div className='container mx-auto'>
<div className='mb-12 text-center'>
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
{t('charity.stats.title')}
</h2>
<p className='mx-auto max-w-2xl text-white/80'>
{t('charity.stats.subtitle')}
</p>
</div>
<div className='grid grid-cols-1 gap-8 text-center md:grid-cols-3'>
{[0, 1, 2].map((index) => (
<div key={index} className='space-y-2'>
<h3 className='text-4xl font-bold'>
{t(`charity.stats.items.${index}.value`)}
</h3>
<p className='text-white/80'>
{t(`charity.stats.items.${index}.label`)}
</p>
</div>
))}
</div>
</div>
</section>
{/* Upcoming Events */}
<section className='bg-gray-50 py-16'>
<div className='container mx-auto'>
<div className='mb-12 text-center'>
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
<Calendar className='h-6 w-6 text-red-600' />
</div>
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
{t('charity.events.title')}
</h2>
<p className='mx-auto max-w-2xl text-gray-600'>
{t('charity.events.subtitle')}
</p>
</div>
<div className='grid gap-6 md:grid-cols-3'>
{[
{
title: 'Благотворительный марафон',
description:
'Ежегодный благотворительный марафон в поддержку детей с особыми потребностями.',
date: '15 июня 2023',
location: 'Парк Рудаки, Душанбе',
image: '/placeholder.svg?height=200&width=300&text=Марафон',
},
{
title: 'Экологическая акция',
description:
'Очистка берегов реки Варзоб от мусора и посадка деревьев.',
date: '22 июля 2023',
location: 'Река Варзоб, Душанбе',
image:
'/placeholder.svg?height=200&width=300&text=Экологическая+акция',
},
{
title: 'Сбор школьных принадлежностей',
description:
'Сбор школьных принадлежностей для детей из малообеспеченных семей к новому учебному году.',
date: '1-20 августа 2023',
location: 'Все заправки GasNetwork',
image:
'/placeholder.svg?height=200&width=300&text=Школьные+принадлежности',
},
].map((event, index) => (
<Card key={index} className='overflow-hidden'>
<div className='relative h-48 w-full'>
<Image
src={event.image || '/placeholder.svg'}
alt={event.title}
fill
className='object-cover'
/>
</div>
<CardHeader>
<CardTitle>{event.title}</CardTitle>
</CardHeader>
<CardContent className='space-y-4'>
<p className='text-gray-600'>{event.description}</p>
<div className='flex items-center text-sm text-gray-500'>
<Calendar className='mr-2 h-4 w-4' />
{event.date}
</div>
<div className='flex items-center text-sm text-gray-500'>
<MapPin className='mr-2 h-4 w-4' />
{event.location}
</div>
</CardContent>
<CardFooter>
<Button className='w-full bg-red-600 hover:bg-red-700'>
{t(`charity.events.button`)}
</Button>
</CardFooter>
</Card>
))}
</div>
</div>
</section>
{/* How to Help */}
<section className='py-16'>
<div className='container mx-auto'>
<div className='mb-12 text-center'>
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
<Users className='h-6 w-6 text-red-600' />
</div>
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
{t('charity.help.title')}
</h2>
<p className='mx-auto max-w-2xl text-gray-600'>
{t('charity.help.subtitle')}
</p>
</div>
<div className='grid gap-3 md:grid-cols-2 md:gap-6 lg:grid-cols-4'>
{[
{
title: 'Сделать пожертвование',
description:
'Ваше пожертвование поможет нам реализовать больше проектов и помочь большему количеству людей.',
icon: <Landmark className='h-10 w-10 text-red-600' />,
},
{
title: 'Стать волонтером',
description:
'Присоединяйтесь к нашей команде волонтеров и помогайте нам в реализации благотворительных проектов.',
icon: <Users className='h-10 w-10 text-red-600' />,
},
{
title: 'Участвовать в мероприятиях',
description:
'Принимайте участие в наших благотворительных мероприятиях и акциях.',
icon: <Calendar className='h-10 w-10 text-red-600' />,
},
{
title: 'Распространять информацию',
description:
'Расскажите о нашем фонде и его деятельности своим друзьям и знакомым.',
icon: <Heart className='h-10 w-10 text-red-600' />,
},
].map((item, index) => (
<Card key={index} className='text-center'>
<CardHeader>
<div className='mb-4 flex justify-center'>{item.icon}</div>
<CardTitle className='break-words hyphens-auto'>
{item.title}
</CardTitle>
</CardHeader>
<CardContent>
<p className='text-gray-600'>{item.description}</p>
</CardContent>
</Card>
))}
</div>
</div>
</section>
<CtaSection />
</main>
</div>
);
}

View File

@ -3,12 +3,12 @@
import { Download, Eye } from 'lucide-react';
import Image from 'next/image';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import { Card, CardContent } from '@/shared/shadcn-ui/card';
export function CertificatesPage() {
const { t } = useLanguage();
const { t } = useTextController();
// This data would typically come from an API or CMS
// We're keeping it as-is since it's dynamic content

View File

@ -1,11 +1,12 @@
"use client"
'use client';
import Image from 'next/image';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { BenefitsSection } from '@/widgets/clients/ui/benefits-section';
import { ServicesOverviewSection } from '@/widgets/clients/ui/services-overview-section';
import { CtaSection } from '@/widgets/cta-section';
import { useLanguage } from '@/shared/language';
export const metadata = {
title: 'Клиентам | GasNetwork - Сеть заправок в Таджикистане',
@ -14,8 +15,7 @@ export const metadata = {
};
export function ClientsPage() {
const { t } = useLanguage()
const { t } = useTextController();
return (
<div className='flex min-h-screen flex-col'>

View File

@ -3,7 +3,7 @@
import { Check, Percent } from 'lucide-react';
import Image from 'next/image';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Card, CardContent } from '@/shared/shadcn-ui/card';
import { CtaSection } from '@/widgets/cta-section';
@ -15,7 +15,7 @@ export const metadata = {
};
export function LoyaltyPage() {
const { t } = useLanguage();
const { t } = useTextController();
return (
<div className='flex min-h-screen flex-col'>

View File

@ -5,7 +5,7 @@ import Link from 'next/link';
import { LoginForm } from '@/features/auth/login-form';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import {
Card,
CardContent,
@ -38,7 +38,7 @@ const tabs = [
];
export default function LoginPage() {
const { t } = useLanguage();
const { t } = useTextController();
return (
<div className='flex min-h-screen flex-col items-center justify-center'>

View File

@ -3,14 +3,15 @@
import { Users } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import AnimatedCounter from './animated-counter';
import { useLanguage } from '../language';
export default function AboutCounter() {
const [isVisible, setIsVisible] = useState(false);
const sectionRef = useRef<HTMLDivElement>(null);
const {t} = useLanguage()
const { t } = useTextController();
useEffect(() => {
const observer = new IntersectionObserver(
@ -44,7 +45,7 @@ export default function AboutCounter() {
<h3 className='text-2xl font-bold text-gray-900'>
{isVisible ? <AnimatedCounter end={150} suffix='+' /> : '0+'}
</h3>
<p className='text-gray-600'>{t("about.stats.items.2.label")}</p>
<p className='text-gray-600'>{t('about.stats.items.2.label')}</p>
</div>
<div className='transform rounded-lg bg-white p-3 shadow-md transition-transform hover:scale-105 sm:p-6'>
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
@ -53,7 +54,7 @@ export default function AboutCounter() {
<h3 className='text-2xl font-bold text-gray-900'>
{isVisible ? <AnimatedCounter end={5} suffix='M+' /> : '0M+'}
</h3>
<p className='text-gray-600'>{t("about.stats.items.4.label")}</p>
<p className='text-gray-600'>{t('about.stats.items.4.label')}</p>
</div>
<div className='transform rounded-lg bg-white p-3 shadow-md transition-transform hover:scale-105 sm:p-6'>
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
@ -66,7 +67,7 @@ export default function AboutCounter() {
'0%'
)}
</h3>
<p className='text-gray-600'>{t("about.stats.items.5.label")}</p>
<p className='text-gray-600'>{t('about.stats.items.5.label')}</p>
</div>
</div>
);

View File

@ -7,11 +7,10 @@ import { useEffect, useState } from 'react';
import { Discounts } from '@/app/api-utlities/@types/main';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import { Card, CardContent } from '@/shared/shadcn-ui/card';
import { useLanguage } from '../language';
const promotions = [
{
id: 1,
@ -52,7 +51,7 @@ export default function PromotionSlider({ discounts }: PromotionSliderProps) {
const [currentIndex, setCurrentIndex] = useState(0);
const [visibleItems, setVisibleItems] = useState(3);
const { t } = useLanguage();
const { t } = useTextController();
useEffect(() => {
const handleResize = () => {

View File

@ -2,10 +2,9 @@
import { Loader2Icon } from 'lucide-react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button, type ButtonProps } from '@/shared/shadcn-ui/button';
import { useLanguage } from '../language';
interface SubmitButtonProps extends ButtonProps {
title?: string;
isLoading: boolean;
@ -21,7 +20,7 @@ export const SubmitButton = ({
onClick,
...props
}: SubmitButtonProps) => {
const { t } = useLanguage();
const { t } = useTextController();
return (
<Button

View File

@ -0,0 +1,12 @@
import { baseAPI } from '@/shared/api/base-api';
import { TextItem } from '@/shared/types/text.types';
export const textControlApi = baseAPI.injectEndpoints({
endpoints: (builder) => ({
fetchText: builder.query<TextItem[], void>({
query: () => '/text',
}),
}),
});
export const { useFetchTextQuery } = textControlApi;

View File

@ -1,73 +0,0 @@
'use client';
import { createContext, type ReactNode, useEffect, useState } from 'react';
import enTranslations from '../locales/en.json';
import ruTranslations from '../locales/ru.json';
// Define available languages
export const languages = {
en: { name: 'English', flag: '🇬🇧' },
ru: { name: 'Русский', flag: '🇷🇺' },
};
export type Language = keyof typeof languages;
// Define translations type with flat structure
export type Translations = Record<string, string>;
// Load translations
const translations: Record<Language, Translations> = {
en: enTranslations,
ru: ruTranslations,
};
// Create context
type LanguageContextType = {
language: Language;
setLanguage: (lang: Language) => void;
t: (key: string) => string;
};
export const LanguageContext = createContext<LanguageContextType | undefined>(
undefined,
);
// Create provider
export function LanguageProvider({ children }: { children: ReactNode }) {
// Default to Russian, but check localStorage on client
const [language, setLanguageState] = useState<Language>('ru');
useEffect(() => {
// Check if we're in the browser
if (typeof window !== 'undefined') {
const savedLanguage = localStorage.getItem('language') as Language;
if (savedLanguage && languages[savedLanguage]) {
setLanguageState(savedLanguage);
}
}
}, []);
const setLanguage = (lang: Language) => {
setLanguageState(lang);
if (typeof window !== 'undefined') {
localStorage.setItem('language', lang);
}
};
// Translation function for flat structure
const t = (key: string): string => {
if (translations[language][key]) {
return translations[language][key];
}
console.warn(`Translation key not found: ${key}`);
return key;
};
return (
<LanguageContext.Provider value={{ language, setLanguage, t }}>
{children}
</LanguageContext.Provider>
);
}

View File

@ -0,0 +1,49 @@
import { createContext, type ReactNode } from 'react';
import { TextItem } from '@/shared/types/text.types';
export type Translations = Record<string, string>;
// Create context
type TextControlContextType = {
t: (key: string) => string;
};
export const TextControlContext = createContext<
TextControlContextType | undefined
>(undefined);
// Create provider
export function TextControlProvider({
children,
textItems,
}: {
children: ReactNode;
textItems?: TextItem[];
}) {
const textMap = textItems?.reduce(
(pr, cr) => {
pr[cr.key] = cr.value;
return pr;
},
{} as Record<string, string | null>,
);
// Translation function for flat structure
const t = (key: string): string => {
if (textMap?.[key]) {
return textMap[key];
}
console.warn(`Translation key not found: ${key}`);
return key;
};
return (
<TextControlContext.Provider value={{ t }}>
{children}
</TextControlContext.Provider>
);
}

View File

@ -2,10 +2,10 @@
import { useContext } from 'react';
import { LanguageContext } from '../context/language-provider';
import { TextControlContext } from '../context/text-control-provider';
export function useLanguage() {
const context = useContext(LanguageContext);
export function useTextController() {
const context = useContext(TextControlContext);
if (context === undefined) {
throw new Error('useLanguage must be used within a LanguageProvider');
}

View File

@ -1,6 +1,2 @@
export {
LanguageProvider,
languages,
type Language,
} from './context/language-provider';
export { useLanguage } from './hooks/use-language';
export { TextControlProvider } from './context/text-control-provider';
export { useTextController } from './hooks/use-text-controller';

View File

@ -1,108 +0,0 @@
{
"common.buttons.readMore": "Read More",
"common.buttons.findStation": "Find Station",
"common.buttons.learnMore": "Learn More",
"common.buttons.download": "Download",
"common.buttons.view": "View",
"common.buttons.contactUs": "Contact Us",
"common.buttons.apply": "Apply",
"common.buttons.login": "Login",
"common.buttons.logout": "Logout",
"common.buttons.submit": "Submit",
"common.buttons.filter": "Filter",
"common.buttons.resetFilters": "Reset Filters",
"common.buttons.downloadApp": "Download App",
"common.buttons.purchaseCardAtGasStations": "Purchase a card at gas stations",
"common.buttons.sendResume": "Send Resume",
"common.buttons.showAllStations": "Show All Stations",
"common.buttons.allPromotions": "All Promotions",
"common.navigation.home": "Home",
"common.navigation.about": "About Us",
"common.navigation.clients": "For Clients",
"common.navigation.stations": "Our Stations",
"common.navigation.vacancies": "Vacancies",
"common.navigation.promotions": "Promotions",
"common.navigation.charity": "Charity",
"common.navigation.certificates": "Certificates",
"common.navigation.contacts": "Contacts",
"common.footer.contacts": "Contacts",
"common.footer.navigation": "Navigation",
"common.footer.yourEmail": "Your email",
"common.footer.rights": "All rights reserved.",
"home.hero.title": "Modern Gas Station Network in Tajikistan",
"home.hero.description": "Quality fuel, convenient locations, and excellent service for our customers",
"home.about.title": "About Our Company",
"home.about.description1": "Our gas station network is one of the leading in Tajikistan. We provide high-quality fuel and a high level of service for our customers for more than 15 years.",
"home.about.description2": "We are constantly developing, opening new stations and improving service at existing ones. Our goal is to make refueling as convenient and fast as possible for every customer.",
"home.about.features.quality.title": "Quality Fuel",
"home.about.features.quality.description": "We guarantee the high quality of our fuel",
"home.about.features.equipment.title": "Modern Equipment",
"home.about.features.equipment.description": "All our stations are equipped with modern equipment",
"home.about.features.staff.title": "Professional Staff",
"home.about.features.staff.description": "Our employees are professionals in their field",
"home.stations.title": "Our Stations",
"home.stations.description": "Find the nearest station in our network. We are located in convenient places throughout Tajikistan.",
"home.promotions.title": "Current Promotions",
"home.promotions.description": "Special offers and promotions for our customers. Refuel profitably!",
"home.vacancies.title": "Vacancies",
"home.vacancies.description": "Join our team of professionals. We offer stable work and opportunities for growth.",
"home.vacancies.all": "All vacancies",
"home.vacancies.office": "Office",
"home.vacancies.stations": "Stations",
"home.vacancies.fullTime": "Full time",
"home.vacancies.experience": "Experience from 1 year",
"home.vacancies.shiftWork": "Shift work",
"home.vacancies.training": "Training",
"home.partners.title": "Our Partners",
"home.partners.description": "We cooperate with leading companies to provide the best services to our customers.",
"home.partners.becomePartner": "Become Our Partner",
"home.partners.becomePartnerText": "We are open to cooperation and new partnerships. Contact us to discuss opportunities.",
"home.charity.title": "Charity Foundation",
"home.charity.description": "Our charity foundation was created to support socially significant projects in Tajikistan. We strive to contribute to the development of society and help those in need.",
"home.charity.directions": "Main directions of our foundation's activities:",
"home.charity.education": "Support for educational programs",
"home.charity.children": "Help for children from low-income families",
"home.charity.ecology": "Environmental initiatives",
"home.charity.sports": "Support for sports events",
"home.charity.learnMore": "Learn More About the Foundation",
"home.cta.title": "Join Us",
"home.cta.description": "Become part of our network. Get special offers, bonuses, and discounts.",
"certificates.title": "Our Certificates",
"certificates.description": "GasNetwork adheres to high standards of quality and safety. Our certificates confirm the compliance of our products and services with international and national standards.",
"certificates.issueDate": "Issue Date",
"certificates.expiryDate": "Valid Until",
"certificates.faq": "Frequently Asked Questions",
"auth.title": "Login to your account",
"auth.description": "Log in to your personal account to access information about your bonuses, transaction history and other features.",
"auth.bonusClient": "Bonus Client",
"auth.corporateClient": "Corporate Client",
"auth.bonusLogin.title": "Login for bonus clients",
"auth.bonusLogin.description": "Enter your phone number and bonus card number to log in to your personal account.",
"auth.corporateLogin.title": "Login for corporate clients",
"auth.corporateLogin.description": "Enter your phone number and corporate card number to log in to your personal account.",
"auth.phoneNumber": "Phone number",
"auth.cardNumber": "Card number",
"auth.loginIssues": "Having trouble logging in?",
"map.filters": "Filters",
"map.stationsList": "Stations List",
"map.noStations": "No stations matching the selected filters",
"map.ourStations": "Our Stations",
"map.totalStations": "Total stations",
"map.services": "Services",
"map.cities": "Cities",
"map.allCities": "All Cities"
}

View File

@ -1,247 +0,0 @@
{
"common.contacts.address": "ул. Рудаки 137, Душанбе, Таджикистан",
"common.contacts.tel": "+992 (37) 223-45-67",
"common.contacts.email": "info@gasnetwork.tj",
"common.buttons.readMore": "Подробнее",
"common.buttons.findStation": "Найти заправку",
"common.buttons.learnMore": "Узнать больше",
"common.buttons.download": "Скачать",
"common.buttons.view": "Просмотр",
"common.buttons.contactUs": "Связаться с нами",
"common.buttons.apply": "Подать заявку",
"common.buttons.login": "Вход",
"common.buttons.logout": "Выйти",
"common.buttons.submit": "Отправить",
"common.buttons.filter": "Фильтры",
"common.buttons.resetFilters": "Сбросить фильтры",
"common.buttons.downloadApp": "Скачать приложение",
"common.buttons.purchaseCardAtGasStations": "Приобретайте карту в сети АЗС",
"common.buttons.sendResume": "Отправить резюме",
"common.buttons.showAllStations": "Показать все заправки",
"common.buttons.allPromotions": "Все акции",
"common.navigation.home": "Главная",
"common.navigation.about": "О нас",
"common.navigation.clients": "Клиентам",
"common.navigation.stations": "Наши заправки",
"common.navigation.vacancies": "Вакансии",
"common.navigation.promotions": "Акции",
"common.navigation.charity": "Благотворительность",
"common.navigation.certificates": "Сертификаты",
"common.navigation.contacts": "Контакты",
"common.footer.contacts": "Контакты",
"common.footer.navigation": "Навигация",
"common.footer.yourEmail": "Ваш email",
"common.footer.rights": "Все права защищены.",
"home.hero.title": "Сеть современных заправок в Таджикистане",
"home.hero.description": "Качественное топливо, удобное расположение и отличный сервис для наших клиентов",
"home.stats.stations": "Заправок по стране",
"home.stats.daily": "Клиентов ежедневно",
"home.stats.years": "Лет на рынке",
"home.stats.mode": "Работаем круглосуточно",
"home.about.title": "О нашей компании",
"home.about.description1": "Наша сеть заправок является одной из ведущих в Таджикистане. Мы предоставляем качественное топливо и высокий уровень обслуживания для наших клиентов уже более 15 лет.",
"home.about.description2": "Мы постоянно развиваемся, открывая новые станции и улучшая сервис на существующих. Наша цель - сделать заправку автомобиля максимально удобной и быстрой для каждого клиента.",
"home.about.features.quality.title": "Качественное топливо",
"home.about.features.quality.description": "Мы гарантируем высокое качество нашего топлива",
"home.about.features.equipment.title": "Современное оборудование",
"home.about.features.equipment.description": "Все наши станции оснащены современным оборудованием",
"home.about.features.staff.title": "Профессиональный персонал",
"home.about.features.staff.description": "Наши сотрудники - профессионалы своего дела",
"about.hero.imageAlt": "О нашей компании",
"about.hero.title": "О нашей компании",
"about.hero.subtitle": "Узнайте больше о нашей истории, ценностях и миссии. Мы стремимся предоставлять лучший сервис и качественное топливо для наших клиентов.",
"about.overview.title": "Лидер на рынке Таджикистана",
"about.overview.description1": "GasNetwork - ведущая сеть автозаправочных станций в Таджикистане, предоставляющая высококачественное топливо и превосходный сервис. Наша компания была основана в 2008 году и с тех пор стала символом надежности и качества в энергетическом секторе страны.",
"about.overview.description2": "Мы гордимся тем, что предлагаем нашим клиентам только лучшее топливо, соответствующее международным стандартам качества. Наши заправочные станции оснащены современным оборудованием, которое обеспечивает быстрое и безопасное обслуживание.",
"about.overview.description3": "Наша миссия - сделать поездки наших клиентов комфортными и безопасными, предоставляя качественное топливо и отличный сервис по всей стране.",
"about.overview.imageAlt": "Главный офис GasNetwork",
"about.overview.benefits.0.title": "Качество",
"about.overview.benefits.0.description": "Топливо высшего стандарта",
"about.overview.benefits.1.title": "Сервис",
"about.overview.benefits.1.description": "Профессиональное обслуживание",
"about.overview.benefits.2.title": "Инновации",
"about.overview.benefits.2.description": "Современные технологии",
"about.overview.benefits.3.title": "Доступность",
"about.overview.benefits.3.description": "Станции по всей стране",
"about.stats.title": "GasNetwork в цифрах",
"about.stats.subtitle": "Наши достижения и рост за годы работы на рынке Таджикистана",
"about.stats.items.0.value": "25",
"about.stats.items.0.suffix": "+",
"about.stats.items.0.label": "Заправок по стране",
"about.stats.items.1.value": "15",
"about.stats.items.1.label": "Лет на рынке",
"about.stats.items.2.value": "150",
"about.stats.items.2.suffix": "+",
"about.stats.items.2.label": "Сотрудников",
"about.stats.items.3.value": "1000000",
"about.stats.items.3.suffix": "+",
"about.stats.items.3.label": "Клиентов в год",
"about.stats.items.4.label": "Литров топлива в месяц",
"about.stats.items.5.label": "Довольных клиентов",
"about.history.title": "Наша история",
"about.history.subtitle": "История развития нашей компании от небольшой сети до лидера рынка",
"about.stations.title": "Наши заправочные станции",
"about.stations.subtitle": "Современные заправочные станции, оснащенные по последнему слову техники",
"about.stations.description": "Наши заправочные станции расположены в стратегически важных точках по всему Таджикистану, обеспечивая удобный доступ для всех наших клиентов.",
"about.stations.buttonText": "Найти ближайшую заправку",
"about.values.title": "Наши ценности",
"about.values.subtitle": "Принципы, которыми мы руководствуемся в нашей работе",
"about.values.items.0.title": "Качество",
"about.values.items.0.description": "Мы предлагаем только высококачественное топливо, соответствующее международным стандартам. Регулярные проверки и контроль качества гарантируют, что наши клиенты получают лучшее.",
"about.values.items.1.title": "Клиентоориентированность",
"about.values.items.1.description": "Наши клиенты - наш главный приоритет. Мы стремимся предоставить лучший сервис, удобные условия и приятную атмосферу на каждой нашей заправке.",
"about.values.items.2.title": "Профессионализм",
"about.values.items.2.description": "Наши сотрудники - профессионалы своего дела. Мы постоянно инвестируем в их обучение и развитие, чтобы обеспечить высокий уровень обслуживания.",
"about.team.title": "Наша команда",
"about.team.subtitle": "Познакомьтесь с профессионалами, которые делают GasNetwork лучшей сетью заправок в Таджикистане",
"about.team.members.0.name": "Алишер Рахмонов",
"about.team.members.0.position": "Генеральный директор",
"about.team.members.1.name": "Фарида Каримова",
"about.team.members.1.position": "Финансовый директор",
"about.team.members.2.name": "Рустам Назаров",
"about.team.members.2.position": "Технический директор",
"about.team.members.3.name": "Зарина Шарипова",
"about.team.members.3.position": "Директор по маркетингу",
"about.testimonials.title": "Отзывы клиентов",
"about.testimonials.subtitle": "Что говорят о нас наши клиенты",
"about.testimonials.items.0.name": "Фархад К.",
"about.testimonials.items.0.text": "Я всегда заправляюсь только на GasNetwork. Качество топлива на высоте, а обслуживание всегда приветливое и быстрое.",
"about.testimonials.items.0.rating": "5",
"about.testimonials.items.1.name": "Нигина М.",
"about.testimonials.items.1.text": "Очень удобно, что заправки расположены по всему городу. Всегда чисто, есть кафе и магазин. Рекомендую!",
"about.testimonials.items.1.rating": "5",
"about.testimonials.items.2.name": "Джамшед Р.",
"about.testimonials.items.2.text": "Пользуюсь картой лояльности GasNetwork уже 3 года. Накопил много бонусов и получил немало приятных подарков. Отличный сервис!",
"about.testimonials.items.2.rating": "4",
"home.stations.title": "Наши заправки",
"home.stations.description": "Найдите ближайшую к вам заправку нашей сети. Мы расположены в удобных местах по всему Таджикистану.",
"home.promotions.title": "Актуальные акции",
"home.promotions.description": "Специальные предложения и акции для наших клиентов. Заправляйтесь выгодно!",
"home.vacancies.title": "Вакансии",
"home.vacancies.description": "Присоединяйтесь к нашей команде профессионалов. Мы предлагаем стабильную работу и возможности для роста.",
"home.vacancies.all": "Все вакансии",
"home.vacancies.office": "Офис",
"home.vacancies.stations": "Заправки",
"home.vacancies.fullTime": "Полный день",
"home.vacancies.experience": "Опыт от 1 года",
"home.vacancies.shiftWork": "Сменный график",
"home.vacancies.training": "Обучение",
"home.partners.title": "Наши партнеры",
"home.partners.description": "Мы сотрудничаем с ведущими компаниями для предоставления лучших услуг нашим клиентам.",
"home.partners.becomePartner": "Станьте нашим партнером",
"home.partners.becomePartnerText": "Мы открыты для сотрудничества и новых партнерских отношений. Свяжитесь с нами для обсуждения возможностей.",
"home.charity.title": "Благотворительный фонд",
"home.charity.description": "Наш благотворительный фонд был создан для поддержки социально значимых проектов в Таджикистане. Мы стремимся внести свой вклад в развитие общества и помочь тем, кто в этом нуждается.",
"home.charity.directions": "Основные направления деятельности нашего фонда:",
"home.charity.education": "Поддержка образовательных программ",
"home.charity.children": "Помощь детям из малообеспеченных семей",
"home.charity.ecology": "Экологические инициативы",
"home.charity.sports": "Поддержка спортивных мероприятий",
"home.charity.learnMore": "Подробнее о фонде",
"home.cta.title": "Присоединяйтесь к нам",
"home.cta.description": "Станьте частью нашей сети. Получайте специальные предложения, бонусы и скидки.",
"clients.title": "Для наших клиентов",
"clients.description": "Информация для клиентов: программа лояльности, топливные карты, сертификаты и способы оплаты.",
"clients.services.title": "Наши услуги для клиентов",
"clients.services.subtitle": "Мы стремимся сделать обслуживание на наших заправках максимально удобным и выгодным для вас",
"clients.benefits.title": "Преимущества для клиентов",
"clients.benefits.subtitle": "Став клиентом GasNetwork, вы получаете множество преимуществ, которые делают заправку вашего автомобиля более выгодной и удобной.",
"clients.loyalty.programm.about": "О программе лояльности",
"clients.loyalty.programm.about-description": "Программа лояльности GasNetwork — это возможность получать баллы за каждую покупку топлива и услуг на наших заправочных станциях. Накопленные баллы можно обменять на скидки, подарки или дополнительные услуги.",
"clients.loyalty.programm.about-description-2": "Участие в программе абсолютно бесплатное. Вам нужно только получить карту лояльности в любой нашей заправочной станции или зарегистрироваться в мобильном приложении.",
"clients.loyalty.programm.conditions-1": "1 литр = 1 балл",
"clients.loyalty.programm.conditions.description-1": "За каждый литр топлива вы получаете 1 балл",
"clients.loyalty.programm.conditions-2": "Дополнительные баллы",
"clients.loyalty.programm.conditions.description-2": "За покупки в магазине и кафе на заправке",
"clients.loyalty.programm.conditions-3": "Специальные акции",
"clients.loyalty.programm.conditions.description-3": "Удвоенные и утроенные баллы в праздничные дни",
"clients.loyalty.works.title": "Как это работает",
"clients.loyalty.works.description": "Простые шаги для участия в программе лояльности GasNetwork",
"clients.loyalty.works.stage-1": "Получите карту",
"clients.loyalty.works.stage.description-1": "Получите карту лояльности на любой заправке GasNetwork или зарегистрируйтесь в мобильном приложении",
"clients.loyalty.works.stage-2": "Заправляйтесь",
"clients.loyalty.works.stage.description-2": "Используйте карту при каждой заправке и покупке в магазинах на наших АЗС",
"clients.loyalty.works.stage-3": "Накапливайте баллы",
"clients.loyalty.works.stage.description-3": "Получайте баллы за каждую покупку и следите за их накоплением в приложении",
"clients.loyalty.works.stage-4": "Получайте выгоду",
"clients.loyalty.works.stage.description-4": "Обменивайте накопленные баллы на скидки, подарки или дополнительные услуги",
"clients.loyalty.works.levels.title": "Уровни лояльности",
"clients.loyalty.works.levels.description": "Чем больше вы заправляетесь, тем больше преимуществ получаете",
"clients.loyalty.works.levels.card.mark": "возврат баллами",
"clients.loyalty.works.levels.card-1.title": "Стандарт",
"clients.loyalty.works.levels.card-1.percent": "1%",
"clients.loyalty.works.levels.card-1.bonus-1": "1 балл за каждый литр топлива",
"clients.loyalty.works.levels.card-1.bonus-2": "Участие в акциях",
"clients.loyalty.works.levels.card-1.bonus-3": "Доступ к мобильному приложению",
"clients.loyalty.works.levels.card-2.title": "Золотой",
"clients.loyalty.works.levels.card-2.percent": "2%",
"clients.loyalty.works.levels.card-2.bonus-1": "2 балла за каждый литр топлива",
"clients.loyalty.works.levels.card-2.bonus-2": "Скидка 5% в кафе на заправках",
"clients.loyalty.works.levels.card-2.bonus-3": "Приоритетное обслуживание",
"clients.loyalty.works.levels.card-2.bonus-4": "Эксклюзивные акции",
"clients.loyalty.works.levels.card-3.title": "Платиновый",
"clients.loyalty.works.levels.card-3.percent": "3%",
"clients.loyalty.works.levels.card-3.bonus-1": "3 балла за каждый литр топлива",
"clients.loyalty.works.levels.card-3.bonus-2": "Скидка 10% в кафе на заправках",
"clients.loyalty.works.levels.card-3.bonus-3": "Бесплатная мойка раз в месяц",
"clients.loyalty.works.levels.card-3.bonus-4": "Персональный менеджер",
"clients.loyalty.works.levels.card-3.bonus-5": "VIP-обслуживание",
"certificates.title": "Наши сертификаты",
"certificates.description": "GasNetwork придерживается высоких стандартов качества и безопасности. Наши сертификаты подтверждают соответствие нашей продукции и услуг международным и национальным стандартам.",
"certificates.issueDate": "Дата выдачи",
"certificates.expiryDate": "Действителен до",
"certificates.faq": "Часто задаваемые вопросы",
"auth.title": "Вход в личный кабинет",
"auth.description": "Войдите в личный кабинет, чтобы получить доступ к информации о ваших бонусах, истории операций и другим возможностям.",
"auth.bonusClient": "Бонусный клиент",
"auth.corporateClient": "Корпоративный клиент",
"auth.bonusLogin.title": "Вход для бонусных клиентов",
"auth.bonusLogin.description": "Введите номер телефона и номер бонусной карты для входа в личный кабинет.",
"auth.corporateLogin.title": "Вход для корпоративных клиентов",
"auth.corporateLogin.description": "Введите номер телефона и номер корпоративной карты для входа в личный кабинет.",
"auth.phoneNumber": "Номер телефона",
"auth.cardNumber": "Номер карты",
"auth.loginIssues": "Возникли проблемы со входом?",
"auth.contactLink": "Свяжитесь с нами",
"map.filters": "Фильтры",
"map.stationsList": "Список заправок",
"map.noStations": "Нет заправок, соответствующих выбранным фильтрам",
"map.ourStations": "Наши заправки",
"map.totalStations": "Всего станций",
"map.services": "Услуги",
"map.cities": "Города",
"map.allCities": "Все города"
}

View File

@ -1,44 +0,0 @@
'use client';
import { Check, Globe } from 'lucide-react';
import { useState } from 'react';
import { type Language, languages, useLanguage } from '@/shared/language';
import { Button } from '@/shared/shadcn-ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/shared/shadcn-ui/dropdown-menu';
export function LanguageSwitcher() {
const { language, setLanguage } = useLanguage();
const [open, setOpen] = useState(false);
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<Button variant='ghost' size='sm' className='h-8 w-8 px-0'>
<Globe className='h-4 w-4' />
<span className='sr-only'>Switch language</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
{Object.entries(languages).map(([code, { name, flag }]) => (
<DropdownMenuItem
key={code}
onClick={() => {
setLanguage(code as Language);
setOpen(false);
}}
>
<span className='mr-2'>{flag}</span>
{name}
{code === language && <Check className='ml-2 h-4 w-4' />}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -2,20 +2,22 @@
import { Provider } from 'react-redux';
import { LanguageProvider } from '../language';
import { TextControlProvider } from '../language';
import { store } from '../store';
import { ThemeProvider } from '../theme/theme-provider';
import { TextItem } from '../types/text.types';
import { AosProvider } from './aos-provider';
import { Toaster } from './toaster';
type ProvidersProps = {
children: React.ReactNode;
textItems: TextItem[];
};
export const Providers = ({ children }: ProvidersProps) => {
export const Providers = ({ children, textItems }: ProvidersProps) => {
return (
<Provider store={store}>
<LanguageProvider>
<TextControlProvider textItems={textItems}>
<ThemeProvider
attribute='class'
defaultTheme='light'
@ -27,7 +29,7 @@ export const Providers = ({ children }: ProvidersProps) => {
<Toaster />
</AosProvider>
</ThemeProvider>
</LanguageProvider>
</TextControlProvider>
</Provider>
);
};

View File

@ -1,15 +1,20 @@
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query/react';
import { createWrapper } from 'next-redux-wrapper';
import { baseAPI } from '@/shared/api/base-api';
import { rootReducer } from './root-reducer';
export const store = configureStore({
export const makeStore = () =>
configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(baseAPI.middleware),
devTools: process.env.NODE_ENV === 'development',
});
});
export const store = makeStore();
setupListeners(store.dispatch);
@ -17,3 +22,5 @@ setupListeners(store.dispatch);
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
export const wrapper = createWrapper(makeStore);

View File

@ -0,0 +1,4 @@
export interface TextItem {
key: string;
value: string | null;
}

View File

@ -3,6 +3,7 @@
import { Calendar, ChevronDown, ChevronUp } from 'lucide-react';
import { useState } from 'react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import { Card, CardContent } from '@/shared/shadcn-ui/card';
@ -61,6 +62,8 @@ export function CompanyTimeline() {
const [expanded, setExpanded] = useState(false);
const displayEvents = expanded ? timelineEvents : timelineEvents.slice(0, 4);
const { t } = useTextController();
return (
<div className='relative'>
<div className='absolute left-1/2 -z-10 -ml-0.5 hidden h-full w-0.5 bg-gradient-to-b from-red-200 via-red-200 to-transparent md:block' />
@ -116,11 +119,13 @@ export function CompanyTimeline() {
>
{expanded ? (
<>
Свернуть <ChevronUp className='ml-2 size-4' />
{t('about.companyTimeline.rollUp.button')}{' '}
<ChevronUp className='ml-2 size-4' />
</>
) : (
<>
Показать больше <ChevronDown className='ml-2 size-4' />
{t('about.companyTimeline.show.button')}{' '}
<ChevronDown className='ml-2 size-4' />
</>
)}
</Button>

View File

@ -4,6 +4,7 @@ import { ChevronLeft, ChevronRight, Maximize } from 'lucide-react';
import Image from 'next/image';
import { useState } from 'react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import {
Dialog,
@ -72,6 +73,8 @@ export function StationGallery() {
setCurrentImage((prev) => (prev === 0 ? stations.length - 1 : prev - 1));
};
const { t } = useTextController();
return (
<div className='space-y-8'>
<div className='relative h-[400px] overflow-hidden rounded-xl shadow-xl md:h-[500px]'>
@ -93,7 +96,7 @@ export function StationGallery() {
onClick={prevImage}
>
<ChevronLeft className='h-6 w-6' />
<span className='sr-only'>Предыдущая</span>
<span className='sr-only'>{t('about.gallery.previous')}</span>
</Button>
<Button
@ -103,7 +106,7 @@ export function StationGallery() {
onClick={nextImage}
>
<ChevronRight className='h-6 w-6' />
<span className='sr-only'>Следующая</span>
<span className='sr-only'>{t('about.gallery.next')}</span>
</Button>
<Dialog>

View File

@ -4,10 +4,10 @@ import { Users } from 'lucide-react';
import Image from 'next/image';
import AboutCounter from '@/shared/components/about-counter';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
export const AboutSection = () => {
const { t } = useLanguage();
const { t } = useTextController();
return (
<section id='about' className='px-2 py-8 sm:py-16'>
@ -64,7 +64,7 @@ const features: Array<Feature> = [
];
const Features = () => {
const { t } = useLanguage();
const { t } = useTextController();
return (
<div className='space-y-4'>

View File

@ -4,11 +4,11 @@ import { ChevronRight, Heart } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
export const CharitySection = () => {
const { t } = useLanguage();
const { t } = useTextController();
return (
<section id='charity' className='px-2 py-8 sm:py-16'>

View File

@ -1,9 +1,10 @@
"use client"
'use client';
import { useLanguage } from '@/shared/language';
import { Percent } from 'lucide-react';
import Image from 'next/image';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
interface Benefit {
title: string;
description: string;
@ -29,8 +30,7 @@ const benefits: Array<Benefit> = [
];
export const BenefitsSection = () => {
const {t} = useLanguage()
const { t } = useTextController();
return (
<section className='bg-gray-50 py-16'>

View File

@ -1,8 +1,9 @@
"use client"
'use client';
import { CreditCard, type LucideProps, Percent, Wallet } from 'lucide-react';
import { type ForwardRefExoticComponent, type RefAttributes } from 'react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import {
Card,
CardContent,
@ -10,7 +11,6 @@ import {
CardHeader,
CardTitle,
} from '@/shared/shadcn-ui/card';
import { useLanguage } from '@/shared/language';
interface ServiceOverview {
title: string;
@ -46,8 +46,7 @@ const servicesOverview: Array<ServiceOverview> = [
];
export const ServicesOverviewSection = () => {
const {t} = useLanguage()
const { t } = useTextController();
return (
<section className='py-16'>

View File

@ -2,11 +2,11 @@
import Link from 'next/link';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
export const CtaSection = () => {
const { t } = useLanguage();
const { t } = useTextController();
return (
<section className='bg-red-600 px-2 py-8 text-white sm:py-16'>

View File

@ -3,10 +3,10 @@
import { Fuel, Mail, MapPin, Phone } from 'lucide-react';
import Link from 'next/link';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
export const Footer = () => {
const { t } = useLanguage();
const { t } = useTextController();
return (
<footer className='bg-gray-900 px-4 py-12 text-white'>
@ -15,7 +15,7 @@ export const Footer = () => {
<div>
<div className='mb-4 flex items-center gap-2'>
<Fuel className='h-6 w-6 text-red-500' />
<span className='text-xl font-bold'>GasNetwork</span>
<span className='text-xl font-bold'>{t('common.name')}</span>
</div>
<p className='mb-4 text-gray-400'>{t('home.hero.description')}</p>
<div className='flex space-x-4'>
@ -122,7 +122,7 @@ export const Footer = () => {
</div>
<div className='mt-8 border-t border-gray-800 pt-8 text-center text-gray-400'>
<p>
&copy; {new Date().getFullYear()} GasNetwork.{' '}
&copy; {new Date().getFullYear()} {t('common.name')}.{' '}
{t('common.footer.rights')}
</p>
</div>

View File

@ -1,6 +1,7 @@
import Link from 'next/link';
import React from 'react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { cn } from '@/shared/lib/utils';
import {
NavigationMenu,
@ -11,11 +12,9 @@ import {
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from '@/shared/shadcn-ui/navigation-menu';
import { useLanguage } from '@/shared/language';
export function DesktopNav() {
const { t } = useLanguage();
const { t } = useTextController();
return (
<NavigationMenu className='hidden lg:flex'>
@ -41,7 +40,9 @@ export function DesktopNav() {
</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>{t('common.navigation.clients')}</NavigationMenuTrigger>
<NavigationMenuTrigger>
{t('common.navigation.clients')}
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className='grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]'>
<li className='row-span-4'>
@ -60,10 +61,10 @@ export function DesktopNav() {
</NavigationMenuLink>
</li>
<ListItem href='/clients/loyalty' title='Программа лояльности'>
Накапливайте баллы и получайте скидки на топливо и услуги
{t('clients.header.loyalty.description')}
</ListItem>
<ListItem href='/clients/certificates' title='Сертификаты'>
Подарочные сертификаты на топливо и услуги нашей сети
{t('clients.header.certificates.description')}
</ListItem>
</ul>
</NavigationMenuContent>

View File

@ -4,7 +4,7 @@ import { UserCircle } from 'lucide-react';
import Link from 'next/link';
import { Logo } from '@/shared/assets/logo';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
// import { LanguageSwitcher } from '@/shared/language/ui/language-switcher';
import { Button } from '@/shared/shadcn-ui/button';
@ -12,7 +12,7 @@ import { DesktopNav } from './desktop-nav';
import { MobileNav } from './mobile-nav';
export function Header() {
const { t } = useLanguage();
const { t } = useTextController();
return (
<header className='sticky top-0 z-40 w-full border-b bg-white'>

View File

@ -4,6 +4,7 @@ import { ChevronDown, ChevronRight, Menu } from 'lucide-react';
import Link from 'next/link';
import { useState } from 'react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import {
Collapsible,
@ -16,6 +17,8 @@ export function MobileNav() {
const [open, setOpen] = useState(false);
const [clientsOpen, setClientsOpen] = useState(false);
const { t } = useTextController();
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
@ -31,19 +34,19 @@ export function MobileNav() {
onClick={() => setOpen(false)}
className='text-lg font-medium transition-colors hover:text-red-600'
>
Главная
{t('common.navigation.home')}
</Link>
<Link
href='/about'
onClick={() => setOpen(false)}
className='text-lg font-medium transition-colors hover:text-red-600'
>
О нас
{t('common.navigation.about')}
</Link>
<Collapsible open={clientsOpen} onOpenChange={setClientsOpen}>
<CollapsibleTrigger className='data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up flex w-full items-center justify-between text-lg font-medium transition-all hover:text-red-600'>
<span>Клиентам</span>
<span>{t('common.navigation.clients')}</span>
{clientsOpen ? (
<ChevronDown className='h-5 w-5' />
) : (
@ -56,28 +59,14 @@ export function MobileNav() {
onClick={() => setOpen(false)}
className='block py-1 text-base transition-colors hover:text-red-600'
>
Программа лояльности
</Link>
<Link
href='/clients/fuel-card'
onClick={() => setOpen(false)}
className='block py-1 text-base transition-colors hover:text-red-600'
>
Топливная карта
{t('common.navigation.loyatly')}
</Link>
<Link
href='/clients/certificates'
onClick={() => setOpen(false)}
className='block py-1 text-base transition-colors hover:text-red-600'
>
Сертификаты
</Link>
<Link
href='/clients/payment'
onClick={() => setOpen(false)}
className='block py-1 text-base transition-colors hover:text-red-600'
>
Способы оплаты
{t('common.navigation.certificates')}
</Link>
</CollapsibleContent>
</Collapsible>
@ -87,35 +76,35 @@ export function MobileNav() {
onClick={() => setOpen(false)}
className='text-lg font-medium transition-colors hover:text-red-600'
>
Наши заправки
{t('common.navigation.stations')}
</Link>
<Link
href='/#vacancies'
onClick={() => setOpen(false)}
className='text-lg font-medium transition-colors hover:text-red-600'
>
Вакансии
{t('common.navigation.vacancies')}
</Link>
<Link
href='/#promotions'
onClick={() => setOpen(false)}
className='text-lg font-medium transition-colors hover:text-red-600'
>
Акции
{t('common.navigation.promotions')}
</Link>
<Link
href='/#partners'
onClick={() => setOpen(false)}
className='text-lg font-medium transition-colors hover:text-red-600'
>
Партнеры
{t('common.navigation.partners')}
</Link>
<Link
href='/#charity'
onClick={() => setOpen(false)}
className='text-lg font-medium transition-colors hover:text-red-600'
>
Благотворительность
{t('common.navigation.charity')}
</Link>
</nav>
</SheetContent>

View File

@ -4,11 +4,11 @@ import { MapPin } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
export const HeroSection = () => {
const { t } = useLanguage();
const { t } = useTextController();
return (
<section className='relative'>

View File

@ -8,14 +8,14 @@ import { GasStationMap } from '@/features/map';
import { Point } from '@/features/map/model';
import { YandexMap } from '@/features/map/ui/yandex-map';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
interface MapSectionProps {
stations: Stations;
}
export const MapSection = ({ stations }: MapSectionProps) => {
const { t } = useLanguage();
const { t } = useTextController();
const points = stations.map((st) => ({
id: st.id,

View File

@ -6,7 +6,7 @@ import Link from 'next/link';
import { Partners } from '@/app/api-utlities/@types/main';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
interface PartnersSectionProps {
@ -14,7 +14,7 @@ interface PartnersSectionProps {
}
export const PartnersSection = ({ partners }: PartnersSectionProps) => {
const { t } = useLanguage();
const { t } = useTextController();
return (
<section id='partners' className='bg-gray-50 px-2 py-8 sm:py-16'>

View File

@ -5,14 +5,14 @@ import { Gift } from 'lucide-react';
import { Discounts } from '@/app/api-utlities/@types/main';
import PromotionSlider from '@/shared/components/promotion-slider';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
interface PromotionsSectionProps {
discounts: Discounts;
}
export const PromotionsSection = ({ discounts }: PromotionsSectionProps) => {
const { t } = useLanguage();
const { t } = useTextController();
return (
<section id='promotions' className='bg-gray-50 px-2 py-8 sm:py-16'>

View File

@ -2,14 +2,15 @@
import { useEffect, useRef, useState } from 'react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import AnimatedCounter from '../shared/components/animated-counter';
import { useLanguage } from '@/shared/language';
export function StatsSection() {
const [isVisible, setIsVisible] = useState(false);
const sectionRef = useRef<HTMLDivElement>(null);
const { t } = useLanguage()
const { t } = useTextController();
useEffect(() => {
const observer = new IntersectionObserver(
@ -35,9 +36,12 @@ export function StatsSection() {
}, []);
return (
<section ref={sectionRef} className='bg-red-600 py-6 sm:py-12 px-2 text-white'>
<section
ref={sectionRef}
className='bg-red-600 px-2 py-6 text-white sm:py-12'
>
<div className='container mx-auto'>
<div className='grid grid-cols-2 gap-4 sm:gap-8 text-center md:grid-cols-4'>
<div className='grid grid-cols-2 gap-4 text-center sm:gap-8 md:grid-cols-4'>
<div className='space-y-2'>
<h3 className='text-3xl font-bold'>
{isVisible ? <AnimatedCounter end={25} suffix='+' /> : '0+'}

View File

@ -5,6 +5,7 @@ import { ru } from 'date-fns/locale';
import { CalendarIcon } from 'lucide-react';
import { useState } from 'react';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import { Calendar } from '@/shared/shadcn-ui/calendar';
import { Label } from '@/shared/shadcn-ui/label';
@ -99,15 +100,21 @@ export const TransactionsTable = () => {
setFilteredTransactions(filtered);
};
const { t } = useTextController();
return (
<div className='space-y-6'>
<div className='flex flex-col items-start justify-between gap-4 md:flex-row md:items-center'>
<h2 className='text-2xl font-bold'>История операций</h2>
<h2 className='text-2xl font-bold'>
{t('corporate.transactions.title')}
</h2>
<div className='flex w-full flex-col gap-4 md:w-auto md:flex-row'>
<div className='grid grid-cols-2 gap-2'>
<div className='flex items-center gap-2'>
<Label htmlFor='start-date'>От</Label>
<Label htmlFor='start-date'>
{t('corporate.transactions.dateFrom')}
</Label>
<Popover>
<PopoverTrigger asChild>
<Button
@ -132,7 +139,9 @@ export const TransactionsTable = () => {
</div>
<div className='flex items-center gap-2'>
<Label htmlFor='end-date'>До</Label>
<Label htmlFor='end-date'>
{t('corporate.transactions.dateTo')}
</Label>
<Popover>
<PopoverTrigger asChild>
<Button
@ -142,7 +151,7 @@ export const TransactionsTable = () => {
<CalendarIcon className='mr-2 h-4 w-4' />
{endDate
? format(endDate, 'PP', { locale: ru })
: 'Выберите дату'}
: t('corporate.transactions.selectDate')}
</Button>
</PopoverTrigger>
<PopoverContent className='w-auto p-0'>
@ -161,7 +170,7 @@ export const TransactionsTable = () => {
className='mt-auto bg-red-600 hover:bg-red-700'
onClick={filterTransactions}
>
Применить
{t('corporate.transactions.applyButton')}
</Button>
</div>
</div>
@ -170,12 +179,24 @@ export const TransactionsTable = () => {
<Table>
<TableHeader>
<TableRow>
<TableHead>Дата</TableHead>
<TableHead>Станция</TableHead>
<TableHead>Продукт</TableHead>
<TableHead className='text-right'>Кол-во (л)</TableHead>
<TableHead className='text-right'>Стоимость</TableHead>
<TableHead className='text-right'>Сумма</TableHead>
<TableHead>
{t('corporate.transactions.tableHeaders.date')}
</TableHead>
<TableHead>
{t('corporate.transactions.tableHeaders.station')}
</TableHead>
<TableHead>
{t('corporate.transactions.tableHeaders.product')}
</TableHead>
<TableHead className='text-right'>
{t('corporate.transactions.tableHeaders.quantity')}
</TableHead>
<TableHead className='text-right'>
{t('corporate.transactions.tableHeaders.price')}
</TableHead>
<TableHead className='text-right'>
{t('corporate.transactions.tableHeaders.total')}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@ -191,10 +212,10 @@ export const TransactionsTable = () => {
{transaction.quantity}
</TableCell>
<TableCell className='text-right'>
{transaction.cost.toFixed(2)} сомони
{transaction.cost.toFixed(2)} {t('corporate.currency')}
</TableCell>
<TableCell className='text-right font-medium'>
{transaction.total.toFixed(2)} сомони
{transaction.total.toFixed(2)} {t('corporate.currency')}
</TableCell>
</TableRow>
))
@ -204,7 +225,7 @@ export const TransactionsTable = () => {
colSpan={6}
className='py-6 text-center text-gray-500'
>
Нет операций за выбранный период
{t('corporate.transactions.noTransactions')}
</TableCell>
</TableRow>
)}

View File

@ -4,7 +4,7 @@ import { Briefcase } from 'lucide-react';
import { Jobs } from '@/app/api-utlities/@types/main';
import { useLanguage } from '@/shared/language';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { cn } from '@/shared/lib/utils';
import { Badge } from '@/shared/shadcn-ui/badge';
import { Button } from '@/shared/shadcn-ui/button';
@ -21,7 +21,7 @@ interface VacanciesSectionProps {
}
export const VacanciesSection = ({ jobs }: VacanciesSectionProps) => {
const { t } = useLanguage();
const { t } = useTextController();
const jobsByType = new Map();
@ -31,6 +31,8 @@ export const VacanciesSection = ({ jobs }: VacanciesSectionProps) => {
});
const allVacancies = t('home.vacancies.all');
const officeVacancies = t('home.vacancies.office');
const stationsVacancies = t('home.vacancies.stations');
const jobsTabsTitle = [allVacancies, ...Array.from(jobsByType.keys())];
@ -51,11 +53,15 @@ export const VacanciesSection = ({ jobs }: VacanciesSectionProps) => {
<Tabs defaultValue={allVacancies} className='mx-auto w-full max-w-3xl'>
<TabsList className='mb-8 grid grid-cols-3'>
{jobsTabsTitle.map((type) => (
<TabsTrigger key={type} value={type}>
{type}
<TabsTrigger value={allVacancies}>
{t('home.vacancies.all')}
</TabsTrigger>
<TabsTrigger value={officeVacancies}>
{t('home.vacancies.office')}
</TabsTrigger>
<TabsTrigger value={stationsVacancies}>
{t('home.vacancies.stations')}
</TabsTrigger>
))}
</TabsList>
<TabsContent value={allVacancies} className='space-y-4'>
@ -94,7 +100,7 @@ interface VacancyProps {
}
const Vacancy = ({ jobTitle, location, tags }: VacancyProps) => {
const { t } = useLanguage();
const { t } = useTextController();
return (
<Card