mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Merge pull request #51 from Nez27/feat/createAccount
Implement create account function
This commit is contained in:
@@ -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,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 };
|
||||||
@@ -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,4 +1,5 @@
|
|||||||
interface IAccount {
|
interface IAccount {
|
||||||
|
email?: string;
|
||||||
fullName?: string;
|
fullName?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user