mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Create hooks and implement login pages
This commit is contained in:
@@ -7,12 +7,15 @@ import { useEffect, useMemo, useReducer } from 'react';
|
||||
// Components
|
||||
import AppLayout from './components/AppLayout';
|
||||
import Toast from './components/Toast';
|
||||
import RouteProtected from '@component/RouteProtected';
|
||||
import RootLayout from '@component/RootLayout';
|
||||
|
||||
// Pages
|
||||
import User from './pages/User';
|
||||
import Booking from './pages/Booking';
|
||||
import Room from './pages/Room';
|
||||
import NotFound from './pages/NotFound';
|
||||
import Login from '@page/Login';
|
||||
|
||||
// Constants
|
||||
import * as PATH from './constants/path';
|
||||
@@ -75,13 +78,25 @@ function App() {
|
||||
<UserRoomAvailableContext.Provider value={store}>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<AppLayout />}>
|
||||
<Route index element={<Navigate replace to={PATH.BOOKING} />} />
|
||||
<Route path={PATH.BOOKING} element={<Booking />} />
|
||||
<Route path={PATH.USER} element={<User />} />
|
||||
<Route path={PATH.ROOM} element={<Room />} />
|
||||
<Route element={<RootLayout />}>
|
||||
<Route
|
||||
element={
|
||||
<RouteProtected>
|
||||
<AppLayout />
|
||||
</RouteProtected>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
index
|
||||
element={<Navigate replace to={PATH.BOOKING} />}
|
||||
/>
|
||||
<Route path={PATH.BOOKING} element={<Booking />} />
|
||||
<Route path={PATH.USER} element={<User />} />
|
||||
<Route path={PATH.ROOM} element={<Room />} />
|
||||
</Route>
|
||||
<Route path={PATH.LOGIN} element={<Login />} />
|
||||
<Route path={PATH.OTHER_PATH} element={<NotFound />} />
|
||||
</Route>
|
||||
<Route path={PATH.OTHER_PATH} element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</UserRoomAvailableContext.Provider>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
// Services
|
||||
import { getCurrentAccount } from '@service/authenticationService';
|
||||
|
||||
const useAccount = () => {
|
||||
let isAuthenticated: boolean = false;
|
||||
const { data: account, isPending } = useQuery({
|
||||
queryKey: ['account'],
|
||||
queryFn: getCurrentAccount,
|
||||
});
|
||||
|
||||
if (account) {
|
||||
isAuthenticated = account.role === 'authenticated';
|
||||
}
|
||||
|
||||
return { isPending, account, isAuthenticated };
|
||||
};
|
||||
|
||||
export { useAccount };
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
// Services
|
||||
import { login as loginFn } from '@service/authenticationService';
|
||||
|
||||
// Types
|
||||
import { ILogin } from '@type/common';
|
||||
|
||||
const useLogin = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const redirectTo = searchParams.get('redirectTo') || null;
|
||||
|
||||
const { mutate: login, isPending } = useMutation({
|
||||
mutationFn: ({ email, password }: ILogin) => loginFn({ email, password }),
|
||||
onSuccess: (account) => {
|
||||
queryClient.setQueryData(['account'], account.user);
|
||||
redirectTo ? navigate(redirectTo) : navigate('/');
|
||||
},
|
||||
onError: (err) => {
|
||||
console.error(err.message);
|
||||
toast.error('Email or password not correct!');
|
||||
},
|
||||
});
|
||||
|
||||
return { login, isPending };
|
||||
};
|
||||
|
||||
export { useLogin };
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
// Services
|
||||
import { logout as logoutFn } from '@service/authenticationService';
|
||||
|
||||
const useLogout = () => {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: logout, isPending } = useMutation({
|
||||
mutationFn: logoutFn,
|
||||
onSuccess: () => {
|
||||
queryClient.removeQueries();
|
||||
navigate('/login', { replace: true });
|
||||
},
|
||||
});
|
||||
|
||||
return { logout, isPending };
|
||||
};
|
||||
|
||||
export { useLogout };
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
// Components
|
||||
import Form from '@component/Form';
|
||||
|
||||
// Styled
|
||||
import { FieldInput, StyledLoginForm } from './styled';
|
||||
import Button from '@commonStyle/Button';
|
||||
|
||||
// Constants
|
||||
import { REQUIRED_FIELD_ERROR } from '@constant/formValidateMessage';
|
||||
|
||||
// Types
|
||||
import { ILogin } from '@type/common';
|
||||
|
||||
// Hooks
|
||||
import { useLogin } from '@hook/authentication/useLogin';
|
||||
|
||||
const LoginForm = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<ILogin>();
|
||||
const { login, isPending } = useLogin();
|
||||
|
||||
const onSubmit = (data: ILogin) => {
|
||||
login(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledLoginForm>
|
||||
<Form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Form.Row
|
||||
label="Email:"
|
||||
direction="vertical"
|
||||
error={errors.email?.message}
|
||||
>
|
||||
<FieldInput
|
||||
type="text"
|
||||
{...register('email', {
|
||||
required: REQUIRED_FIELD_ERROR,
|
||||
pattern: {
|
||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||
message: 'Invalid email address',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</Form.Row>
|
||||
<Form.Row
|
||||
label="Password:"
|
||||
direction="vertical"
|
||||
error={errors.password?.message}
|
||||
>
|
||||
<FieldInput
|
||||
type="password"
|
||||
{...register('password', {
|
||||
required: REQUIRED_FIELD_ERROR,
|
||||
})}
|
||||
/>
|
||||
</Form.Row>
|
||||
|
||||
<Button type="submit" disabled={isPending}>Login</Button>
|
||||
</Form>
|
||||
</StyledLoginForm>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
// Styled
|
||||
import { LoginLayout, StyledLogo, TitleStyled } from './styled';
|
||||
|
||||
// Assets
|
||||
import logo from '@assets/images/logo.png';
|
||||
|
||||
// Components
|
||||
import LoginForm from './LoginForm';
|
||||
|
||||
// Hooks
|
||||
import { useAccount } from '@hook/authentication/useAccount';
|
||||
|
||||
const Login = () => {
|
||||
const { account } = useAccount();
|
||||
|
||||
if (account) {
|
||||
return <Navigate to={'/booking'} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<LoginLayout>
|
||||
<StyledLogo src={logo} />
|
||||
<TitleStyled>Log In</TitleStyled>
|
||||
|
||||
<LoginForm />
|
||||
</LoginLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
@@ -0,0 +1,43 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
// Styled
|
||||
import CommonInput from '@commonStyle/CommonInput';
|
||||
|
||||
const LoginLayout = styled.main`
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--background-login);
|
||||
`;
|
||||
|
||||
const StyledLogo = styled.img`
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
`;
|
||||
|
||||
const TitleStyled = styled.h1`
|
||||
font-size: var(--fs-md);
|
||||
`;
|
||||
|
||||
const StyledLoginForm = styled.div`
|
||||
background-color: var(--form-color);
|
||||
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
margin-top: 20px;
|
||||
padding: 30px 50px;
|
||||
`;
|
||||
|
||||
const FieldInput = styled.input`
|
||||
${CommonInput}
|
||||
|
||||
width: 300px;
|
||||
`
|
||||
|
||||
export { LoginLayout, StyledLogo, TitleStyled, StyledLoginForm, FieldInput };
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ILogin } from '@type/common';
|
||||
|
||||
// Services
|
||||
import supabase from './supabaseService';
|
||||
|
||||
const login = async ({ email, password }: ILogin) => {
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getCurrentAccount = async () => {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
|
||||
if (!data.session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data: accountData, error } = await supabase.auth.getUser();
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
return accountData.user;
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
const { error } = await supabase.auth.signOut();
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export { login, logout, getCurrentAccount };
|
||||
@@ -1,6 +1,5 @@
|
||||
// Types
|
||||
import { IRoom } from '@type/rooms';
|
||||
import { IDataState } from '@type/common';
|
||||
|
||||
// Services
|
||||
import supabase from './supabaseService';
|
||||
@@ -127,28 +126,17 @@ const getRoomById = async (idRoom: string): Promise<IRoom> => {
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
* Update status of room
|
||||
* @param id The id of room need to be change status
|
||||
* @param status The status of room
|
||||
*/
|
||||
const getRoomsAvailable = async (): Promise<IDataState[]> => {
|
||||
const { data, error } = await supabase
|
||||
.from(ROOMS_TABLE)
|
||||
.select('id, name, status');
|
||||
|
||||
if (error) {
|
||||
console.error(error.message);
|
||||
throw new Error(ERROR_FETCHING_ROOM);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateRoomStatus = async (
|
||||
id: number,
|
||||
status: boolean
|
||||
): Promise<number> => {
|
||||
const { error, status: statusConnection } = await supabase
|
||||
): Promise<void> => {
|
||||
const { error} = await supabase
|
||||
.from(ROOMS_TABLE)
|
||||
.update({ status })
|
||||
.eq('id', id);
|
||||
@@ -158,7 +146,6 @@ const updateRoomStatus = async (
|
||||
throw new Error(ERROR_UPDATE_ROOM);
|
||||
}
|
||||
|
||||
return statusConnection;
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -166,7 +153,6 @@ export {
|
||||
updateRoom,
|
||||
createRoom,
|
||||
deleteRoom,
|
||||
getRoomsAvailable,
|
||||
getRoomById,
|
||||
updateRoomStatus,
|
||||
};
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"@component/*": ["./src/components/*"],
|
||||
"@commonStyle/*": ["./src/commons/styles/*"],
|
||||
"@type/*": ["./src/types/*"],
|
||||
"@page/*": ["./src/pages/*"]
|
||||
"@page/*": ["./src/pages/*"],
|
||||
"@assets/*": ["./src/assets/*"],
|
||||
}
|
||||
},
|
||||
"include": ["src", "test"],
|
||||
|
||||
Reference in New Issue
Block a user