Compare commits

...

7 Commits

Author SHA1 Message Date
Umar Adilov
64d2c12494 Fixed media content fetch 2025-05-14 20:08:01 +05:00
Umar Adilov
a6d836a543 Merge branch 'dev' of https://devgit.oriyo.tj/adilovcode/oriyo_next into feat-get-media 2025-05-14 20:05:00 +05:00
Umar Adilov
a2730f0b03 Integrated media content to all pages 2025-05-14 20:04:24 +05:00
BunyodL
4e54957a15 Merge branch 'dev' into feat-get-media 2025-05-10 18:04:17 +05:00
BunyodL
3367b38f23 update: set media from taylor-db 2025-05-10 18:01:43 +05:00
Umar Adilov
c660c18d37 Fixed bug with media provider 2025-05-10 01:04:50 +05:00
BunyodL
9f43dd02d7 feat: add media provider and get madia from use-media-controller hook 2025-05-10 00:29:02 +05:00
18 changed files with 213 additions and 30 deletions

View File

@ -67,6 +67,12 @@ export type TextResponse = Root<{
znachenie: string | null; znachenie: string | null;
}>; }>;
export type MediaResponse = Root<{
mestopolozheniya: string;
foto: Image[];
klyuchneizmenyat: string;
}>;
export type Team = Root<{ export type Team = Root<{
foto: Image[]; foto: Image[];
zvanie: string; zvanie: string;

View File

@ -7,6 +7,7 @@ import {
History, History,
Image, Image,
Job, Job,
MediaResponse,
Partner, Partner,
Review, Review,
Select, Select,
@ -88,6 +89,14 @@ export const presentTexts = (texts: TextResponse) =>
value: item.znachenie, value: item.znachenie,
})); }));
export const presentMedia = (media: MediaResponse) => {
return media.records.map((record) => ({
key: record.klyuchneizmenyat,
name: record.mestopolozheniya,
photo: presentImage(record.foto),
}));
};
export const presentReviews = (reviews: Review) => export const presentReviews = (reviews: Review) =>
reviews.records.map((review) => ({ reviews.records.map((review) => ({
id: review.id, id: review.id,

View File

@ -123,6 +123,19 @@ export const textsRequest = {
}, },
}; };
export const mediaRequest = {
mediakontentsajta: {
records: {
mestopolozheniya: true,
foto: {
id: true,
url: true,
},
klyuchneizmenyat: true,
},
},
};
export const teamRequest = { export const teamRequest = {
komanda: { komanda: {
records: { records: {

View File

@ -2,8 +2,10 @@ import type { Metadata } from 'next';
import { Inter } from 'next/font/google'; import { Inter } from 'next/font/google';
import { textControlApi } from '@/shared/language/api/text-control.api'; import { textControlApi } from '@/shared/language/api/text-control.api';
import { mediaControlApi } from '@/shared/media/api/media-control.api';
import { Providers } from '@/shared/providers/providers'; import { Providers } from '@/shared/providers/providers';
import { makeStore } from '@/shared/store'; import { makeStore } from '@/shared/store';
import { MediaItem } from '@/shared/types/media.type';
import { TextItem } from '@/shared/types/text.types'; import { TextItem } from '@/shared/types/text.types';
import { Footer } from '@/widgets/footer'; import { Footer } from '@/widgets/footer';
@ -29,10 +31,15 @@ export default async function RootLayout({
}>) { }>) {
const store = makeStore(); const store = makeStore();
const response = await store.dispatch( // Запрос текстов
const textResponse = await store.dispatch(
textControlApi.endpoints.fetchText.initiate(), textControlApi.endpoints.fetchText.initiate(),
); );
// Запрос медиа
const mediaResponse = await store.dispatch(
mediaControlApi.endpoints.fetchMedia.initiate(),
);
return ( return (
<html <html
lang='ru' lang='ru'
@ -41,7 +48,10 @@ export default async function RootLayout({
style={{ scrollBehavior: 'smooth' }} style={{ scrollBehavior: 'smooth' }}
> >
<body className={`${inter.className} min-w-2xs antialiased`}> <body className={`${inter.className} min-w-2xs antialiased`}>
<Providers textItems={response.data as TextItem[]}> <Providers
textItems={textResponse.data as TextItem[]}
mediaItems={mediaResponse.data as MediaItem[]}
>
<Header /> <Header />
{children} {children}
<Footer /> <Footer />

View File

@ -12,6 +12,7 @@ import AnimatedCounter from '@/shared/components/animated-counter';
import { Container } from '@/shared/components/container'; import { Container } from '@/shared/components/container';
import { Review } from '@/shared/components/review'; import { Review } from '@/shared/components/review';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { useMediaController } from '@/shared/media/hooks/use-media-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';
@ -25,6 +26,7 @@ export interface AboutPageProps {
export default function AboutPage({ content }: AboutPageProps) { export default function AboutPage({ content }: AboutPageProps) {
const { t } = useTextController(); const { t } = useTextController();
const { m } = useMediaController();
return ( return (
<div className='flex min-h-screen flex-col'> <div className='flex min-h-screen flex-col'>
@ -33,7 +35,10 @@ export default function AboutPage({ content }: AboutPageProps) {
<section className='relative'> <section className='relative'>
<div className='relative h-[400px] w-full overflow-hidden'> <div className='relative h-[400px] w-full overflow-hidden'>
<Image <Image
src='/placeholder.svg?height=400&width=1920&text=Наша+История' src={
m('about.hero-section.banner') ||
'/placeholder.svg?height=400&width=1920&text=Наша+История'
}
alt={t('about.hero.imageAlt')} alt={t('about.hero.imageAlt')}
width={1920} width={1920}
height={400} height={400}
@ -102,7 +107,10 @@ export default function AboutPage({ content }: AboutPageProps) {
className='relative h-[500px] overflow-hidden rounded-xl shadow-xl' className='relative h-[500px] overflow-hidden rounded-xl shadow-xl'
> >
<Image <Image
src='/placeholder.svg?height=500&width=600&text=Главный+офис' src={
m('about.second-section.banner') ||
'/placeholder.svg?height=500&width=600&text=Главный+офис'
}
alt={t('about.overview.imageAlt')} alt={t('about.overview.imageAlt')}
fill fill
className='object-cover' className='object-cover'

View File

@ -4,6 +4,7 @@ import Image from 'next/image';
import { Container } from '@/shared/components/container'; import { Container } from '@/shared/components/container';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { useMediaController } from '@/shared/media/hooks/use-media-controller';
import { BenefitsSection } from '@/widgets/clients/ui/benefits-section'; import { BenefitsSection } from '@/widgets/clients/ui/benefits-section';
import { ServicesOverviewSection } from '@/widgets/clients/ui/services-overview-section'; import { ServicesOverviewSection } from '@/widgets/clients/ui/services-overview-section';
@ -11,6 +12,7 @@ import { CtaSection } from '@/widgets/cta-section';
export function ClientsPage() { export function ClientsPage() {
const { t } = useTextController(); const { t } = useTextController();
const { m } = useMediaController();
return ( return (
<div className='flex min-h-screen flex-col'> <div className='flex min-h-screen flex-col'>
@ -19,7 +21,10 @@ export function ClientsPage() {
<section className='relative'> <section className='relative'>
<div className='relative h-[400px] w-full overflow-hidden'> <div className='relative h-[400px] w-full overflow-hidden'>
<Image <Image
src='/placeholder.svg?height=400&width=1920&text=Для+наших+клиентов' src={
m('clients.hero-section.banner') ||
'/placeholder.svg?height=400&width=1920&text=Для+наших+клиентов'
}
alt='Для наших клиентов' alt='Для наших клиентов'
width={1920} width={1920}
height={400} height={400}

View File

@ -5,6 +5,7 @@ import Image from 'next/image';
import { Container } from '@/shared/components/container'; import { Container } from '@/shared/components/container';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { useMediaController } from '@/shared/media/hooks/use-media-controller';
// import LoyaltyLevels from '@/widgets/clients/loyalty/ui/loyalty-levels'; // import LoyaltyLevels from '@/widgets/clients/loyalty/ui/loyalty-levels';
import { CtaSection } from '@/widgets/cta-section'; import { CtaSection } from '@/widgets/cta-section';
@ -13,6 +14,7 @@ import ProgrammImg from '../../../../public/clients/loyatly/03a771e7-5d76-4111-a
export function LoyaltyPage() { export function LoyaltyPage() {
const { t } = useTextController(); const { t } = useTextController();
const { m } = useMediaController();
return ( return (
<div className='flex min-h-screen flex-col'> <div className='flex min-h-screen flex-col'>
@ -21,7 +23,10 @@ export function LoyaltyPage() {
<section className='relative'> <section className='relative'>
<div className='relative h-[400px] w-full overflow-hidden'> <div className='relative h-[400px] w-full overflow-hidden'>
<Image <Image
src='/placeholder.svg?height=400&width=1920&text=Программа+лояльности' src={
m('loyalty.hero-section.banner') ||
'/placeholder.svg?height=400&width=1920&text=Программа+лояльности'
}
alt='Программа лояльности' alt='Программа лояльности'
width={1920} width={1920}
height={400} height={400}
@ -107,7 +112,7 @@ export function LoyaltyPage() {
className='relative h-[400px] overflow-hidden rounded-xl shadow-xl' className='relative h-[400px] overflow-hidden rounded-xl shadow-xl'
> >
<Image <Image
src={ProgrammImg} src={m('loyalty.second-section.banner') || ProgrammImg}
alt='Программа лояльности' alt='Программа лояльности'
fill fill
className='w-full object-contain p-2.5' className='w-full object-contain p-2.5'

View File

@ -1,11 +1,21 @@
'use client';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { useMediaController } from '../media/hooks/use-media-controller';
export const Logo = () => { export const Logo = () => {
const { m } = useMediaController();
return ( return (
<Link className='flex items-center gap-2' href={'/'}> <Link className='flex items-center gap-2' href={'/'}>
<Image src='/logo-new.png' alt='oriyo-logo' width={110} height={40} /> <Image
{/* <span className='text-xl font-bold'>Ориё</span> */} src={m('logo') || '/logo-new.png'}
alt='oriyo-logo'
width={110}
height={40}
/>
</Link> </Link>
); );
}; };

View File

@ -7,7 +7,9 @@ import { TextControlContext } from '../context/text-control-provider';
export function useTextController() { export function useTextController() {
const context = useContext(TextControlContext); const context = useContext(TextControlContext);
if (context === undefined) { if (context === undefined) {
throw new Error('useLanguage must be used within a LanguageProvider'); throw new Error(
'useTextController must be used within a TextControlProvider',
);
} }
if (typeof context.t !== 'function') { if (typeof context.t !== 'function') {

View File

@ -0,0 +1,24 @@
import { jsonToGraphQLQuery } from 'json-to-graphql-query';
import { presentMedia } from '@/app/api-utlities/presenters';
import { mediaRequest } from '@/app/api-utlities/requests/common';
import { taylorAPI } from '@/shared/api/taylor-api';
import { MediaItem } from '@/shared/types/media.type';
export const mediaControlApi = taylorAPI.injectEndpoints({
endpoints: (builder) => ({
fetchMedia: builder.query<MediaItem[], void>({
query: () => ({
url: '',
method: 'POST',
body: {
query: jsonToGraphQLQuery({ query: mediaRequest }),
},
}),
transformResponse: (response: any) => {
return presentMedia(response.data.mediakontentsajta);
},
}),
}),
});

View File

@ -0,0 +1,42 @@
'use client';
import { createContext, type ReactNode } from 'react';
import { MediaItem } from '@/shared/types/media.type';
export type MediaMap = Record<string, MediaItem>;
type MediaControlContextType = {
m: (key: string) => string | null;
};
export const MediaControlContext = createContext<
MediaControlContextType | undefined
>(undefined);
export function MediaControlProvider({
children,
mediaItems,
}: {
children: ReactNode;
mediaItems?: MediaItem[];
}) {
const mediaMap = mediaItems?.reduce((pr, cr) => {
pr[cr.key] = cr;
return pr;
}, {} as MediaMap);
const getMedia = (key: string): string | null => {
if (mediaMap?.[key]) {
return mediaMap[key].photo;
}
console.warn(`Media key not found: ${key}`);
return null;
};
return (
<MediaControlContext.Provider value={{ m: getMedia }}>
{children}
</MediaControlContext.Provider>
);
}

View File

@ -0,0 +1,20 @@
'use client';
import { useContext } from 'react';
import { MediaControlContext } from '../context/media-control.provider';
export function useMediaController() {
const context = useContext(MediaControlContext);
if (context === undefined) {
throw new Error(
'useMediaController must be used within a MediaControlProvider',
);
}
if (typeof context.m !== 'function') {
throw new Error('Media function (m) is not available');
}
return context;
}

View File

@ -4,8 +4,10 @@ import { TooltipProvider } from '@radix-ui/react-tooltip';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { TextControlProvider } from '../language'; import { TextControlProvider } from '../language';
import { MediaControlProvider } from '../media/context/media-control.provider';
import { store } from '../store'; import { store } from '../store';
import { ThemeProvider } from '../theme/theme-provider'; import { ThemeProvider } from '../theme/theme-provider';
import { MediaItem } from '../types/media.type';
import { TextItem } from '../types/text.types'; import { TextItem } from '../types/text.types';
import { AosProvider } from './aos-provider'; import { AosProvider } from './aos-provider';
import { Toaster } from './toaster'; import { Toaster } from './toaster';
@ -13,12 +15,18 @@ import { Toaster } from './toaster';
type ProvidersProps = { type ProvidersProps = {
children: React.ReactNode; children: React.ReactNode;
textItems: TextItem[]; textItems: TextItem[];
mediaItems: MediaItem[];
}; };
export const Providers = ({ children, textItems }: ProvidersProps) => { export const Providers = ({
children,
textItems,
mediaItems,
}: ProvidersProps) => {
return ( return (
<Provider store={store}> <Provider store={store}>
<TextControlProvider textItems={textItems}> <TextControlProvider textItems={textItems}>
<MediaControlProvider mediaItems={mediaItems}>
<ThemeProvider <ThemeProvider
attribute='class' attribute='class'
defaultTheme='light' defaultTheme='light'
@ -32,6 +40,7 @@ export const Providers = ({ children, textItems }: ProvidersProps) => {
</AosProvider> </AosProvider>
</TooltipProvider> </TooltipProvider>
</ThemeProvider> </ThemeProvider>
</MediaControlProvider>
</TextControlProvider> </TextControlProvider>
</Provider> </Provider>
); );

View File

@ -0,0 +1,5 @@
export interface MediaItem {
key: string; // _klyuchNeIzmenya
name: string; // _name
photo: string | null;
}

View File

@ -6,9 +6,11 @@ import Image from 'next/image';
import AboutCounter from '@/shared/components/about-counter'; import AboutCounter from '@/shared/components/about-counter';
import { Container } from '@/shared/components/container'; import { Container } from '@/shared/components/container';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { useMediaController } from '@/shared/media/hooks/use-media-controller';
export const AboutSection = () => { export const AboutSection = () => {
const { t } = useTextController(); const { t } = useTextController();
const { m } = useMediaController();
return ( return (
<section id='about'> <section id='about'>
@ -37,7 +39,7 @@ export const AboutSection = () => {
data-aos='zoom-in-down' data-aos='zoom-in-down'
> >
<Image <Image
src='/clients/loyatly/oriyo-price-board.png' src={m('main.price-board') || ''}
alt='About our company' alt='About our company'
fill fill
className='w-full object-contain p-2.5' className='w-full object-contain p-2.5'

View File

@ -6,10 +6,12 @@ import Link from 'next/link';
import { Container } from '@/shared/components/container'; import { Container } from '@/shared/components/container';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { useMediaController } from '@/shared/media/hooks/use-media-controller';
import { Button } from '@/shared/shadcn-ui/button'; import { Button } from '@/shared/shadcn-ui/button';
export const CharitySection = () => { export const CharitySection = () => {
const { t } = useTextController(); const { t } = useTextController();
const { m } = useMediaController();
return ( return (
<section id='charity'> <section id='charity'>
@ -20,10 +22,14 @@ export const CharitySection = () => {
data-aos='zoom-in' data-aos='zoom-in'
> >
<Image <Image
src='/placeholder.svg?height=400&width=600' src={
m('home.charity.banner') ||
'/placeholder.svg?height=400&width=600'
}
alt='Charity Foundation' alt='Charity Foundation'
fill fill
className='object-cover' className='object-cover'
loader={({ src }) => src}
/> />
</div> </div>
<div className='order-1 md:order-2'> <div className='order-1 md:order-2'>

View File

@ -5,6 +5,7 @@ import Image from 'next/image';
import { Container } from '@/shared/components/container'; import { Container } from '@/shared/components/container';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { useMediaController } from '@/shared/media/hooks/use-media-controller';
interface Benefit { interface Benefit {
title: string; title: string;
@ -32,6 +33,7 @@ const benefits: Array<Benefit> = [
export const BenefitsSection = () => { export const BenefitsSection = () => {
const { t } = useTextController(); const { t } = useTextController();
const { m } = useMediaController();
return ( return (
<section className='bg-gray-50'> <section className='bg-gray-50'>
@ -72,7 +74,10 @@ export const BenefitsSection = () => {
className='relative order-1 h-[400px] overflow-hidden rounded-xl shadow-xl md:order-2' className='relative order-1 h-[400px] overflow-hidden rounded-xl shadow-xl md:order-2'
> >
<Image <Image
src='/placeholder.svg?height=400&width=600&text=Преимущества+для+клиентов' src={
m('clients.third-section.banner') ||
'/placeholder.svg?height=400&width=600&text=Преимущества+для+клиентов'
}
alt='Преимущества для клиентов' alt='Преимущества для клиентов'
fill fill
className='object-cover' className='object-cover'

View File

@ -5,10 +5,12 @@ import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { useMediaController } from '@/shared/media/hooks/use-media-controller';
import { Button } from '@/shared/shadcn-ui/button'; import { Button } from '@/shared/shadcn-ui/button';
export const HeroSection = () => { export const HeroSection = () => {
const { t } = useTextController(); const { t } = useTextController();
const { m } = useMediaController();
return ( return (
<section className='relative'> <section className='relative'>
@ -21,13 +23,13 @@ export const HeroSection = () => {
// top: -60, // top: -60,
// right: 20, // right: 20,
}} }}
className='sm:!top-0 sm:!right-20 sm:!h-[70vh] sm:!w-[100vh]' className='sm:!-top-16 sm:-right-40 sm:!h-[70vh] sm:!w-[100vh] md:!-top-10 md:!-right-30 xl:!top-0 xl:!right-0'
> >
<Image <Image
src='/oriyo_bg.jpeg' src={m('home.hero-section.banner') || '/oriyo_bg.jpeg'}
alt='Oriyo Station' alt='Oriyo Station'
fill fill
className='object-cover' className='object-cover sm:scale-110 md:scale-120 xl:scale-140'
priority priority
/> />
</div> </div>