feat: make auth logic
This commit is contained in:
parent
c989b2b7a4
commit
935b7f72e5
59
src/app/api/auth/login/route.ts
Normal file
59
src/app/api/auth/login/route.ts
Normal 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 });
|
||||
}
|
||||
};
|
||||
@ -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 (
|
||||
<div className='flex min-h-screen flex-col'>
|
||||
<main className='flex-1'>
|
||||
<div className='container max-w-6xl py-16'>
|
||||
<div className='container mx-auto max-w-6xl px-2 py-16'>
|
||||
<div className='mb-12 flex flex-col items-center text-center'>
|
||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||
<Fuel className='h-6 w-6 text-red-600' />
|
||||
@ -35,46 +54,37 @@ export default function LoginPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='mx-auto max-w-md'>
|
||||
<div className='mx-auto max-w-lg'>
|
||||
<Tabs defaultValue='bonus' className='w-full'>
|
||||
<TabsList className='mb-8 grid w-full grid-cols-2'>
|
||||
<TabsTrigger value='bonus' className='text-base'>
|
||||
<User className='mr-2 h-4 w-4' /> Бонусный клиент
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value='corporate' className='text-base'>
|
||||
<Building2 className='mr-2 h-4 w-4' /> Корпоративный клиент
|
||||
<TabsList className='mb-8 flex flex-col sm:flex-row w-full h-fit'>
|
||||
{tabs.map((tab) => {
|
||||
return (
|
||||
<TabsTrigger
|
||||
key={tab.label}
|
||||
value={tab.type}
|
||||
className='text-base w-full'
|
||||
>
|
||||
<tab.Icon className='mr-2 h-4 w-4' /> {tab.label}
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value='bonus'>
|
||||
{tabs.map((tab) => {
|
||||
return (
|
||||
<TabsContent key={tab.label} value={tab.type}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Вход для бонусных клиентов</CardTitle>
|
||||
<CardDescription>
|
||||
Введите номер телефона и номер бонусной карты для входа в
|
||||
личный кабинет.
|
||||
</CardDescription>
|
||||
<CardTitle>{tab.title}</CardTitle>
|
||||
<CardDescription>{tab.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-4'>
|
||||
<LoginForm />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value='corporate'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Вход для корпоративных клиентов</CardTitle>
|
||||
<CardDescription>
|
||||
Введите номер телефона и номер корпоративной карты для
|
||||
входа в личный кабинет.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-4'>
|
||||
<LoginForm />
|
||||
<LoginForm type={tab.type} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
|
||||
<div className='mt-8 text-center text-sm text-gray-500'>
|
||||
|
||||
24
src/entities/auth/api/login.api.ts
Normal file
24
src/entities/auth/api/login.api.ts
Normal 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;
|
||||
1
src/entities/auth/index.ts
Normal file
1
src/entities/auth/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { useLazyLoginQuery } from './api/login.api';
|
||||
9
src/entities/auth/model/contracts/login.contract.ts
Normal file
9
src/entities/auth/model/contracts/login.contract.ts
Normal 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';
|
||||
}
|
||||
7
src/entities/auth/model/types/index.ts
Normal file
7
src/entities/auth/model/types/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface LoginData {
|
||||
card_id: number;
|
||||
created_at: string;
|
||||
phone: string;
|
||||
token: string;
|
||||
uid: string;
|
||||
}
|
||||
@ -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>;
|
||||
|
||||
@ -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<void>;
|
||||
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<LoginFormData>({
|
||||
resolver: zodResolver(loginFormSchema),
|
||||
@ -35,7 +38,8 @@ export const LoginForm = ({}: LoginFormProps) => {
|
||||
});
|
||||
|
||||
const onSubmit = async (data: LoginFormData) => {
|
||||
// const response = await login(data).unwrap();
|
||||
try {
|
||||
const response = await login({ ...data, type }).unwrap();
|
||||
// const user = response.data;
|
||||
// dispatch(
|
||||
// setCredentials({
|
||||
@ -51,7 +55,12 @@ export const LoginForm = ({}: LoginFormProps) => {
|
||||
// );
|
||||
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 }) => (
|
||||
<FormItem>
|
||||
<FormItem className='flex flex-col'>
|
||||
<FormLabel>Номер телефона</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@ -79,7 +88,7 @@ export const LoginForm = ({}: LoginFormProps) => {
|
||||
control={form.control}
|
||||
name='cardNumber'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormItem className='flex flex-col'>
|
||||
<FormLabel>Номер карты</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@ -92,16 +101,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>
|
||||
);
|
||||
|
||||
36
src/shared/components/submit-button.tsx
Normal file
36
src/shared/components/submit-button.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@ -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,
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user