mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Add edit information account page
This commit is contained in:
@@ -16,6 +16,7 @@ import Booking from './pages/Booking';
|
|||||||
import Room from './pages/Room';
|
import Room from './pages/Room';
|
||||||
import NotFound from './pages/NotFound';
|
import NotFound from './pages/NotFound';
|
||||||
import Login from '@page/Login';
|
import Login from '@page/Login';
|
||||||
|
import Account from '@page/Account';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import * as PATH from './constants/path';
|
import * as PATH from './constants/path';
|
||||||
@@ -74,6 +75,7 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
{/* <ReactQueryDevtools initialIsOpen={false} /> */}
|
||||||
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
|
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
|
||||||
<UserRoomAvailableContext.Provider value={store}>
|
<UserRoomAvailableContext.Provider value={store}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
@@ -93,6 +95,7 @@ function App() {
|
|||||||
<Route path={PATH.BOOKING} element={<Booking />} />
|
<Route path={PATH.BOOKING} element={<Booking />} />
|
||||||
<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>
|
</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 />} />
|
||||||
|
|||||||
@@ -3,15 +3,18 @@ import Nav from '@component/Nav';
|
|||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import { Heading, StyledSidebar } from './styled';
|
import { Heading, StyledSidebar } from './styled';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
interface ISidebar {
|
interface ISidebar {
|
||||||
heading: string;
|
heading: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Sidebar = ({ heading }: ISidebar) => {
|
const Sidebar = ({ heading }: ISidebar) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSidebar>
|
<StyledSidebar>
|
||||||
<Heading>{heading}</Heading>
|
<Heading onClick={() => navigate('/')}>{heading}</Heading>
|
||||||
<Nav />
|
<Nav />
|
||||||
</StyledSidebar>
|
</StyledSidebar>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const Heading = styled.h1`
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { updateAccount as updateAccountFn } from '@service/authenticationService';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { UPDATE_ACCOUNT_SUCCESSFUL } from '@constant/messages';
|
||||||
|
|
||||||
|
const useUpdateAccount = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { mutate: updateAccount, isPending: isUpdating } = useMutation({
|
||||||
|
mutationFn: updateAccountFn,
|
||||||
|
onSuccess: (account) => {
|
||||||
|
toast.success(UPDATE_ACCOUNT_SUCCESSFUL);
|
||||||
|
queryClient.setQueryData(['account'], account.user);
|
||||||
|
},
|
||||||
|
onError: (err) => toast.error(err.message),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { updateAccount, isUpdating };
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useUpdateAccount };
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
|
// Styled
|
||||||
|
import Input from '@commonStyle/Input';
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import Form from '@component/Form';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { REQUIRED_FIELD_ERROR } from '@constant/formValidateMessage';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
import { useUpdateAccount } from '@hook/authentication/useUpdateAccount';
|
||||||
|
import { useAccount } from '@hook/authentication/useAccount';
|
||||||
|
|
||||||
|
interface IAccountForm {
|
||||||
|
fullName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccountInforForm = () => {
|
||||||
|
const { account } = useAccount();
|
||||||
|
|
||||||
|
const { updateAccount, isUpdating } = useUpdateAccount();
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<IAccountForm>();
|
||||||
|
|
||||||
|
const onSubmit = (data: IAccountForm) => {
|
||||||
|
updateAccount(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Form.Row label="Email:">
|
||||||
|
<Input id="email" readOnly value={account?.email} />
|
||||||
|
</Form.Row>
|
||||||
|
|
||||||
|
<Form.Row label="Full Name:" error={errors?.fullName?.message as string}>
|
||||||
|
<Input
|
||||||
|
id="fullName"
|
||||||
|
{...register('fullName', {
|
||||||
|
required: REQUIRED_FIELD_ERROR,
|
||||||
|
value: account?.user_metadata.fullName,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Form.Row>
|
||||||
|
|
||||||
|
<Form.Action>
|
||||||
|
<Form.Button type="submit" name="submit" disabled={isUpdating}>
|
||||||
|
Update account
|
||||||
|
</Form.Button>
|
||||||
|
</Form.Action>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountInforForm;
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
// Styled
|
||||||
|
import Input from '@commonStyle/Input';
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import Form from '@component/Form';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { REGEX } from '@constant/commons';
|
||||||
|
import {
|
||||||
|
PASSWORD_CONDITION,
|
||||||
|
PASSWORD_NOT_MATCH,
|
||||||
|
PASSWORD_NOT_MATCH_REQUIREMENT,
|
||||||
|
} from '@constant/formValidateMessage';
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
import { isValidRegex } from '@helper/validators';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
import { useUpdateAccount } from '@hook/authentication/useUpdateAccount';
|
||||||
|
|
||||||
|
interface IAccountPasswordForm {
|
||||||
|
password: string;
|
||||||
|
confirmPassword: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccountPasswordForm = () => {
|
||||||
|
const { updateAccount, isUpdating } = useUpdateAccount();
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
getValues,
|
||||||
|
reset,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<IAccountPasswordForm>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (errors?.password?.message) {
|
||||||
|
toast.error(PASSWORD_CONDITION);
|
||||||
|
}
|
||||||
|
}, [errors?.password?.message]);
|
||||||
|
|
||||||
|
const onSubmit = (data: IAccountPasswordForm) => {
|
||||||
|
updateAccount(data, {
|
||||||
|
onSuccess: () => {
|
||||||
|
reset();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<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={isUpdating}>
|
||||||
|
Update password
|
||||||
|
</Form.Button>
|
||||||
|
</Form.Action>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountPasswordForm;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Styled
|
||||||
|
import { FormArea, StyledAccount, SubTitle, Title } from './styled';
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import AccountInforForm from './AccountInforForm';
|
||||||
|
import AccountPasswordForm from './AccountPasswordForm';
|
||||||
|
|
||||||
|
const Account = () => {
|
||||||
|
return (
|
||||||
|
<StyledAccount>
|
||||||
|
<Title>Update account</Title>
|
||||||
|
|
||||||
|
<FormArea>
|
||||||
|
<SubTitle>Update information</SubTitle>
|
||||||
|
<AccountInforForm />
|
||||||
|
</FormArea>
|
||||||
|
|
||||||
|
<FormArea>
|
||||||
|
<SubTitle>Update password</SubTitle>
|
||||||
|
<AccountPasswordForm />
|
||||||
|
</FormArea>
|
||||||
|
</StyledAccount>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Account;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Title = styled.h2`
|
||||||
|
font-size: var(--fs-md);
|
||||||
|
color: var(--dark-text);
|
||||||
|
text-transform: capitalize;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SubTitle = styled.h3`
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
text-transform: capitalize;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const StyledAccount = 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, StyledAccount, FormArea, SubTitle };
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import { ILogin } from '@type/common';
|
import { ILogin } from '@type/common';
|
||||||
|
import { UserAttributes } from '@supabase/supabase-js';
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
import supabase from './supabaseService';
|
import supabase from './supabaseService';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { IAccount } from '@type/account';
|
||||||
|
|
||||||
const login = async ({ email, password }: ILogin) => {
|
const login = async ({ email, password }: ILogin) => {
|
||||||
const { data, error } = await supabase.auth.signInWithPassword({
|
const { data, error } = await supabase.auth.signInWithPassword({
|
||||||
email,
|
email,
|
||||||
@@ -40,4 +44,25 @@ const logout = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { login, logout, getCurrentAccount };
|
const updateAccount = async ({ fullName, password }: IAccount) => {
|
||||||
|
let accountUpdate: UserAttributes | null = null;
|
||||||
|
|
||||||
|
if (password) {
|
||||||
|
accountUpdate = { password };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullName) {
|
||||||
|
accountUpdate = { data: { fullName } };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await supabase.auth.updateUser(accountUpdate!);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(error.message);
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { login, logout, getCurrentAccount, updateAccount };
|
||||||
|
|||||||
Reference in New Issue
Block a user