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 cdb2979..bb9a24e 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,5 +1,5 @@ -import LoginPage from "@/pages-templates/login"; +import LoginPage from '@/pages-templates/login'; export default function Login() { - return -} \ No newline at end of file + return ; +} 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 af41279..808756d 100644 --- a/src/features/auth/login-form/ui/login-form.tsx +++ b/src/features/auth/login-form/ui/login-form.tsx @@ -5,7 +5,10 @@ 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 { useLanguage } from '@/shared/language'; import { Form, FormControl, @@ -17,17 +20,16 @@ import { import { Input } from '@/shared/shadcn-ui/input'; import { LoginFormData, loginFormSchema } from '../model/login-form.schema'; -import { useLanguage } from '@/shared/language'; interface LoginFormProps { - // onSubmit: (data: any) => Promise; + type: 'bonus' | 'corporate'; } -export const LoginForm = ({}: LoginFormProps) => { - const {t} = useLanguage() +export const LoginForm = ({ type }: LoginFormProps) => { + const { t } = useLanguage(); const router = useRouter(); - // const [login, results] = useLoginMutation(); + const [login, { isLoading: isLoginLoading }] = useLazyLoginQuery(); const form = useForm({ resolver: zodResolver(loginFormSchema), @@ -38,34 +40,30 @@ 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 { + await login({ ...data, type }).unwrap(); - router.push('/customer-dashboard'); + toast.success('Logged in successfully!'); + router.push( + type === 'bonus' ? '/customer-dashboard' : '/corporate-dashboard', + ); + } catch (error) { + toast.error('An error occured during login'); + } }; return ( -
- + + ( - - {t("auth.phoneNumber")} + + {t('auth.phoneNumber')} { control={form.control} name='cardNumber' render={({ field }) => ( - - {t("auth.cardNumber")} + + {t('auth.cardNumber')} { )} /> - + ); 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, });