From 6deb48239e55b825dfd505772a87899bc365774b Mon Sep 17 00:00:00 2001
From: Umar Adilov <99314948+adilovcode@users.noreply.github.com>
Date: Thu, 1 May 2025 23:25:37 +0500
Subject: [PATCH 1/3] Fixed issue with SSR
---
src/app/about/page.tsx | 4 +-
src/app/api/bonus/info/route.ts | 2 +-
.../middlewares/error-handler.middleware.ts | 4 +-
src/app/api/pages/about-us/route.ts | 28 ---
src/app/api/pages/main/route.ts | 28 ---
src/app/api/text/route.ts | 14 --
src/app/layout.tsx | 2 +-
src/app/page.tsx | 2 +-
src/features/map/ui/gas-station-map.tsx | 2 +-
src/features/pages/api/pages.api.ts | 56 +++++-
src/pages-templates/login/index.tsx | 171 ++++++++++--------
src/shared/api/taylor-api.ts | 14 ++
src/shared/components/promotion-slider.tsx | 2 +-
src/shared/language/api/text-control.api.ts | 23 ++-
src/shared/store/root-reducer.ts | 3 +
src/widgets/about-page/company-timeline.tsx | 2 +-
src/widgets/map-section.tsx | 3 +-
src/widgets/partners-section.tsx | 2 +-
src/widgets/promotions-section.tsx | 2 +-
src/widgets/vacancies-section.tsx | 2 +-
20 files changed, 189 insertions(+), 177 deletions(-)
delete mode 100644 src/app/api/pages/about-us/route.ts
delete mode 100644 src/app/api/pages/main/route.ts
delete mode 100644 src/app/api/text/route.ts
create mode 100644 src/shared/api/taylor-api.ts
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index 7b72902..778e5c7 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -7,11 +7,11 @@ import { makeStore } from '@/shared/store';
export default async function About() {
const store = makeStore();
- const { data, isLoading } = await store.dispatch(
+ const { data } = await store.dispatch(
mainPageApi.endpoints.fetchAboutUsPageContent.initiate(),
);
- if (isLoading || !data) return null;
+ if (!data) return null;
return ;
}
diff --git a/src/app/api/bonus/info/route.ts b/src/app/api/bonus/info/route.ts
index efb8011..d7a8178 100644
--- a/src/app/api/bonus/info/route.ts
+++ b/src/app/api/bonus/info/route.ts
@@ -4,7 +4,7 @@ import oriyoClient from '@/app/api-utlities/utilities/oriyo.client';
import { validationErrorHandler } from '../../middlewares/error-handler.middleware';
-export const routeHandler = async (req: NextRequest) => {
+const routeHandler = async (req: NextRequest) => {
const bonusTokenData = req.cookies.get('bonus__token');
if (!bonusTokenData) {
diff --git a/src/app/api/middlewares/error-handler.middleware.ts b/src/app/api/middlewares/error-handler.middleware.ts
index a3046e8..cc02041 100644
--- a/src/app/api/middlewares/error-handler.middleware.ts
+++ b/src/app/api/middlewares/error-handler.middleware.ts
@@ -2,9 +2,9 @@ import { NextRequest, NextResponse } from 'next/server';
import { ZodError } from 'zod';
export const validationErrorHandler =
- (handler: Function) => async (req: NextRequest, res: NextResponse) => {
+ (handler: Function) => async (req: NextRequest) => {
try {
- return await handler(req, res);
+ return await handler(req);
} catch (error) {
if (error instanceof ZodError)
return NextResponse.json({ message: error.format() }, { status: 400 });
diff --git a/src/app/api/pages/about-us/route.ts b/src/app/api/pages/about-us/route.ts
deleted file mode 100644
index 31363d9..0000000
--- a/src/app/api/pages/about-us/route.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import {
- presentHistoryItems,
- presentReviews,
- presentStations,
- presentTeamMembers,
-} from '@/app/api-utlities/presenters';
-import { aboutUsPageRequest } from '@/app/api-utlities/requests/about-us-page.request';
-import { requestTaylor } from '@/app/api-utlities/utilities/taylor.client';
-
-import { validationErrorHandler } from '../../middlewares/error-handler.middleware';
-
-const routeHandler = async () => {
- const response = await requestTaylor(aboutUsPageRequest);
-
- return new Response(
- JSON.stringify({
- team: presentTeamMembers(response.data._komanda),
- history: presentHistoryItems(response.data._istoriya),
- stations: presentStations(response.data._azs),
- reviews: presentReviews(response.data._otzyvy),
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- },
- );
-};
-
-export const GET = validationErrorHandler(routeHandler);
diff --git a/src/app/api/pages/main/route.ts b/src/app/api/pages/main/route.ts
deleted file mode 100644
index deb8f1b..0000000
--- a/src/app/api/pages/main/route.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import {
- presentDiscounts,
- presentJobs,
- presentPartners,
- presentStations,
-} from '@/app/api-utlities/presenters';
-import { mainPageRequest } from '@/app/api-utlities/requests/main-page.request';
-import { requestTaylor } from '@/app/api-utlities/utilities/taylor.client';
-
-import { validationErrorHandler } from '../../middlewares/error-handler.middleware';
-
-const routeHandler = async (request: Request) => {
- const response = await requestTaylor(mainPageRequest);
-
- return new Response(
- JSON.stringify({
- partners: presentPartners(response.data._partners),
- jobs: presentJobs(response.data._vacancies),
- discounts: presentDiscounts(response.data._akcii),
- stations: presentStations(response.data._azs),
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- },
- );
-};
-
-export const GET = validationErrorHandler(routeHandler);
diff --git a/src/app/api/text/route.ts b/src/app/api/text/route.ts
deleted file mode 100644
index a5bf3df..0000000
--- a/src/app/api/text/route.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-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' },
- },
- );
-}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 6406af6..a195aa5 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -37,7 +37,7 @@ export default async function RootLayout({
className='scroll-smooth'
style={{ scrollBehavior: 'smooth' }}
>
-
+
{children}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index e66b1f8..a340dee 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -15,7 +15,7 @@ import { VacanciesSection } from '@/widgets/vacancies-section';
export default async function Home() {
const store = makeStore();
- const { data, isLoading } = await store.dispatch(
+ const { data, isLoading, error } = await store.dispatch(
mainPageApi.endpoints.fetchMainPageContent.initiate(),
);
diff --git a/src/features/map/ui/gas-station-map.tsx b/src/features/map/ui/gas-station-map.tsx
index 76ce169..5f1357e 100644
--- a/src/features/map/ui/gas-station-map.tsx
+++ b/src/features/map/ui/gas-station-map.tsx
@@ -10,7 +10,7 @@ import {
} from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
-import { Stations } from '@/app/api-utlities/@types/main';
+import { Stations } from '@/app/api-utlities/@types';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Badge } from '@/shared/shadcn-ui/badge';
diff --git a/src/features/pages/api/pages.api.ts b/src/features/pages/api/pages.api.ts
index e0f4d45..6909d11 100644
--- a/src/features/pages/api/pages.api.ts
+++ b/src/features/pages/api/pages.api.ts
@@ -1,19 +1,59 @@
+import { jsonToGraphQLQuery } from 'json-to-graphql-query';
+
import { AboutUsPageData } from '@/app/api-utlities/@types/about-us';
import { MainPageData } from '@/app/api-utlities/@types/main';
+import {
+ presentDiscounts,
+ presentHistoryItems,
+ presentJobs,
+ presentPartners,
+ presentReviews,
+ presentStations,
+ presentTeamMembers,
+} from '@/app/api-utlities/presenters';
+import { aboutUsPageRequest } from '@/app/api-utlities/requests/about-us-page.request';
+import { mainPageRequest } from '@/app/api-utlities/requests/main-page.request';
-import { baseAPI } from '@/shared/api/base-api';
+import { taylorAPI } from '@/shared/api/taylor-api';
-export const mainPageApi = baseAPI.injectEndpoints({
+export const mainPageApi = taylorAPI.injectEndpoints({
endpoints: (builder) => ({
fetchMainPageContent: builder.query({
- query: () => '/pages/main',
+ query: () => ({
+ url: '',
+ method: 'POST',
+ body: {
+ query: jsonToGraphQLQuery({ query: mainPageRequest }),
+ },
+ }),
+
+ transformResponse: (response: any) => {
+ return {
+ partners: presentPartners(response.data._partners),
+ jobs: presentJobs(response.data._vacancies),
+ discounts: presentDiscounts(response.data._akcii),
+ stations: presentStations(response.data._azs),
+ };
+ },
}),
- fetchAboutUsPageContent: builder.query({
- query: () => '/pages/about-us',
+ fetchAboutUsPageContent: builder.mutation({
+ query: () => ({
+ url: '',
+ method: 'POST',
+ body: {
+ query: jsonToGraphQLQuery({ query: aboutUsPageRequest }),
+ },
+ }),
+
+ transformResponse: (response: any) => {
+ return {
+ team: presentTeamMembers(response.data._komanda),
+ history: presentHistoryItems(response.data._istoriya),
+ stations: presentStations(response.data._azs),
+ reviews: presentReviews(response.data._otzyvy),
+ };
+ },
}),
}),
});
-
-export const { useFetchMainPageContentQuery, useFetchAboutUsPageContentQuery } =
- mainPageApi;
diff --git a/src/pages-templates/login/index.tsx b/src/pages-templates/login/index.tsx
index ac51ec6..40c4bb1 100644
--- a/src/pages-templates/login/index.tsx
+++ b/src/pages-templates/login/index.tsx
@@ -4,6 +4,7 @@ import { deleteCookie, getCookie } from 'cookies-next';
import { Building2, Fuel, User } from 'lucide-react';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
+import { Suspense } from 'react';
import { LoginForm } from '@/features/auth/login-form';
@@ -16,13 +17,13 @@ import {
CardHeader,
CardTitle,
} from '@/shared/shadcn-ui/card';
+import Container from '@/shared/shadcn-ui/conteiner';
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from '@/shared/shadcn-ui/tabs';
-import Container from '@/shared/shadcn-ui/conteiner';
const tabs = [
{
@@ -41,16 +42,97 @@ const tabs = [
},
];
-export default function LoginPage() {
+function LoginPageTabs() {
const { t } = useTextController();
const router = useRouter();
const searchParams = useSearchParams();
+
const defaultTab = searchParams.get('tab') || 'bonus';
const handleTabChange = (tabType: string) => {
- router.push(`?tab=${tabType}`, undefined, { shallow: true });
+ router.push(`?tab=${tabType}`, undefined);
};
+ return (
+
+
+ {tabs.map((tab) => {
+ return (
+
+ {t(tab.label)}
+
+ );
+ })}
+
+
+ {tabs.map((tab) => {
+ const tabCookieName = `${tab.type}__token`;
+
+ const authenticationCookie = getCookie(tabCookieName);
+
+ if (authenticationCookie) {
+ return (
+
+
+
+ {t(tab.title)}
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {t(tab.title)}
+ {t(tab.description)}
+
+
+
+
+
+
+ );
+ })}
+
+ );
+}
+
+export default function LoginPage() {
+ const { t } = useTextController();
+
return (
@@ -67,86 +149,17 @@ export default function LoginPage() {
-
-
- {tabs.map((tab) => {
- return (
-
- {t(tab.label)}
-
- );
- })}
-
-
- {tabs.map((tab) => {
- const tabCookieName = `${tab.type}__token`;
-
- const authenticationCookie = getCookie(tabCookieName);
-
- if (authenticationCookie) {
- return (
-
-
-
- {t(tab.title)}
-
-
-
-
-
-
-
-
-
- );
- }
-
- return (
-
-
-
- {t(tab.title)}
- {t(tab.description)}
-
-
-
-
-
-
- );
- })}
-
+
+
+
{t('auth.loginIssues')}{' '}
-
+
{t('auth.contactLink')}
diff --git a/src/shared/api/taylor-api.ts b/src/shared/api/taylor-api.ts
new file mode 100644
index 0000000..200a98c
--- /dev/null
+++ b/src/shared/api/taylor-api.ts
@@ -0,0 +1,14 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
+
+const baseQuery = fetchBaseQuery({
+ baseUrl: process.env.TAYLOR_API_ENDPOINT,
+ headers: {
+ Authorization: process.env.TAYLOR_API_TOKEN || '',
+ },
+});
+
+export const taylorAPI = createApi({
+ reducerPath: 'taylorAPI',
+ baseQuery,
+ endpoints: () => ({}),
+});
diff --git a/src/shared/components/promotion-slider.tsx b/src/shared/components/promotion-slider.tsx
index fabe4be..06a75f7 100644
--- a/src/shared/components/promotion-slider.tsx
+++ b/src/shared/components/promotion-slider.tsx
@@ -5,7 +5,7 @@ import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useState } from 'react';
-import { Discounts } from '@/app/api-utlities/@types/main';
+import { Discounts } from '@/app/api-utlities/@types';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
diff --git a/src/shared/language/api/text-control.api.ts b/src/shared/language/api/text-control.api.ts
index 1fe2b26..dcbce86 100644
--- a/src/shared/language/api/text-control.api.ts
+++ b/src/shared/language/api/text-control.api.ts
@@ -1,12 +1,25 @@
-import { baseAPI } from '@/shared/api/base-api';
+import { jsonToGraphQLQuery } from 'json-to-graphql-query';
+
+import { presentTexts } from '@/app/api-utlities/presenters';
+import { textsRequest } from '@/app/api-utlities/requests/common';
+
+import { taylorAPI } from '@/shared/api/taylor-api';
import { TextItem } from '@/shared/types/text.types';
-export const textControlApi = baseAPI.injectEndpoints({
+export const textControlApi = taylorAPI.injectEndpoints({
endpoints: (builder) => ({
fetchText: builder.query
({
- query: () => '/text',
+ query: () => ({
+ url: '',
+ method: 'POST',
+ body: {
+ query: jsonToGraphQLQuery({ query: textsRequest }),
+ },
+ }),
+
+ transformResponse: (response: any) => {
+ return presentTexts(response.data._kontentSajta);
+ },
}),
}),
});
-
-export const { useFetchTextQuery } = textControlApi;
diff --git a/src/shared/store/root-reducer.ts b/src/shared/store/root-reducer.ts
index 1b1a8cd..080aa28 100644
--- a/src/shared/store/root-reducer.ts
+++ b/src/shared/store/root-reducer.ts
@@ -2,6 +2,9 @@ import { combineReducers } from '@reduxjs/toolkit';
import { baseAPI } from '@/shared/api/base-api';
+import { taylorAPI } from '../api/taylor-api';
+
export const rootReducer = combineReducers({
[baseAPI.reducerPath]: baseAPI.reducer,
+ [taylorAPI.reducerPath]: taylorAPI.reducer,
});
diff --git a/src/widgets/about-page/company-timeline.tsx b/src/widgets/about-page/company-timeline.tsx
index 3142061..1dcf68b 100644
--- a/src/widgets/about-page/company-timeline.tsx
+++ b/src/widgets/about-page/company-timeline.tsx
@@ -3,7 +3,7 @@
import { Calendar, ChevronDown, ChevronUp } from 'lucide-react';
import { useState } from 'react';
-import { HistoryItems } from '@/app/api-utlities/@types/about-us';
+import { HistoryItems } from '@/app/api-utlities/@types';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
diff --git a/src/widgets/map-section.tsx b/src/widgets/map-section.tsx
index 8bfa798..dc4c187 100644
--- a/src/widgets/map-section.tsx
+++ b/src/widgets/map-section.tsx
@@ -2,9 +2,8 @@
import { MapPin } from 'lucide-react';
-import { Stations } from '@/app/api-utlities/@types/main';
+import { Stations } from '@/app/api-utlities/@types';
-import { GasStationMap } from '@/features/map';
import { Point } from '@/features/map/model';
import { YandexMap } from '@/features/map/ui/yandex-map';
diff --git a/src/widgets/partners-section.tsx b/src/widgets/partners-section.tsx
index dd51fd9..e85119f 100644
--- a/src/widgets/partners-section.tsx
+++ b/src/widgets/partners-section.tsx
@@ -4,7 +4,7 @@ import { Handshake } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
-import { Partners } from '@/app/api-utlities/@types/main';
+import { Partners } from '@/app/api-utlities/@types';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
diff --git a/src/widgets/promotions-section.tsx b/src/widgets/promotions-section.tsx
index 39068b2..610b1ea 100644
--- a/src/widgets/promotions-section.tsx
+++ b/src/widgets/promotions-section.tsx
@@ -2,7 +2,7 @@
import { Gift } from 'lucide-react';
-import { Discounts } from '@/app/api-utlities/@types/main';
+import { Discounts } from '@/app/api-utlities/@types';
import PromotionSlider from '@/shared/components/promotion-slider';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
diff --git a/src/widgets/vacancies-section.tsx b/src/widgets/vacancies-section.tsx
index db81ee0..20cd737 100644
--- a/src/widgets/vacancies-section.tsx
+++ b/src/widgets/vacancies-section.tsx
@@ -2,7 +2,7 @@
import { Briefcase } from 'lucide-react';
-import { Jobs } from '@/app/api-utlities/@types/main';
+import { Jobs } from '@/app/api-utlities/@types';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { cn } from '@/shared/lib/utils';
From 891fb643399391a36ca5675169d82939af1cfe42 Mon Sep 17 00:00:00 2001
From: Umar Adilov <99314948+adilovcode@users.noreply.github.com>
Date: Thu, 1 May 2025 23:27:00 +0500
Subject: [PATCH 2/3] Added taylor api middleware
---
src/shared/store/index.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/shared/store/index.ts b/src/shared/store/index.ts
index fb9db85..599e8de 100644
--- a/src/shared/store/index.ts
+++ b/src/shared/store/index.ts
@@ -4,13 +4,16 @@ import { createWrapper } from 'next-redux-wrapper';
import { baseAPI } from '@/shared/api/base-api';
+import { taylorAPI } from '../api/taylor-api';
import { rootReducer } from './root-reducer';
export const makeStore = () =>
configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
- getDefaultMiddleware().concat(baseAPI.middleware),
+ getDefaultMiddleware()
+ .concat(baseAPI.middleware)
+ .concat(taylorAPI.middleware),
devTools: process.env.NODE_ENV === 'development',
});
From 93c4b75998ac720e9152003a1426a4a23fbe2a3b Mon Sep 17 00:00:00 2001
From: Umar Adilov <99314948+adilovcode@users.noreply.github.com>
Date: Fri, 2 May 2025 00:49:05 +0500
Subject: [PATCH 3/3] integrate transaction table
---
src/app/api/auth/login/route.ts | 16 +--
src/app/api/bonus/transactions/route.ts | 63 +++++++++
src/app/api/corporate/info/route.ts | 23 ++++
.../middlewares/error-handler.middleware.ts | 5 +-
src/entities/bonus/api/bonus.api.ts | 20 ++-
.../model/types/bonus-client-info.type.ts | 26 ++++
src/entities/corporate/api/corporate.api.ts | 17 +++
.../model/types/corporate-client-info.type.ts | 9 ++
.../(dashboard)/corporate-dashboard/index.tsx | 126 +++++++++++-------
src/widgets/transactions-table.tsx | 101 ++++----------
10 files changed, 264 insertions(+), 142 deletions(-)
create mode 100644 src/app/api/bonus/transactions/route.ts
create mode 100644 src/app/api/corporate/info/route.ts
create mode 100644 src/entities/corporate/api/corporate.api.ts
create mode 100644 src/entities/corporate/model/types/corporate-client-info.type.ts
diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts
index 7dbe591..8772735 100644
--- a/src/app/api/auth/login/route.ts
+++ b/src/app/api/auth/login/route.ts
@@ -23,22 +23,18 @@ const routeHandler = async (req: NextRequest) => {
},
});
- const { token, card_id } = JSON.parse(oriyoResponse.data);
+ const parsedResponse = JSON.parse(oriyoResponse.data);
- if (!token) {
+ if (!parsedResponse.token) {
return NextResponse.json({ error: 'Credentials error' }, { status: 401 });
}
const response = NextResponse.json({ success: true });
- response.cookies.set(
- `${validatedBody.type}__token`,
- JSON.stringify({ token, card_id }),
- {
- path: '/',
- maxAge: 2 * 60 * 60,
- },
- );
+ response.cookies.set(`${validatedBody.type}__token`, oriyoResponse.data, {
+ path: '/',
+ maxAge: 2 * 60 * 60,
+ });
return response;
} catch (error) {
diff --git a/src/app/api/bonus/transactions/route.ts b/src/app/api/bonus/transactions/route.ts
new file mode 100644
index 0000000..310e78a
--- /dev/null
+++ b/src/app/api/bonus/transactions/route.ts
@@ -0,0 +1,63 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { z } from 'zod';
+
+import oriyoClient from '@/app/api-utlities/utilities/oriyo.client';
+
+import { validationErrorHandler } from '../../middlewares/error-handler.middleware';
+
+const validatedSchema = z.object({
+ start_date: z.string().optional(),
+ end_date: z.string().optional(),
+ limit: z.coerce.number(),
+ page: z.coerce.number(),
+});
+
+const routeHandler = async (req: NextRequest) => {
+ const bonusTokenData = req.cookies.get('bonus__token');
+
+ if (!bonusTokenData) {
+ return NextResponse.json(
+ { error: 'User does not have access' },
+ { status: 401 },
+ );
+ }
+
+ const params = Array.from(req.nextUrl.searchParams.entries()).reduce(
+ (pr, cr) => {
+ pr[cr[0]] = cr[1];
+
+ return pr;
+ },
+ {} as Record,
+ );
+
+ const validatedRequest = validatedSchema.parse(params);
+
+ const { card_id, token } = JSON.parse(bonusTokenData.value);
+
+ const oriyoResponse = await oriyoClient.get('/client/transactions', {
+ params: {
+ card_id,
+ token,
+ limit: validatedRequest.limit,
+ page: validatedRequest.page,
+ type: 'bonus',
+ sort: 'id',
+ direction: 'desc',
+ start_date: validatedRequest.start_date,
+ end_date: validatedRequest.end_date,
+ },
+ });
+
+ const parsedResponse = JSON.parse(oriyoResponse.data);
+
+ if (parsedResponse.error) {
+ return NextResponse.json({ message: 'Fetch error' }, { status: 400 });
+ }
+
+ return new Response(oriyoResponse.data, {
+ headers: { 'Content-Type': 'application/json' },
+ });
+};
+
+export const GET = validationErrorHandler(routeHandler);
diff --git a/src/app/api/corporate/info/route.ts b/src/app/api/corporate/info/route.ts
new file mode 100644
index 0000000..b7e23ff
--- /dev/null
+++ b/src/app/api/corporate/info/route.ts
@@ -0,0 +1,23 @@
+import { omit } from 'lodash';
+import { NextRequest, NextResponse } from 'next/server';
+
+import { validationErrorHandler } from '../../middlewares/error-handler.middleware';
+
+const routeHandler = async (req: NextRequest) => {
+ const bonusTokenData = req.cookies.get('corporate__token');
+
+ if (!bonusTokenData) {
+ return NextResponse.json(
+ { error: 'User does not have access' },
+ { status: 401 },
+ );
+ }
+
+ const parsedData = JSON.parse(bonusTokenData.value);
+
+ return new Response(JSON.stringify(omit(parsedData, 'token')), {
+ headers: { 'Content-Type': 'application/json' },
+ });
+};
+
+export const GET = validationErrorHandler(routeHandler);
diff --git a/src/app/api/middlewares/error-handler.middleware.ts b/src/app/api/middlewares/error-handler.middleware.ts
index cc02041..d7fa719 100644
--- a/src/app/api/middlewares/error-handler.middleware.ts
+++ b/src/app/api/middlewares/error-handler.middleware.ts
@@ -2,9 +2,10 @@ import { NextRequest, NextResponse } from 'next/server';
import { ZodError } from 'zod';
export const validationErrorHandler =
- (handler: Function) => async (req: NextRequest) => {
+ (handler: Function) =>
+ async (req: NextRequest, ...args: any[]) => {
try {
- return await handler(req);
+ return await handler(req, ...args);
} catch (error) {
if (error instanceof ZodError)
return NextResponse.json({ message: error.format() }, { status: 400 });
diff --git a/src/entities/bonus/api/bonus.api.ts b/src/entities/bonus/api/bonus.api.ts
index b8d31bf..b83c712 100644
--- a/src/entities/bonus/api/bonus.api.ts
+++ b/src/entities/bonus/api/bonus.api.ts
@@ -1,6 +1,10 @@
import { baseAPI } from '@/shared/api/base-api';
-import { ClientInfo } from '../model/types/bonus-client-info.type';
+import {
+ ClientInfo,
+ TransactionRequest,
+ TransactionResponse,
+} from '../model/types/bonus-client-info.type';
export const bonusApi = baseAPI.injectEndpoints({
endpoints: (builder) => ({
@@ -11,7 +15,19 @@ export const bonusApi = baseAPI.injectEndpoints({
};
},
}),
+ fetchBonusTransactions: builder.query<
+ TransactionResponse,
+ TransactionRequest
+ >({
+ query: (request) => {
+ return {
+ url: '/bonus/transactions',
+ params: request,
+ };
+ },
+ }),
}),
});
-export const { useFetchMyBonusInfoQuery } = bonusApi;
+export const { useFetchMyBonusInfoQuery, useFetchBonusTransactionsQuery } =
+ bonusApi;
diff --git a/src/entities/bonus/model/types/bonus-client-info.type.ts b/src/entities/bonus/model/types/bonus-client-info.type.ts
index dd7138f..bc50a6c 100644
--- a/src/entities/bonus/model/types/bonus-client-info.type.ts
+++ b/src/entities/bonus/model/types/bonus-client-info.type.ts
@@ -6,3 +6,29 @@ export interface ClientInfo {
end_date: string;
bonuses: string;
}
+
+export interface TransactionResponse {
+ transactions: Transaction[];
+ card_id: string;
+ current_page: number;
+ limit: number;
+ total_records: number;
+ total_pages: number;
+}
+
+export interface Transaction {
+ id: number;
+ date_create: string;
+ station: string;
+ product_name: string;
+ amount: string;
+ price_real: string;
+ sum_real: string;
+}
+
+export interface TransactionRequest {
+ start_date?: string;
+ end_date?: string;
+ page: number;
+ limit: number;
+}
diff --git a/src/entities/corporate/api/corporate.api.ts b/src/entities/corporate/api/corporate.api.ts
new file mode 100644
index 0000000..32426ac
--- /dev/null
+++ b/src/entities/corporate/api/corporate.api.ts
@@ -0,0 +1,17 @@
+import { baseAPI } from '@/shared/api/base-api';
+
+import { CorporateInfoResponse } from '../model/types/corporate-client-info.type';
+
+export const corporateApi = baseAPI.injectEndpoints({
+ endpoints: (builder) => ({
+ fetchMyCorporateInfo: builder.query({
+ query: () => {
+ return {
+ url: '/corporate/info',
+ };
+ },
+ }),
+ }),
+});
+
+export const { useFetchMyCorporateInfoQuery } = corporateApi;
diff --git a/src/entities/corporate/model/types/corporate-client-info.type.ts b/src/entities/corporate/model/types/corporate-client-info.type.ts
new file mode 100644
index 0000000..3a069ae
--- /dev/null
+++ b/src/entities/corporate/model/types/corporate-client-info.type.ts
@@ -0,0 +1,9 @@
+export interface CorporateInfoResponse {
+ created_at: string;
+ fund: string;
+ fund_total: string;
+ group_id: number;
+ group_name: string;
+ overdraft: string;
+ total_cards: number;
+}
diff --git a/src/pages-templates/(dashboard)/corporate-dashboard/index.tsx b/src/pages-templates/(dashboard)/corporate-dashboard/index.tsx
index 02ddedd..6f0bc94 100644
--- a/src/pages-templates/(dashboard)/corporate-dashboard/index.tsx
+++ b/src/pages-templates/(dashboard)/corporate-dashboard/index.tsx
@@ -4,6 +4,8 @@ import { subMonths } from 'date-fns';
import { Building2, LogOut, Wallet } from 'lucide-react';
import { useState } from 'react';
+import { useFetchMyCorporateInfoQuery } from '@/entities/corporate/api/corporate.api';
+
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import {
@@ -96,6 +98,8 @@ export function CorporateDashboard() {
const { t } = useTextController();
+ const { data, isLoading } = useFetchMyCorporateInfoQuery({});
+
return (
@@ -110,7 +114,11 @@ export function CorporateDashboard() {
{/* Company Card */}
-
+
@@ -118,55 +126,67 @@ export function CorporateDashboard() {
-
-
-
-
- {t('corporate.companyCard.companyNameLabel')}
-
-
{companyData.companyName}
+ {!data ? (
+ <>Loading>
+ ) : (
+
+
+
+
+ {t('corporate.companyCard.companyNameLabel')}
+
+
+ {data.group_name}
+
+
+
+
+ {t('corporate.companyCard.cardsCountLabel')}
+
+
{data.total_cards}
+
+
+
+ {t('corporate.companyCard.registrationDateLabel')}
+
+
+
+ {new Date(data.created_at).toLocaleDateString(
+ 'en-GB',
+ )}
+
+
-
-
- {t('corporate.companyCard.cardsCountLabel')}
-
-
{companyData.numberOfCards}
-
-
-
- {t('corporate.companyCard.registrationDateLabel')}
-
-
- {companyData.registrationDate}
-
+
+
+
+ {t('corporate.companyCard.fundLabel')}
+
+
+ {data.fund.toLocaleString()} {t('corporate.currency')}
+
+
+
+
+ {t('corporate.companyCard.overdraftLabel')}
+
+
+ {data.overdraft.toLocaleString()}{' '}
+ {t('corporate.currency')}
+
+
-
-
-
- {t('corporate.companyCard.fundLabel')}
-
-
- {companyData.fund.toLocaleString()}{' '}
- {t('corporate.currency')}
-
-
-
-
- {t('corporate.companyCard.overdraftLabel')}
-
-
- {companyData.overdraft.toLocaleString()}{' '}
- {t('corporate.currency')}
-
-
-
-
+ )}
{/* Fund Card */}
-
+
@@ -177,14 +197,18 @@ export function CorporateDashboard() {
-
-
- {companyData.totalFund.toLocaleString()}
-
-
- {t('corporate.fundCard.currency')}
-
-
+ {!data ? (
+ <>Loading>
+ ) : (
+
+
+ {data.fund_total?.toLocaleString()}
+
+
+ {t('corporate.fundCard.currency')}
+
+
+ )}
diff --git a/src/widgets/transactions-table.tsx b/src/widgets/transactions-table.tsx
index 03fe3ca..993ac00 100644
--- a/src/widgets/transactions-table.tsx
+++ b/src/widgets/transactions-table.tsx
@@ -3,7 +3,9 @@
import { format, subMonths } from 'date-fns';
import { ru } from 'date-fns/locale';
import { CalendarIcon } from 'lucide-react';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
+
+import { useFetchBonusTransactionsQuery } from '@/entities/bonus/api/bonus.api';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
@@ -23,85 +25,30 @@ import {
TableRow,
} from '@/shared/shadcn-ui/table';
-// Sample customer data
-const customerData = {
- firstName: 'Алишер',
- lastName: 'Рахмонов',
- passportNumber: 'A12345678',
- bonusPoints: 1250,
- cardNumber: '5678-9012-3456-7890',
- expiryDate: '12/2025',
- registrationDate: '15.06.2020',
-};
-
-// 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 const TransactionsTable = () => {
- const [startDate, setStartDate] = useState
(
- subMonths(new Date(), 1),
- );
- const [endDate, setEndDate] = useState(new Date());
- const [filteredTransactions, setFilteredTransactions] =
- useState(transactions);
+ const [startDate, setStartDate] = useState(subMonths(new Date(), 1));
+ const [endDate, setEndDate] = useState(new Date());
+
+ const { data, refetch } = useFetchBonusTransactionsQuery({
+ limit: 100,
+ page: 1,
+ start_date: format(startDate, 'yyyy-MM-dd'),
+ end_date: format(endDate, 'yyyy-MM-dd'),
+ });
// 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);
+ refetch();
};
const { t } = useTextController();
+ useEffect(() => {}, [startDate, endDate]);
+
+ if (!data) return null;
+
return (
@@ -200,22 +147,22 @@ export const TransactionsTable = () => {
- {filteredTransactions.length > 0 ? (
- filteredTransactions.map((transaction) => (
+ {data.transactions.length > 0 ? (
+ data.transactions.map((transaction) => (
- {format(transaction.date, 'dd.MM.yyyy')}
+ {format(new Date(transaction.date_create), 'dd.MM.yyyy')}
{transaction.station}
- {transaction.product}
+ {transaction.product_name}
- {transaction.quantity}
+ {transaction.price_real}
- {transaction.cost.toFixed(2)} {t('corporate.currency')}
+ {transaction.amount} {t('corporate.currency')}
- {transaction.total.toFixed(2)} {t('corporate.currency')}
+ {transaction.sum_real} {t('corporate.currency')}
))