Merge pull request #51 from Nez27/feat/createAccount

Implement create account function
This commit is contained in:
Loi Phan
2023-12-15 10:00:58 +07:00
committed by GitHub
12 changed files with 226 additions and 9 deletions
+5
View File
@@ -16,6 +16,7 @@ import Room from './pages/Room';
import NotFound from './pages/NotFound'; import NotFound from './pages/NotFound';
import Login from '@src/pages/Login'; import Login from '@src/pages/Login';
import Account from '@src/pages/Account'; import Account from '@src/pages/Account';
import SignUpAccount from '@src/pages/SignUpAccount';
// Constants // Constants
import * as PATH from './constants/path'; import * as PATH from './constants/path';
@@ -41,6 +42,10 @@ function App() {
<Route path={PATH.USER} element={<User />} /> <Route path={PATH.USER} element={<User />} />
<Route path={PATH.ROOM} element={<Room />} /> <Route path={PATH.ROOM} element={<Room />} />
<Route path={PATH.ACCOUNT} element={<Account />} /> <Route path={PATH.ACCOUNT} element={<Account />} />
<Route
path={PATH.SIGN_UP_ACCOUNT}
element={<SignUpAccount />}
/>
</Route> </Route>
<Route path={PATH.LOGIN} element={<Login />} /> <Route path={PATH.LOGIN} element={<Login />} />
<Route path={PATH.OTHER_PATH} element={<NotFound />} /> <Route path={PATH.OTHER_PATH} element={<NotFound />} />
@@ -11,7 +11,11 @@ interface IHeader {
const Header = ({accountName}: IHeader) => { const Header = ({accountName}: IHeader) => {
return ( return (
<StyledHeader> <StyledHeader>
<p>Hi, {accountName}!</p> <p>Hi, {
accountName
? accountName
: 'Admin'
}!</p>
<HeaderMenu/> <HeaderMenu/>
</StyledHeader> </StyledHeader>
); );
@@ -7,7 +7,8 @@ import { BsHouseDoorFill } from 'react-icons/bs';
import { StyledNav, StyledNavLink } from './styled'; import { StyledNav, StyledNavLink } from './styled';
// Constants // Constants
import { BOOKING, ROOM, USER } from '@src/constants/path'; import {BOOKING, ROOM, SIGN_UP_ACCOUNT, USER} from '@src/constants/path';
import {FaUserPlus} from "react-icons/fa6";
const Nav = () => { const Nav = () => {
return ( return (
@@ -24,6 +25,10 @@ const Nav = () => {
<BsHouseDoorFill /> <BsHouseDoorFill />
<span>Room</span> <span>Room</span>
</StyledNavLink> </StyledNavLink>
<StyledNavLink to={SIGN_UP_ACCOUNT}>
<FaUserPlus />
<span>Create Account</span>
</StyledNavLink>
</StyledNav> </StyledNav>
); );
}; };
@@ -1,17 +1,16 @@
const INVALID_FIELD = 'This field is invalid format!'; const INVALID_FIELD = 'This field is invalid format!';
const INVALID_PHONE = 'Phone number is invalid!'; const INVALID_PHONE = 'Phone number is invalid!';
const REQUIRED_FIELD_ERROR = 'This field is required!'; const REQUIRED_FIELD_ERROR = 'This field is required!';
const INVALID_DISCOUNT = 'Discount should be greater than 0 and less than 100!';
const PASSWORD_CONDITION = const PASSWORD_CONDITION =
'Your password must have 1 uppercase, 1 lowercase, 1 special character, min 1 number and the length between from 8 to 16!'; 'Your password must have 1 uppercase, 1 lowercase, 1 special character, min 1 number and the length between from 8 to 16!';
const INVALID_PASSWORD = 'Invalid password'; const INVALID_PASSWORD = 'Invalid password';
const PASSWORD_NOT_MATCH = 'Password not match!'; const PASSWORD_NOT_MATCH = 'Password not match!';
const PASSWORD_NOT_MATCH_REQUIREMENT = 'Password not match the requirement!'; const PASSWORD_NOT_MATCH_REQUIREMENT = 'Password not match the requirement!';
const INVALID_EMAIL = 'Invalid email address'; const INVALID_EMAIL = 'Invalid email address';
const INVALID_STRING = 'This field need more than 5 characters!';
export { export {
REQUIRED_FIELD_ERROR, REQUIRED_FIELD_ERROR,
INVALID_DISCOUNT,
INVALID_FIELD, INVALID_FIELD,
INVALID_PHONE, INVALID_PHONE,
PASSWORD_CONDITION, PASSWORD_CONDITION,
@@ -19,4 +18,5 @@ export {
PASSWORD_NOT_MATCH, PASSWORD_NOT_MATCH,
PASSWORD_NOT_MATCH_REQUIREMENT, PASSWORD_NOT_MATCH_REQUIREMENT,
INVALID_EMAIL, INVALID_EMAIL,
INVALID_STRING
}; };
+2
View File
@@ -2,6 +2,7 @@ const BOOKING = '/booking';
const USER = '/user'; const USER = '/user';
const ROOM = '/room'; const ROOM = '/room';
const ACCOUNT = '/account'; const ACCOUNT = '/account';
const SIGN_UP_ACCOUNT = '/createAccount'
const OTHER_PATH = '*'; const OTHER_PATH = '*';
const LOGIN = 'login'; const LOGIN = 'login';
@@ -12,4 +13,5 @@ export {
ACCOUNT, ACCOUNT,
OTHER_PATH, OTHER_PATH,
LOGIN, LOGIN,
SIGN_UP_ACCOUNT,
} }
@@ -0,0 +1,17 @@
import { useMutation } from '@tanstack/react-query';
import { createAccount as createAccountFn } from '@src/services/authenticationService.ts';
import toast from 'react-hot-toast';
const useSignUpAccount = () => {
const { mutate: createAccount, isPending: isCreating } = useMutation({
mutationFn: createAccountFn,
onSuccess: (account) => {
toast.success(`Create user ${account!.user!.email} successful!`);
},
onError: (err) => toast.error(err.message),
});
return { createAccount, isCreating };
};
export { useSignUpAccount };
+1 -1
View File
@@ -35,7 +35,7 @@ const StyledLoginForm = styled.div`
`; `;
const FieldInput = styled.input` const FieldInput = styled.input`
${CommonInput} ${CommonInput};
width: 300px; width: 300px;
` `
@@ -0,0 +1,107 @@
import { useForm } from 'react-hook-form';
import Form from '@src/components/Form';
import Input from '@src/commons/styles/Input.ts';
import { isValidRegex, isValidString } from '@src/helpers/validators.ts';
import { REGEX } from '@src/constants/commons.ts';
import {
INVALID_EMAIL,
INVALID_STRING,
PASSWORD_CONDITION,
PASSWORD_NOT_MATCH,
PASSWORD_NOT_MATCH_REQUIREMENT,
REQUIRED_FIELD_ERROR,
} from '@src/constants/formValidateMessage.ts';
import { useEffect } from 'react';
import toast from 'react-hot-toast';
import { useSignUpAccount } from '@src/hooks/authentication/useSignUpAccount.ts';
interface ISignUpAccountForm {
email: string;
fullName: string;
password: string;
confirmPassword: string;
}
const SignUpAccountForm = () => {
const { createAccount, isCreating } = useSignUpAccount();
const {
register,
handleSubmit,
formState: { errors },
getValues,
reset,
} = useForm<ISignUpAccountForm>();
useEffect(() => {
if (errors?.password?.message) {
toast.error(PASSWORD_CONDITION);
}
}, [errors?.password?.message]);
const onSubmit = (data: ISignUpAccountForm) => {
createAccount({ email: data.email, password: data.password });
reset();
};
return (
<Form onSubmit={handleSubmit(onSubmit)}>
<Form.Row label="Email:" error={errors?.email?.message as string}>
<Input
id="email"
{...register('email', {
required: REQUIRED_FIELD_ERROR,
validate: (value) =>
isValidRegex(REGEX.EMAIL, value) || INVALID_EMAIL,
})}
/>
</Form.Row>
<Form.Row label="Full Name:" error={errors?.fullName?.message as string}>
<Input
id="fullName"
{...register('fullName', {
required: REQUIRED_FIELD_ERROR,
validate: (value) => isValidString(value) || INVALID_STRING,
})}
/>
</Form.Row>
<Form.Row label="Password:" error={errors?.password?.message}>
<Input
id="password"
type="password"
{...register('password', {
validate: {
checkPassword: (v) =>
isValidRegex(REGEX.PASSWORD, v) ||
PASSWORD_NOT_MATCH_REQUIREMENT,
},
})}
/>
</Form.Row>
<Form.Row
label="Confirm Password:"
error={errors?.confirmPassword?.message}
>
<Input
id="confirmPassword"
type="password"
{...register('confirmPassword', {
validate: (value) =>
getValues().password === value || PASSWORD_NOT_MATCH,
})}
/>
</Form.Row>
<Form.Action>
<Form.Button type="submit" name="submit" disabled={isCreating}>
Create Account
</Form.Button>
</Form.Action>
</Form>
);
};
export default SignUpAccountForm;
@@ -0,0 +1,18 @@
import {FormArea, StyledSignUpAccount, Title} from "@src/pages/SignUpAccount/styled.ts";
import SignUpForm from "@src/pages/SignUpAccount/SignUpForm.tsx";
const SignUpAccount = () => {
return (
<StyledSignUpAccount>
<Title>Create account</Title>
<FormArea>
<SignUpForm />
</FormArea>
</StyledSignUpAccount>
);
}
export default SignUpAccount;
@@ -0,0 +1,31 @@
import styled from 'styled-components';
const Title = styled.h2`
font-size: var(--fs-md);
color: var(--dark-text);
text-transform: capitalize;
`;
const StyledSignUpAccount = styled.div`
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
`;
const FormArea = styled.div`
border-radius: var(--radius-sm);
border: 1px solid var(--border-color);
padding: 20px 40px;
width: 500px;
margin-top: 50px;
`
export {
Title,
StyledSignUpAccount,
FormArea,
};
@@ -6,6 +6,7 @@ import supabase from './supabaseService';
// Types // Types
import { IAccount } from '@src/types/account'; import { IAccount } from '@src/types/account';
import toast from 'react-hot-toast';
/** /**
* Login services * Login services
@@ -82,4 +83,30 @@ const updateAccount = async ({ fullName, password }: IAccount) => {
return data; return data;
}; };
export { login, logout, getCurrentAccount, updateAccount }; const createAccount = async ({ email, password }: IAccount) => {
if (!email || !password) {
toast.error('Email and password is not valid!');
return;
}
const { data, error } = await supabase.auth.signUp({
email,
password,
});
if (error) {
console.error(error.message);
throw new Error(error.message);
}
return data;
};
export {
login,
logout,
getCurrentAccount,
updateAccount,
createAccount
};
+1
View File
@@ -1,4 +1,5 @@
interface IAccount { interface IAccount {
email?: string;
fullName?: string; fullName?: string;
password?: string; password?: string;
} }