Compare commits

...

3 Commits

Author SHA1 Message Date
BunyodL
b5b20b054d Merge branch 'feat-auth' into dev 2025-04-28 22:00:26 +05:00
BunyodL
a78467947d Merge branch 'dev' into feat-auth 2025-04-28 21:48:22 +05:00
BunyodL
935b7f72e5 feat: make auth logic 2025-04-28 21:48:14 +05:00
10 changed files with 181 additions and 43 deletions

View File

@ -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 });
}
};

View File

@ -1,5 +1,5 @@
import LoginPage from "@/pages-templates/login";
import LoginPage from '@/pages-templates/login';
export default function Login() {
return <LoginPage/>
}
return <LoginPage />;
}

View File

@ -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<LoginResponse, LoginParams>({
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;

View File

@ -0,0 +1 @@
export { useLazyLoginQuery } from './api/login.api';

View File

@ -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';
}

View File

@ -0,0 +1,7 @@
export interface LoginData {
card_id: number;
created_at: string;
phone: string;
token: string;
uid: string;
}

View File

@ -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<typeof loginFormSchema>;

View File

@ -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<void>;
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<LoginFormData>({
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 (
<Form {...form} >
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4 mx-auto'>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className='mx-auto space-y-4'
>
<FormField
control={form.control}
name='phoneNumber'
render={({ field }) => (
<FormItem>
<FormLabel>{t("auth.phoneNumber")}</FormLabel>
<FormItem className='flex flex-col'>
<FormLabel>{t('auth.phoneNumber')}</FormLabel>
<FormControl>
<Input
type='tel'
@ -82,8 +80,8 @@ export const LoginForm = ({}: LoginFormProps) => {
control={form.control}
name='cardNumber'
render={({ field }) => (
<FormItem>
<FormLabel>{t("auth.cardNumber")}</FormLabel>
<FormItem className='flex flex-col'>
<FormLabel>{t('auth.cardNumber')}</FormLabel>
<FormControl>
<Input
type='text'
@ -95,16 +93,14 @@ export const LoginForm = ({}: LoginFormProps) => {
</FormItem>
)}
/>
<Button
// isLoading={results.isLoading}
// title='Login'
<SubmitButton
isLoading={isLoginLoading}
type='submit'
className='w-full'
// variant={'default'}
// disabled={loading}
disabled={isLoginLoading}
>
Войти
</Button>
</SubmitButton>
</form>
</Form>
);

View File

@ -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 (
<Button
onClick={onClick}
type={type}
size={size}
className={className}
disabled={isLoading || disabled}
{...props}
>
{isLoading ? (
<Loader2Icon className='animate-spin' />
) : (
(props.children ?? title)
)}
</Button>
);
};

View File

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