Create hooks and implement login pages

This commit is contained in:
2023-11-30 11:19:18 +07:00
parent d67dfa685f
commit f09a4bc60d
10 changed files with 290 additions and 27 deletions
+21 -6
View File
@@ -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 };
+6 -20
View File
@@ -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,
};