Merge pull request 'render-main-page-data' (#8) from render-main-page-data into dev
Reviewed-on: #8
This commit is contained in:
commit
ff9ac551bf
@ -1,7 +1,15 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from 'next';
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'media.bambooapp.ai',
|
||||||
|
pathname: '/files/**',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
|
"@pbe/react-yandex-maps": "^1.2.5",
|
||||||
"@radix-ui/react-collapsible": "^1.1.8",
|
"@radix-ui/react-collapsible": "^1.1.8",
|
||||||
"@radix-ui/react-dialog": "^1.1.11",
|
"@radix-ui/react-dialog": "^1.1.11",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.11",
|
"@radix-ui/react-dropdown-menu": "^2.1.11",
|
||||||
|
|||||||
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
@ -11,6 +11,9 @@ importers:
|
|||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1(react-hook-form@7.56.1(react@19.1.0))
|
version: 5.0.1(react-hook-form@7.56.1(react@19.1.0))
|
||||||
|
'@pbe/react-yandex-maps':
|
||||||
|
specifier: ^1.2.5
|
||||||
|
version: 1.2.5(react@19.1.0)
|
||||||
'@radix-ui/react-collapsible':
|
'@radix-ui/react-collapsible':
|
||||||
specifier: ^1.1.8
|
specifier: ^1.1.8
|
||||||
version: 1.1.8(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 1.1.8(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
@ -526,6 +529,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
||||||
engines: {node: '>=12.4.0'}
|
engines: {node: '>=12.4.0'}
|
||||||
|
|
||||||
|
'@pbe/react-yandex-maps@1.2.5':
|
||||||
|
resolution: {integrity: sha512-cBojin5e1fPx9XVCAqHQJsCnHGMeBNsP0TrNfpWCrPFfxb30ye+JgcGr2mn767Gbr1d+RufBLRiUcX2kaiAwjQ==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.x || ^17.x || ^18.x
|
||||||
|
|
||||||
'@pkgr/core@0.2.4':
|
'@pkgr/core@0.2.4':
|
||||||
resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==}
|
resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==}
|
||||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||||
@ -1115,6 +1124,9 @@ packages:
|
|||||||
'@types/use-sync-external-store@0.0.6':
|
'@types/use-sync-external-store@0.0.6':
|
||||||
resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
|
resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
|
||||||
|
|
||||||
|
'@types/yandex-maps@2.1.29':
|
||||||
|
resolution: {integrity: sha512-nuibRWj3RU/9KXlCzTrRtDE+n6V9l7NbT9JakicqZ5OXIdwyb6blvV2Uwn6lB58WYm3DSUDP2I2AWlnWMc8z2w==}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.30.1':
|
'@typescript-eslint/eslint-plugin@8.30.1':
|
||||||
resolution: {integrity: sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==}
|
resolution: {integrity: sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@ -3047,6 +3059,11 @@ snapshots:
|
|||||||
|
|
||||||
'@nolyfill/is-core-module@1.0.39': {}
|
'@nolyfill/is-core-module@1.0.39': {}
|
||||||
|
|
||||||
|
'@pbe/react-yandex-maps@1.2.5(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@types/yandex-maps': 2.1.29
|
||||||
|
react: 19.1.0
|
||||||
|
|
||||||
'@pkgr/core@0.2.4': {}
|
'@pkgr/core@0.2.4': {}
|
||||||
|
|
||||||
'@radix-ui/number@1.1.1': {}
|
'@radix-ui/number@1.1.1': {}
|
||||||
@ -3619,6 +3636,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/use-sync-external-store@0.0.6': {}
|
'@types/use-sync-external-store@0.0.6': {}
|
||||||
|
|
||||||
|
'@types/yandex-maps@2.1.29': {}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.30.1(@typescript-eslint/parser@8.30.1(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3)':
|
'@typescript-eslint/eslint-plugin@8.30.1(@typescript-eslint/parser@8.30.1(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.1
|
'@eslint-community/regexpp': 4.12.1
|
||||||
@ -4194,7 +4213,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import-x@4.10.6(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3))(eslint-plugin-import@2.31.0)(eslint@9.25.0(jiti@2.4.2)))(eslint@9.25.0(jiti@2.4.2)):
|
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0)(eslint@9.25.0(jiti@2.4.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@ -4236,7 +4255,7 @@ snapshots:
|
|||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 9.25.0(jiti@2.4.2)
|
eslint: 9.25.0(jiti@2.4.2)
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import-x@4.10.6(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3))(eslint-plugin-import@2.31.0)(eslint@9.25.0(jiti@2.4.2)))(eslint@9.25.0(jiti@2.4.2))
|
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0)(eslint@9.25.0(jiti@2.4.2))
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export type Station = Root<{
|
|||||||
_propanCopy: boolean;
|
_propanCopy: boolean;
|
||||||
_zaryadnayaStanci: boolean;
|
_zaryadnayaStanci: boolean;
|
||||||
_miniMarketCop: boolean;
|
_miniMarketCop: boolean;
|
||||||
_region: Select;
|
_region: Select[];
|
||||||
_foto: Image[];
|
_foto: Image[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|||||||
18
src/app/api-utlities/@types/main.ts
Normal file
18
src/app/api-utlities/@types/main.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import {
|
||||||
|
presentDiscounts,
|
||||||
|
presentJobs,
|
||||||
|
presentPartners,
|
||||||
|
presentStations,
|
||||||
|
} from '../presenters';
|
||||||
|
|
||||||
|
export type Partners = ReturnType<typeof presentPartners>;
|
||||||
|
export type Jobs = ReturnType<typeof presentJobs>;
|
||||||
|
export type Discounts = ReturnType<typeof presentDiscounts>;
|
||||||
|
export type Stations = ReturnType<typeof presentStations>;
|
||||||
|
|
||||||
|
export type MainPageData = {
|
||||||
|
discounts: Discounts;
|
||||||
|
jobs: Jobs;
|
||||||
|
partners: Partners;
|
||||||
|
stations: Stations;
|
||||||
|
};
|
||||||
@ -19,13 +19,15 @@ export const presentSelect = (selectItems: Select[]) =>
|
|||||||
!isEmpty(selectItems) ? selectItems[0].name : null;
|
!isEmpty(selectItems) ? selectItems[0].name : null;
|
||||||
|
|
||||||
export const presentPartners = (partners: Partner) =>
|
export const presentPartners = (partners: Partner) =>
|
||||||
partners.records.map((record) => ({
|
partners.records.map((record, index) => ({
|
||||||
|
id: index + 1,
|
||||||
name: record._name,
|
name: record._name,
|
||||||
poster: presentImage(record._image),
|
poster: presentImage(record._image),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const presentJobs = (jobs: Job) =>
|
export const presentJobs = (jobs: Job) =>
|
||||||
jobs.records.map((job) => ({
|
jobs.records.map((job, index) => ({
|
||||||
|
id: index + 1,
|
||||||
name: job._name,
|
name: job._name,
|
||||||
tags: job._tags.map((tag) => tag.name),
|
tags: job._tags.map((tag) => tag.name),
|
||||||
location: presentSelect(job._localtio),
|
location: presentSelect(job._localtio),
|
||||||
@ -47,7 +49,8 @@ export const presentHistoryItems = (historyItems: History) =>
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const presentDiscounts = (discounts: Discount) =>
|
export const presentDiscounts = (discounts: Discount) =>
|
||||||
discounts.records.map((discount) => ({
|
discounts.records.map((discount, index) => ({
|
||||||
|
id: index + 1,
|
||||||
name: discount._name,
|
name: discount._name,
|
||||||
description: discount._opisanie,
|
description: discount._opisanie,
|
||||||
expiresAt: discount._do,
|
expiresAt: discount._do,
|
||||||
@ -55,7 +58,8 @@ export const presentDiscounts = (discounts: Discount) =>
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const presentStations = (stations: Station) =>
|
export const presentStations = (stations: Station) =>
|
||||||
stations.records.map((station: any) => ({
|
stations.records.map((station, index) => ({
|
||||||
|
id: index + 1,
|
||||||
name: station._name,
|
name: station._name,
|
||||||
description: station._opisanie,
|
description: station._opisanie,
|
||||||
address: station._adress,
|
address: station._adress,
|
||||||
|
|||||||
@ -8,16 +8,23 @@ import { PromotionsSection } from '@/widgets/promotions-section';
|
|||||||
import { StatsSection } from '@/widgets/stats-section';
|
import { StatsSection } from '@/widgets/stats-section';
|
||||||
import { VacanciesSection } from '@/widgets/vacancies-section';
|
import { VacanciesSection } from '@/widgets/vacancies-section';
|
||||||
|
|
||||||
export default function Home() {
|
import { MainPageData } from './api-utlities/@types/main';
|
||||||
|
|
||||||
|
export default async function Home() {
|
||||||
|
const mainPageData = (await fetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_BASE_URL}/api/pages/main`,
|
||||||
|
{ method: 'GET' },
|
||||||
|
).then((res) => res.json())) as MainPageData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
<StatsSection />
|
<StatsSection />
|
||||||
<MapSection />
|
<MapSection stations={mainPageData.stations} />
|
||||||
<AboutSection />
|
<AboutSection />
|
||||||
<PromotionsSection />
|
<PromotionsSection discounts={mainPageData.discounts} />
|
||||||
<VacanciesSection />
|
<VacanciesSection jobs={mainPageData.jobs} />
|
||||||
<PartnersSection />
|
<PartnersSection partners={mainPageData.partners} />
|
||||||
<CharitySection />
|
<CharitySection />
|
||||||
<CtaSection />
|
<CtaSection />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
4
src/features/map/model/index.ts
Normal file
4
src/features/map/model/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type Point = {
|
||||||
|
id: number;
|
||||||
|
coordinates: [number, number];
|
||||||
|
};
|
||||||
@ -10,6 +10,8 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { Stations } from '@/app/api-utlities/@types/main';
|
||||||
|
|
||||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||||
import { Badge } from '@/shared/shadcn-ui/badge';
|
import { Badge } from '@/shared/shadcn-ui/badge';
|
||||||
import { Button } from '@/shared/shadcn-ui/button';
|
import { Button } from '@/shared/shadcn-ui/button';
|
||||||
@ -168,7 +170,13 @@ const allFilters = [
|
|||||||
// Extract unique cities from stations
|
// Extract unique cities from stations
|
||||||
const allCities = [...new Set(stations.map((station) => station.city))].sort();
|
const allCities = [...new Set(stations.map((station) => station.city))].sort();
|
||||||
|
|
||||||
export default function GasStationMap() {
|
interface GasStationMapProps {
|
||||||
|
stations: Stations;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GasStationMap({
|
||||||
|
stations: _stations,
|
||||||
|
}: GasStationMapProps) {
|
||||||
const { t } = useTextController();
|
const { t } = useTextController();
|
||||||
const mapRef = useRef<HTMLDivElement>(null);
|
const mapRef = useRef<HTMLDivElement>(null);
|
||||||
const [activeFilters, setActiveFilters] = useState<string[]>([]);
|
const [activeFilters, setActiveFilters] = useState<string[]>([]);
|
||||||
|
|||||||
38
src/features/map/ui/yandex-map.tsx
Normal file
38
src/features/map/ui/yandex-map.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Map, Placemark, YMaps } from '@pbe/react-yandex-maps';
|
||||||
|
|
||||||
|
import { Point } from '../model';
|
||||||
|
|
||||||
|
type YandexMapProps = {
|
||||||
|
points: Point[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const YandexMap = ({ points }: YandexMapProps) => {
|
||||||
|
return (
|
||||||
|
<YMaps
|
||||||
|
query={{
|
||||||
|
apikey: process.env.NEXT_PUBLIC_YANDEX_MAP_API_KEY,
|
||||||
|
lang: 'ru_RU',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Map
|
||||||
|
defaultState={{
|
||||||
|
center: points[0].coordinates || [55.751574, 37.573856], // центр карты,
|
||||||
|
zoom: 11,
|
||||||
|
}}
|
||||||
|
className='rounded-md shadow-lg'
|
||||||
|
options={{
|
||||||
|
suppressMapOpenBlock: true,
|
||||||
|
suppressObsoleteBrowserNotifier: true,
|
||||||
|
}}
|
||||||
|
width={'100%'}
|
||||||
|
height={'500px'}
|
||||||
|
>
|
||||||
|
{points.map((point) => (
|
||||||
|
<Placemark key={point.id} geometry={point.coordinates} />
|
||||||
|
))}
|
||||||
|
</Map>
|
||||||
|
</YMaps>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -5,6 +5,8 @@ import Image from 'next/image';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { Discounts } from '@/app/api-utlities/@types/main';
|
||||||
|
|
||||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||||
import { Button } from '@/shared/shadcn-ui/button';
|
import { Button } from '@/shared/shadcn-ui/button';
|
||||||
import { Card, CardContent } from '@/shared/shadcn-ui/card';
|
import { Card, CardContent } from '@/shared/shadcn-ui/card';
|
||||||
@ -41,7 +43,11 @@ const promotions = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function PromotionSlider() {
|
interface PromotionSliderProps {
|
||||||
|
discounts: Discounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PromotionSlider({ discounts }: PromotionSliderProps) {
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
const [visibleItems, setVisibleItems] = useState(3);
|
const [visibleItems, setVisibleItems] = useState(3);
|
||||||
|
|
||||||
@ -85,7 +91,7 @@ export default function PromotionSlider() {
|
|||||||
transform: `translateX(-${currentIndex * (100 / visibleItems)}%)`,
|
transform: `translateX(-${currentIndex * (100 / visibleItems)}%)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{promotions.map((promo) => (
|
{discounts.map((promo) => (
|
||||||
<div
|
<div
|
||||||
key={promo.id}
|
key={promo.id}
|
||||||
className='w-full flex-none p-2 sm:w-1/2 lg:w-1/3'
|
className='w-full flex-none p-2 sm:w-1/2 lg:w-1/3'
|
||||||
@ -96,19 +102,21 @@ export default function PromotionSlider() {
|
|||||||
<div className='relative h-48'>
|
<div className='relative h-48'>
|
||||||
<Image
|
<Image
|
||||||
src={promo.image || '/placeholder.svg'}
|
src={promo.image || '/placeholder.svg'}
|
||||||
alt={promo.title}
|
alt={promo.name}
|
||||||
fill
|
fill
|
||||||
className='object-cover'
|
className='object-cover'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CardContent className='p-4'>
|
<CardContent className='p-4'>
|
||||||
<h3 className='mb-2 text-lg font-bold'>{promo.title}</h3>
|
<h3 className='mb-2 text-lg font-bold'>{promo.name}</h3>
|
||||||
<p className='mb-3 text-sm text-gray-600'>
|
<p className='mb-3 text-sm text-gray-600'>
|
||||||
{promo.description}
|
{promo.description}
|
||||||
</p>
|
</p>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<span className='text-xs text-gray-500'>
|
<span className='text-xs text-gray-500'>
|
||||||
Действует до: {promo.validUntil}
|
{promo.expiresAt
|
||||||
|
? `Действует до: ${promo.expiresAt}`
|
||||||
|
: null}
|
||||||
</span>
|
</span>
|
||||||
<Link href='#'>
|
<Link href='#'>
|
||||||
<Button
|
<Button
|
||||||
@ -125,6 +133,8 @@ export default function PromotionSlider() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{discounts.length > 3 && (
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant='outline'
|
variant='outline'
|
||||||
size='icon'
|
size='icon'
|
||||||
@ -143,6 +153,8 @@ export default function PromotionSlider() {
|
|||||||
<ChevronRight className='h-4 w-4' />
|
<ChevronRight className='h-4 w-4' />
|
||||||
<span className='sr-only'>Следующий</span>
|
<span className='sr-only'>Следующий</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,9 +20,9 @@ export function TextControlProvider({
|
|||||||
textItems,
|
textItems,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
textItems: TextItem[];
|
textItems?: TextItem[];
|
||||||
}) {
|
}) {
|
||||||
const textMap = textItems.reduce(
|
const textMap = textItems?.reduce(
|
||||||
(pr, cr) => {
|
(pr, cr) => {
|
||||||
pr[cr.key] = cr.value;
|
pr[cr.key] = cr.value;
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ export function TextControlProvider({
|
|||||||
|
|
||||||
// Translation function for flat structure
|
// Translation function for flat structure
|
||||||
const t = (key: string): string => {
|
const t = (key: string): string => {
|
||||||
if (textMap[key]) {
|
if (textMap?.[key]) {
|
||||||
return textMap[key];
|
return textMap[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,16 +2,30 @@
|
|||||||
|
|
||||||
import { MapPin } from 'lucide-react';
|
import { MapPin } from 'lucide-react';
|
||||||
|
|
||||||
|
import { Stations } from '@/app/api-utlities/@types/main';
|
||||||
|
|
||||||
import { GasStationMap } from '@/features/map';
|
import { GasStationMap } from '@/features/map';
|
||||||
|
import { Point } from '@/features/map/model';
|
||||||
|
import { YandexMap } from '@/features/map/ui/yandex-map';
|
||||||
|
|
||||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||||
|
|
||||||
export const MapSection = () => {
|
interface MapSectionProps {
|
||||||
|
stations: Stations;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MapSection = ({ stations }: MapSectionProps) => {
|
||||||
const { t } = useTextController();
|
const { t } = useTextController();
|
||||||
|
|
||||||
|
const points = stations.map((st) => ({
|
||||||
|
id: st.id,
|
||||||
|
coordinates: [st.latitude, st.longitude],
|
||||||
|
})) as Point[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id='stations' className='bg-gray-50 px-2 py-8 sm:py-16'>
|
<section id='stations' className='bg-gray-50 px-2 py-8 sm:py-16'>
|
||||||
<div className='container mx-auto'>
|
<div className='container mx-auto'>
|
||||||
|
<YandexMap points={points} />
|
||||||
<div className='mb-12 flex flex-col items-center justify-center text-center'>
|
<div className='mb-12 flex flex-col items-center justify-center text-center'>
|
||||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||||
<MapPin className='h-6 w-6 text-red-600' />
|
<MapPin className='h-6 w-6 text-red-600' />
|
||||||
@ -27,7 +41,7 @@ export const MapSection = () => {
|
|||||||
className='h-[500px] overflow-hidden rounded-xl border shadow-lg'
|
className='h-[500px] overflow-hidden rounded-xl border shadow-lg'
|
||||||
data-aos='fade-up'
|
data-aos='fade-up'
|
||||||
>
|
>
|
||||||
<GasStationMap />
|
{/* <GasStationMap stations={stations} /> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -4,10 +4,16 @@ import { Handshake } from 'lucide-react';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import { Partners } from '@/app/api-utlities/@types/main';
|
||||||
|
|
||||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||||
import { Button } from '@/shared/shadcn-ui/button';
|
import { Button } from '@/shared/shadcn-ui/button';
|
||||||
|
|
||||||
export const PartnersSection = () => {
|
interface PartnersSectionProps {
|
||||||
|
partners: Partners;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PartnersSection = ({ partners }: PartnersSectionProps) => {
|
||||||
const { t } = useTextController();
|
const { t } = useTextController();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -26,20 +32,23 @@ export const PartnersSection = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='grid grid-cols-2 gap-4 sm:gap-8 md:grid-cols-4'>
|
<div className='grid grid-cols-2 gap-4 sm:gap-8 md:grid-cols-4'>
|
||||||
{[1, 2, 3, 4, 5, 6, 7, 8].map((partner) => (
|
{partners.map(({ id, name, poster }) => (
|
||||||
<div
|
<div
|
||||||
key={partner}
|
key={id}
|
||||||
className='flex h-32 flex-col items-center justify-center gap-0.5 rounded-lg bg-white p-6 shadow-md transition-transform hover:scale-105'
|
className='flex h-32 flex-col items-center justify-center gap-0.5 rounded-lg bg-white p-6 shadow-md transition-transform hover:scale-105'
|
||||||
data-aos='flip-left'
|
data-aos='flip-left'
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={`/placeholder.svg?height=80&width=160&text=Partner ${partner}`}
|
src={
|
||||||
alt={`Partner ${partner}`}
|
poster ??
|
||||||
|
`/placeholder.svg?height=80&width=160&text=Partner ${id}`
|
||||||
|
}
|
||||||
|
alt={`Partner ${id}`}
|
||||||
width={160}
|
width={160}
|
||||||
height={80}
|
height={80}
|
||||||
className='max-h-16 w-auto'
|
className='max-h-16 w-auto'
|
||||||
/>
|
/>
|
||||||
<h4 className='font-extralight'>Название</h4>
|
<h4 className='font-extralight'>{name}</h4>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,10 +2,16 @@
|
|||||||
|
|
||||||
import { Gift } from 'lucide-react';
|
import { Gift } from 'lucide-react';
|
||||||
|
|
||||||
|
import { Discounts } from '@/app/api-utlities/@types/main';
|
||||||
|
|
||||||
import PromotionSlider from '@/shared/components/promotion-slider';
|
import PromotionSlider from '@/shared/components/promotion-slider';
|
||||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||||
|
|
||||||
export const PromotionsSection = () => {
|
interface PromotionsSectionProps {
|
||||||
|
discounts: Discounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PromotionsSection = ({ discounts }: PromotionsSectionProps) => {
|
||||||
const { t } = useTextController();
|
const { t } = useTextController();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -22,7 +28,7 @@ export const PromotionsSection = () => {
|
|||||||
{t('home.promotions.description')}
|
{t('home.promotions.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<PromotionSlider />
|
<PromotionSlider discounts={discounts} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { Briefcase } from 'lucide-react';
|
import { Briefcase } from 'lucide-react';
|
||||||
|
|
||||||
|
import { Jobs } from '@/app/api-utlities/@types/main';
|
||||||
|
|
||||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||||
import { cn } from '@/shared/lib/utils';
|
import { cn } from '@/shared/lib/utils';
|
||||||
import { Badge } from '@/shared/shadcn-ui/badge';
|
import { Badge } from '@/shared/shadcn-ui/badge';
|
||||||
@ -14,9 +16,26 @@ import {
|
|||||||
TabsTrigger,
|
TabsTrigger,
|
||||||
} from '@/shared/shadcn-ui/tabs';
|
} from '@/shared/shadcn-ui/tabs';
|
||||||
|
|
||||||
export const VacanciesSection = () => {
|
interface VacanciesSectionProps {
|
||||||
|
jobs: Jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VacanciesSection = ({ jobs }: VacanciesSectionProps) => {
|
||||||
const { t } = useTextController();
|
const { t } = useTextController();
|
||||||
|
|
||||||
|
const jobsByType = new Map();
|
||||||
|
|
||||||
|
jobs.forEach((job) => {
|
||||||
|
const existing = jobsByType.get(job.type) || [];
|
||||||
|
jobsByType.set(job.type, [...existing, job]);
|
||||||
|
});
|
||||||
|
|
||||||
|
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())];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id='vacancies' className='px-2 py-8 sm:py-16'>
|
<section id='vacancies' className='px-2 py-8 sm:py-16'>
|
||||||
<div className='container mx-auto'>
|
<div className='container mx-auto'>
|
||||||
@ -32,57 +51,42 @@ export const VacanciesSection = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs defaultValue='all' className='mx-auto w-full max-w-3xl'>
|
<Tabs defaultValue={allVacancies} className='mx-auto w-full max-w-3xl'>
|
||||||
<TabsList className='mb-8 grid grid-cols-3'>
|
<TabsList className='mb-8 grid grid-cols-3'>
|
||||||
<TabsTrigger value='all'>{t('home.vacancies.all')}</TabsTrigger>
|
<TabsTrigger value={allVacancies}>
|
||||||
<TabsTrigger value='office'>
|
{t('home.vacancies.all')}
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value={officeVacancies}>
|
||||||
{t('home.vacancies.office')}
|
{t('home.vacancies.office')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value='stations'>
|
<TabsTrigger value={stationsVacancies}>
|
||||||
{t('home.vacancies.stations')}
|
{t('home.vacancies.stations')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value='all' className='space-y-4'>
|
|
||||||
{[
|
<TabsContent value={allVacancies} className='space-y-4'>
|
||||||
'Оператор АЗС',
|
{jobs.map((job, index) => (
|
||||||
'Менеджер по продажам',
|
|
||||||
'Бухгалтер',
|
|
||||||
'Специалист по логистике',
|
|
||||||
].map((job, index) => (
|
|
||||||
<Vacancy
|
<Vacancy
|
||||||
key={index}
|
key={index}
|
||||||
jobTitle={job}
|
jobTitle={job.name}
|
||||||
location='Душанбе, Таджикистан'
|
location={job.location ?? ''}
|
||||||
tags={['Полный день', 'Опыт от 1 года']}
|
tags={job.tags}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value='office' className='space-y-4'>
|
|
||||||
{[
|
{Array.from(jobsByType.entries()).map(([type, jobs]) => (
|
||||||
'Менеджер по продажам',
|
<TabsContent key={type} value={type} className='space-y-4'>
|
||||||
'Бухгалтер',
|
{jobs.map((job: Jobs[number], index: number) => (
|
||||||
'Специалист по логистике',
|
|
||||||
].map((job, index) => (
|
|
||||||
<Vacancy
|
<Vacancy
|
||||||
key={index}
|
key={index}
|
||||||
jobTitle={job}
|
jobTitle={job.name}
|
||||||
location='Душанбе, Таджикистан'
|
location={job.location ?? ''}
|
||||||
tags={['Полный день', 'Опыт от 1 года']}
|
tags={job.tags}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value='stations' className='space-y-4'>
|
))}
|
||||||
{['Оператор АЗС', 'Заправщик', 'Менеджер станции'].map(
|
|
||||||
(job, index) => (
|
|
||||||
<Vacancy
|
|
||||||
key={index}
|
|
||||||
jobTitle={job}
|
|
||||||
location='Душанбе, Таджикистан'
|
|
||||||
tags={['Сменный график', 'Обучение']}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user