From 935b7f72e5bc3221e78c84036754799f4cec35a0 Mon Sep 17 00:00:00 2001 From: BunyodL Date: Mon, 28 Apr 2025 21:48:14 +0500 Subject: [PATCH] feat: make auth logic --- src/app/api/auth/login/route.ts | 59 +++++++++++++ src/app/login/page.tsx | 86 +++++++++++-------- src/entities/auth/api/login.api.ts | 24 ++++++ src/entities/auth/index.ts | 1 + .../auth/model/contracts/login.contract.ts | 9 ++ src/entities/auth/model/types/index.ts | 7 ++ .../login-form/model/login-form.schema.ts | 14 +-- .../auth/login-form/ui/login-form.tsx | 61 +++++++------ src/shared/components/submit-button.tsx | 36 ++++++++ src/shared/store/root-reducer.ts | 4 + 10 files changed, 230 insertions(+), 71 deletions(-) create mode 100644 src/app/api/auth/login/route.ts create mode 100644 src/entities/auth/api/login.api.ts create mode 100644 src/entities/auth/index.ts create mode 100644 src/entities/auth/model/contracts/login.contract.ts create mode 100644 src/entities/auth/model/types/index.ts create mode 100644 src/shared/components/submit-button.tsx diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts new file mode 100644 index 0000000..774e8e7 --- /dev/null +++ b/src/app/api/auth/login/route.ts @@ -0,0 +1,59 @@ +import { NextRequest, NextResponse } from 'next/server'; + +import { LoginData } from '@/entities/auth/model/types'; + +export const GET = async (req: NextRequest) => { + if (req.method !== 'GET') { + return NextResponse.json( + { error: 'Method is not supported' }, + { status: 405 }, + ); + } + + const { searchParams } = req.nextUrl; + + const phoneNumber = searchParams.get('phoneNumber'); + const cardNumber = searchParams.get('cardNumber'); + const type = searchParams.get('type'); + + if (!phoneNumber || !cardNumber || !type) { + return NextResponse.json({ error: 'Bad request' }, { status: 400 }); + } + + try { + const loginRes = await fetch( + `https://test.oriyo.tj/api/client/login?type=${type}&phone=${phoneNumber}&uid=${cardNumber}`, + { + method: 'GET', + }, + ); + + if (!loginRes.ok) { + return NextResponse.json( + { error: 'Error during login' }, + { status: 400 }, + ); + } + + const data = (await loginRes.json()) as LoginData; + + const token = data.token; + if (!token) { + return NextResponse.json({ error: 'No auth token' }, { status: 401 }); + } + + const response = NextResponse.json({ success: true }); + + response.cookies.set('token', token, { + httpOnly: true, + path: '/', + maxAge: 2 * 60 * 60, + secure: process.env.NODE_ENV === 'production', + }); + + return response; + } catch (error) { + console.error('login error:', error); + return NextResponse.json({ error: 'Server error' }, { status: 500 }); + } +}; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 23db643..de16713 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -17,11 +17,30 @@ import { TabsTrigger, } from '@/shared/shadcn-ui/tabs'; +const tabs = [ + { + label: 'Бонусный клиент', + type: 'bonus' as const, + title: 'Вход для бонусных клиентов', + description: + 'Введите номер телефона и номер бонусной карты для входа в личный кабинет.', + Icon: User, + }, + { + label: 'Корпоративный клиент', + type: 'corporate' as const, + title: 'Вход для корпоративных клиентов', + description: + 'Введите номер телефона и номер корпоративной карты для входа в личный кабинет.', + Icon: Building2, + }, +]; + export default function LoginPage() { return (
-
+
@@ -35,46 +54,37 @@ export default function LoginPage() {

-
+
- - - Бонусный клиент - - - Корпоративный клиент - + + {tabs.map((tab) => { + return ( + + {tab.label} + + ); + })} - - - - Вход для бонусных клиентов - - Введите номер телефона и номер бонусной карты для входа в - личный кабинет. - - - - - - - - - - - - Вход для корпоративных клиентов - - Введите номер телефона и номер корпоративной карты для - входа в личный кабинет. - - - - - - - + {tabs.map((tab) => { + return ( + + + + {tab.title} + {tab.description} + + + + + + + ); + })}
diff --git a/src/entities/auth/api/login.api.ts b/src/entities/auth/api/login.api.ts new file mode 100644 index 0000000..99ad4ed --- /dev/null +++ b/src/entities/auth/api/login.api.ts @@ -0,0 +1,24 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; + +import { LoginParams, LoginResponse } from '../model/contracts/login.contract'; + +export const loginAPI = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: '/api' }), + endpoints: (build) => ({ + login: build.query({ + query: (data) => { + const params = new URLSearchParams({ + type: data.type, + phoneNumber: data.phoneNumber, + cardNumber: data.cardNumber, + }).toString(); + + return { + url: `/auth/login?${params}`, + }; + }, + }), + }), +}); + +export const { useLazyLoginQuery } = loginAPI; diff --git a/src/entities/auth/index.ts b/src/entities/auth/index.ts new file mode 100644 index 0000000..d99a63a --- /dev/null +++ b/src/entities/auth/index.ts @@ -0,0 +1 @@ +export { useLazyLoginQuery } from './api/login.api'; diff --git a/src/entities/auth/model/contracts/login.contract.ts b/src/entities/auth/model/contracts/login.contract.ts new file mode 100644 index 0000000..cb3a3da --- /dev/null +++ b/src/entities/auth/model/contracts/login.contract.ts @@ -0,0 +1,9 @@ +import { LoginFormData } from '@/features/auth/login-form/model/login-form.schema'; + +export interface LoginResponse { + success: boolean; +} + +export interface LoginParams extends LoginFormData { + type: 'bonus' | 'corporate'; +} diff --git a/src/entities/auth/model/types/index.ts b/src/entities/auth/model/types/index.ts new file mode 100644 index 0000000..1144cca --- /dev/null +++ b/src/entities/auth/model/types/index.ts @@ -0,0 +1,7 @@ +export interface LoginData { + card_id: number; + created_at: string; + phone: string; + token: string; + uid: string; +} diff --git a/src/features/auth/login-form/model/login-form.schema.ts b/src/features/auth/login-form/model/login-form.schema.ts index eca9731..a8da155 100644 --- a/src/features/auth/login-form/model/login-form.schema.ts +++ b/src/features/auth/login-form/model/login-form.schema.ts @@ -6,13 +6,15 @@ export const loginFormSchema = z.object({ .trim() .regex(/^[0-9+\-() ]*$/, { message: - 'Phone number can only contain numbers, spaces, and the following symbols: + - ( )', + 'Номер телефона может содержать только цифры, пробелы и следующие символы: + - ( )', }) - .refine((val) => !val || val.length >= 5, { - message: - 'Phone number is too short. Please enter a complete phone number', - }), - cardNumber: z.string().min(16).trim(), + .min(5, 'Номер телефона слишком короткий. Введите полный номер телефона') + .max(13, 'Номер телефона не может быть длиннее 13 символов'), + cardNumber: z + .string() + .min(6, 'Неверный номер карты. Введите полный номер карты') + .max(20, 'Номер карты не может быть длиннее 20 символов') + .trim(), }); export type LoginFormData = z.infer; diff --git a/src/features/auth/login-form/ui/login-form.tsx b/src/features/auth/login-form/ui/login-form.tsx index b89887b..ad2bc10 100644 --- a/src/features/auth/login-form/ui/login-form.tsx +++ b/src/features/auth/login-form/ui/login-form.tsx @@ -5,7 +5,9 @@ import { useRouter } from 'next/navigation'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; -import { Button } from '@/shared/shadcn-ui/button'; +import { useLazyLoginQuery } from '@/entities/auth'; + +import { SubmitButton } from '@/shared/components/submit-button'; import { Form, FormControl, @@ -20,11 +22,12 @@ import { LoginFormData, loginFormSchema } from '../model/login-form.schema'; interface LoginFormProps { // onSubmit: (data: any) => Promise; + type: 'bonus' | 'corporate'; } -export const LoginForm = ({}: LoginFormProps) => { +export const LoginForm = ({ type }: LoginFormProps) => { const router = useRouter(); - // const [login, results] = useLoginMutation(); + const [login, { isLoading: isLoginLoading }] = useLazyLoginQuery(); const form = useForm({ resolver: zodResolver(loginFormSchema), @@ -35,23 +38,29 @@ export const LoginForm = ({}: LoginFormProps) => { }); const onSubmit = async (data: LoginFormData) => { - // const response = await login(data).unwrap(); - // const user = response.data; - // dispatch( - // setCredentials({ - // user: { - // accessToken: user.accessToken, - // affiliateId: user.affiliateId, - // email: user.email, - // id: user.id, - // role: user.role, - // username: user.username, - // }, - // }), - // ); - toast.success('Logged in successfully!'); + try { + const response = await login({ ...data, type }).unwrap(); + // const user = response.data; + // dispatch( + // setCredentials({ + // user: { + // accessToken: user.accessToken, + // affiliateId: user.affiliateId, + // email: user.email, + // id: user.id, + // role: user.role, + // username: user.username, + // }, + // }), + // ); + toast.success('Logged in successfully!'); - router.push('/customer-dashboard'); + router.push( + type === 'bonus' ? '/customer-dashboard' : '/corporate-dashboard', + ); + } catch (error) { + toast.error('An error occured during login'); + } }; return ( @@ -61,7 +70,7 @@ export const LoginForm = ({}: LoginFormProps) => { control={form.control} name='phoneNumber' render={({ field }) => ( - + Номер телефона { control={form.control} name='cardNumber' render={({ field }) => ( - + Номер карты { )} /> - + ); diff --git a/src/shared/components/submit-button.tsx b/src/shared/components/submit-button.tsx new file mode 100644 index 0000000..ef9db2d --- /dev/null +++ b/src/shared/components/submit-button.tsx @@ -0,0 +1,36 @@ +import { Loader2Icon } from 'lucide-react'; + +import { Button, type ButtonProps } from '@/shared/shadcn-ui/button'; + +interface SubmitButtonProps extends ButtonProps { + title?: string; + isLoading: boolean; +} + +export const SubmitButton = ({ + title = 'Отправить', + size = 'default', + type = 'submit', + className, + disabled, + isLoading, + onClick, + ...props +}: SubmitButtonProps) => { + return ( + + ); +}; diff --git a/src/shared/store/root-reducer.ts b/src/shared/store/root-reducer.ts index 27360ad..56b999c 100644 --- a/src/shared/store/root-reducer.ts +++ b/src/shared/store/root-reducer.ts @@ -1,6 +1,10 @@ import { combineReducers } from '@reduxjs/toolkit'; + +import { loginAPI } from '@/entities/auth/api/login.api'; + import { baseAPI } from '@/shared/api/base-api'; export const rootReducer = combineReducers({ [baseAPI.reducerPath]: baseAPI.reducer, + [loginAPI.reducerPath]: loginAPI.reducer, });