feat: add media provider and get madia from use-media-controller hook

This commit is contained in:
BunyodL 2025-05-10 00:29:02 +05:00
parent 39bb647b5c
commit 9f43dd02d7
11 changed files with 174 additions and 17 deletions

View File

@ -67,6 +67,15 @@ export type TextResponse = Root<{
_znachenie: string | null;
}>;
export type MediaResponse = Root<{
_name: string;
_foto: {
id: string;
url: string;
};
_klyuchNeIzmenya: string;
}>;
export type Team = Root<{
_foto: Image[];
_zvanie: string;

View File

@ -1,5 +1,7 @@
import { isEmpty } from 'lodash';
import { MediaItem } from '@/shared/types/media.type';
import {
Certificate,
Charity,
@ -7,6 +9,7 @@ import {
History,
Image,
Job,
MediaResponse,
Partner,
Review,
Select,
@ -88,6 +91,17 @@ export const presentTexts = (texts: TextResponse) =>
value: item._znachenie,
}));
export const presentMedia = (media: MediaResponse) => {
return media.records.map((record) => ({
key: record._klyuchNeIzmenya,
name: record._name,
photo: {
id: record._foto.id,
url: record._foto.url,
},
}));
};
export const presentReviews = (reviews: Review) =>
reviews.records.map((review) => ({
id: review.id,

View File

@ -122,6 +122,19 @@ export const textsRequest = {
},
};
export const mediaRequest = {
_mediaKontent: {
records: {
_name: true,
_foto: {
id: true,
url: true,
},
_klyuchNeIzmenya: true,
},
},
};
export const teamRequest = {
_komanda: {
records: {

View File

@ -2,8 +2,10 @@ import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
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 { makeStore } from '@/shared/store';
import { MediaItem } from '@/shared/types/media.type';
import { TextItem } from '@/shared/types/text.types';
import { Footer } from '@/widgets/footer';
@ -29,10 +31,15 @@ export default async function RootLayout({
}>) {
const store = makeStore();
const response = await store.dispatch(
// Запрос текстов
const textResponse = await store.dispatch(
textControlApi.endpoints.fetchText.initiate(),
);
// Запрос медиа
const mediaResponse = await store.dispatch(
mediaControlApi.endpoints.fetchMedia.initiate(),
);
return (
<html
lang='ru'
@ -41,7 +48,10 @@ export default async function RootLayout({
style={{ scrollBehavior: 'smooth' }}
>
<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 />
{children}
<Footer />

View File

@ -7,7 +7,9 @@ import { TextControlContext } from '../context/text-control-provider';
export function useTextController() {
const context = useContext(TextControlContext);
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') {

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._mediaKontent);
},
}),
}),
});

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) => MediaItem | undefined;
};
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): MediaItem | undefined => {
if (mediaMap?.[key]) {
return mediaMap[key];
}
console.warn(`Media key not found: ${key}`);
return undefined;
};
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 { TextControlProvider } from '../language';
import { MediaControlProvider } from '../media/context/media-control.provider';
import { store } from '../store';
import { ThemeProvider } from '../theme/theme-provider';
import { MediaItem } from '../types/media.type';
import { TextItem } from '../types/text.types';
import { AosProvider } from './aos-provider';
import { Toaster } from './toaster';
@ -13,12 +15,18 @@ import { Toaster } from './toaster';
type ProvidersProps = {
children: React.ReactNode;
textItems: TextItem[];
mediaItems: MediaItem[];
};
export const Providers = ({ children, textItems }: ProvidersProps) => {
export const Providers = ({
children,
textItems,
mediaItems,
}: ProvidersProps) => {
return (
<Provider store={store}>
<TextControlProvider textItems={textItems}>
<MediaControlProvider>
<ThemeProvider
attribute='class'
defaultTheme='light'
@ -32,6 +40,7 @@ export const Providers = ({ children, textItems }: ProvidersProps) => {
</AosProvider>
</TooltipProvider>
</ThemeProvider>
</MediaControlProvider>
</TextControlProvider>
</Provider>
);

View File

@ -0,0 +1,8 @@
export interface MediaItem {
key: string; // _klyuchNeIzmenya
name: string; // _name
photo: {
id: string;
url: string;
};
}

View File

@ -8,6 +8,7 @@ import { GasStationMap } from '@/features/map';
import { Container } from '@/shared/components/container';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { useMediaController } from '@/shared/media/hooks/use-media-controller';
interface MapSectionProps {
stations: Stations;
@ -16,6 +17,11 @@ interface MapSectionProps {
export const MapSection = ({ stations }: MapSectionProps) => {
const { t } = useTextController();
const { m } = useMediaController();
// NOTE: doesn't work
const stationsMedia = m('stations.main');
return (
<section id='stations' className='bg-gray-50'>
<Container>