update: implement account page reducer to manage account page state

This commit is contained in:
Aaron William Po
2023-06-02 00:08:50 -04:00
parent 81cb95391a
commit 02d753fb41
5 changed files with 121 additions and 37 deletions

View File

@@ -3,19 +3,25 @@ import validateUsernameRequest from '@/requests/validateUsernameRequest';
import { BaseCreateUserSchema } from '@/services/User/schema/CreateUserValidationSchemas'; import { BaseCreateUserSchema } from '@/services/User/schema/CreateUserValidationSchemas';
import { Switch } from '@headlessui/react'; import { Switch } from '@headlessui/react';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { FC, useContext, useState } from 'react'; import { Dispatch, FC, useContext } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import UserContext from '@/contexts/UserContext'; import UserContext from '@/contexts/UserContext';
import sendEditUserRequest from '@/requests/User/sendEditUserRequest'; import sendEditUserRequest from '@/requests/User/sendEditUserRequest';
import createErrorToast from '@/util/createErrorToast'; import createErrorToast from '@/util/createErrorToast';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import { AccountPageAction, AccountPageState } from '@/reducers/accountPageReducer';
import FormError from '../ui/forms/FormError'; import FormError from '../ui/forms/FormError';
import FormInfo from '../ui/forms/FormInfo'; import FormInfo from '../ui/forms/FormInfo';
import FormLabel from '../ui/forms/FormLabel'; import FormLabel from '../ui/forms/FormLabel';
import FormTextInput from '../ui/forms/FormTextInput'; import FormTextInput from '../ui/forms/FormTextInput';
const AccountInfo: FC = () => { interface AccountInfoProps {
pageState: AccountPageState;
dispatch: Dispatch<AccountPageAction>;
}
const AccountInfo: FC<AccountInfoProps> = ({ pageState, dispatch }) => {
const { user, mutate } = useContext(UserContext); const { user, mutate } = useContext(UserContext);
const EditUserSchema = BaseCreateUserSchema.pick({ const EditUserSchema = BaseCreateUserSchema.pick({
@@ -47,18 +53,16 @@ const AccountInfo: FC = () => {
), ),
}); });
const [editToggled, setEditToggled] = useState(false);
const onSubmit = async (data: z.infer<typeof EditUserSchema>) => { const onSubmit = async (data: z.infer<typeof EditUserSchema>) => {
const loadingToast = toast.loading('Submitting edits...'); const loadingToast = toast.loading('Submitting edits...');
try { try {
await sendEditUserRequest({ user: user!, data }); await sendEditUserRequest({ user: user!, data });
toast.remove(loadingToast); toast.remove(loadingToast);
toast.success('Edits submitted successfully.'); toast.success('Edits submitted successfully.');
setEditToggled(false); dispatch({ type: 'CLOSE_ALL' });
await mutate!(); await mutate!();
} catch (error) { } catch (error) {
setEditToggled(false); dispatch({ type: 'CLOSE_ALL' });
toast.remove(loadingToast); toast.remove(loadingToast);
createErrorToast(error); createErrorToast(error);
await mutate!(); await mutate!();
@@ -82,9 +86,9 @@ const AccountInfo: FC = () => {
<Switch <Switch
className="toggle" className="toggle"
id="edit-toggle" id="edit-toggle"
checked={editToggled} checked={pageState.accountInfoOpen}
onClick={async () => { onClick={async () => {
setEditToggled((val) => !val); dispatch({ type: 'TOGGLE_ACCOUNT_INFO_VISIBILITY' });
await mutate!(); await mutate!();
reset({ reset({
username: user!.username, username: user!.username,
@@ -96,7 +100,7 @@ const AccountInfo: FC = () => {
/> />
</div> </div>
</div> </div>
{editToggled && ( {pageState.accountInfoOpen && (
<form <form
className="form-control space-y-5" className="form-control space-y-5"
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
@@ -109,7 +113,7 @@ const AccountInfo: FC = () => {
</FormInfo> </FormInfo>
<FormTextInput <FormTextInput
type="text" type="text"
disabled={!editToggled || formState.isSubmitting} disabled={!pageState.accountInfoOpen || formState.isSubmitting}
error={!!formState.errors.username} error={!!formState.errors.username}
id="username" id="username"
formValidationSchema={register('username')} formValidationSchema={register('username')}
@@ -120,7 +124,7 @@ const AccountInfo: FC = () => {
</FormInfo> </FormInfo>
<FormTextInput <FormTextInput
type="email" type="email"
disabled={!editToggled || formState.isSubmitting} disabled={!pageState.accountInfoOpen || formState.isSubmitting}
error={!!formState.errors.email} error={!!formState.errors.email}
id="email" id="email"
formValidationSchema={register('email')} formValidationSchema={register('email')}
@@ -134,7 +138,7 @@ const AccountInfo: FC = () => {
</FormInfo> </FormInfo>
<FormTextInput <FormTextInput
type="text" type="text"
disabled={!editToggled || formState.isSubmitting} disabled={!pageState.accountInfoOpen || formState.isSubmitting}
error={!!formState.errors.firstName} error={!!formState.errors.firstName}
id="firstName" id="firstName"
formValidationSchema={register('firstName')} formValidationSchema={register('firstName')}
@@ -147,7 +151,7 @@ const AccountInfo: FC = () => {
</FormInfo> </FormInfo>
<FormTextInput <FormTextInput
type="text" type="text"
disabled={!editToggled || formState.isSubmitting} disabled={!pageState.accountInfoOpen || formState.isSubmitting}
error={!!formState.errors.lastName} error={!!formState.errors.lastName}
id="lastName" id="lastName"
formValidationSchema={register('lastName')} formValidationSchema={register('lastName')}
@@ -157,7 +161,7 @@ const AccountInfo: FC = () => {
<button <button
className="btn-primary btn my-5 w-full" className="btn-primary btn my-5 w-full"
type="submit" type="submit"
disabled={!editToggled || formState.isSubmitting} disabled={!pageState.accountInfoOpen || formState.isSubmitting}
> >
Save Changes Save Changes
</button> </button>

View File

@@ -1,10 +1,16 @@
import { AccountPageState, AccountPageAction } from '@/reducers/accountPageReducer';
import { Switch } from '@headlessui/react'; import { Switch } from '@headlessui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { FunctionComponent, useRef, useState } from 'react'; import { Dispatch, FunctionComponent, useRef } from 'react';
const DeleteAccount: FunctionComponent = () => {
const [deleteToggled, setDeleteToggled] = useState(false);
interface DeleteAccountProps {
pageState: AccountPageState;
dispatch: Dispatch<AccountPageAction>;
}
const DeleteAccount: FunctionComponent<DeleteAccountProps> = ({
dispatch,
pageState,
}) => {
const deleteRef = useRef<null | HTMLDialogElement>(null); const deleteRef = useRef<null | HTMLDialogElement>(null);
const router = useRouter(); const router = useRouter();
@@ -20,14 +26,14 @@ const DeleteAccount: FunctionComponent = () => {
<Switch <Switch
className="toggle" className="toggle"
id="edit-toggle" id="edit-toggle"
checked={deleteToggled} checked={pageState.deleteAccountOpen}
onClick={() => { onClick={() => {
setDeleteToggled((val) => !val); dispatch({ type: 'TOGGLE_DELETE_ACCOUNT_VISIBILITY' });
}} }}
/> />
</div> </div>
</div> </div>
{deleteToggled && ( {pageState.deleteAccountOpen && (
<> <>
<div className="mt-3"> <div className="mt-3">
<button <button

View File

@@ -1,17 +1,24 @@
import { Switch } from '@headlessui/react'; import { Switch } from '@headlessui/react';
import { FunctionComponent, useState } from 'react'; import { Dispatch, FunctionComponent } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form'; import { SubmitHandler, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; import { z } from 'zod';
import { UpdatePasswordSchema } from '@/services/User/schema/CreateUserValidationSchemas'; import { UpdatePasswordSchema } from '@/services/User/schema/CreateUserValidationSchemas';
import sendUpdatePasswordRequest from '@/requests/User/sendUpdatePasswordRequest'; import sendUpdatePasswordRequest from '@/requests/User/sendUpdatePasswordRequest';
import { AccountPageState, AccountPageAction } from '@/reducers/accountPageReducer';
import toast from 'react-hot-toast';
import createErrorToast from '@/util/createErrorToast';
import FormError from '../ui/forms/FormError'; import FormError from '../ui/forms/FormError';
import FormInfo from '../ui/forms/FormInfo'; import FormInfo from '../ui/forms/FormInfo';
import FormLabel from '../ui/forms/FormLabel'; import FormLabel from '../ui/forms/FormLabel';
import FormTextInput from '../ui/forms/FormTextInput'; import FormTextInput from '../ui/forms/FormTextInput';
const Security: FunctionComponent = () => { interface SecurityProps {
const [editToggled, setEditToggled] = useState(false); pageState: AccountPageState;
dispatch: Dispatch<AccountPageAction>;
}
const Security: FunctionComponent<SecurityProps> = ({ dispatch, pageState }) => {
const { register, handleSubmit, formState, reset } = useForm< const { register, handleSubmit, formState, reset } = useForm<
z.infer<typeof UpdatePasswordSchema> z.infer<typeof UpdatePasswordSchema>
>({ >({
@@ -19,9 +26,16 @@ const Security: FunctionComponent = () => {
}); });
const onSubmit: SubmitHandler<z.infer<typeof UpdatePasswordSchema>> = async (data) => { const onSubmit: SubmitHandler<z.infer<typeof UpdatePasswordSchema>> = async (data) => {
const loadingToast = toast.loading('Changing password.');
try {
await sendUpdatePasswordRequest(data); await sendUpdatePasswordRequest(data);
setEditToggled((value) => !value); toast.remove(loadingToast);
reset(); toast.success('Password changed successfully.');
dispatch({ type: 'CLOSE_ALL' });
} catch (error) {
dispatch({ type: 'CLOSE_ALL' });
createErrorToast(error);
}
}; };
return ( return (
@@ -36,15 +50,15 @@ const Security: FunctionComponent = () => {
<Switch <Switch
className="toggle" className="toggle"
id="edit-toggle" id="edit-toggle"
checked={editToggled} checked={pageState.securityOpen}
onClick={() => { onClick={() => {
setEditToggled((val) => !val); dispatch({ type: 'TOGGLE_SECURITY_VISIBILITY' });
reset(); reset();
}} }}
/> />
</div> </div>
</div> </div>
{editToggled && ( {pageState.securityOpen && (
<form className="form-control" noValidate onSubmit={handleSubmit(onSubmit)}> <form className="form-control" noValidate onSubmit={handleSubmit(onSubmit)}>
<FormInfo> <FormInfo>
<FormLabel htmlFor="password">New Password</FormLabel> <FormLabel htmlFor="password">New Password</FormLabel>
@@ -52,7 +66,7 @@ const Security: FunctionComponent = () => {
</FormInfo> </FormInfo>
<FormTextInput <FormTextInput
type="password" type="password"
disabled={!editToggled || formState.isSubmitting} disabled={!pageState.securityOpen || formState.isSubmitting}
error={!!formState.errors.password} error={!!formState.errors.password}
id="password" id="password"
formValidationSchema={register('password')} formValidationSchema={register('password')}
@@ -63,7 +77,7 @@ const Security: FunctionComponent = () => {
</FormInfo> </FormInfo>
<FormTextInput <FormTextInput
type="password" type="password"
disabled={!editToggled || formState.isSubmitting} disabled={!pageState.securityOpen || formState.isSubmitting}
error={!!formState.errors.confirmPassword} error={!!formState.errors.confirmPassword}
id="password" id="password"
formValidationSchema={register('confirmPassword')} formValidationSchema={register('confirmPassword')}
@@ -71,7 +85,7 @@ const Security: FunctionComponent = () => {
<button <button
className="btn-primary btn mt-5" className="btn-primary btn mt-5"
disabled={!editToggled || formState.isSubmitting} disabled={!pageState.securityOpen || formState.isSubmitting}
type="submit" type="submit"
> >
Update Update

View File

@@ -4,13 +4,21 @@ import { NextPage } from 'next';
import { Tab } from '@headlessui/react'; import { Tab } from '@headlessui/react';
import Head from 'next/head'; import Head from 'next/head';
import AccountInfo from '@/components/Account/AccountInfo'; import AccountInfo from '@/components/Account/AccountInfo';
import { useContext } from 'react'; import { useContext, useReducer } from 'react';
import UserContext from '@/contexts/UserContext'; import UserContext from '@/contexts/UserContext';
import Security from '@/components/Account/Security'; import Security from '@/components/Account/Security';
import DeleteAccount from '@/components/Account/DeleteAccount'; import DeleteAccount from '@/components/Account/DeleteAccount';
import accountPageReducer from '@/reducers/accountPageReducer';
const AccountPage: NextPage = () => { const AccountPage: NextPage = () => {
const { user } = useContext(UserContext); const { user } = useContext(UserContext);
const [pageState, dispatch] = useReducer(accountPageReducer, {
accountInfoOpen: false,
securityOpen: false,
deleteAccountOpen: false,
});
if (!user) return null; if (!user) return null;
return ( return (
@@ -47,9 +55,9 @@ const AccountPage: NextPage = () => {
</Tab.List> </Tab.List>
<Tab.Panels> <Tab.Panels>
<Tab.Panel className="h-full space-y-5"> <Tab.Panel className="h-full space-y-5">
<AccountInfo /> <AccountInfo pageState={pageState} dispatch={dispatch} />
<Security /> <Security pageState={pageState} dispatch={dispatch} />
<DeleteAccount /> <DeleteAccount pageState={pageState} dispatch={dispatch} />
</Tab.Panel> </Tab.Panel>
<Tab.Panel>Your posts!</Tab.Panel> <Tab.Panel>Your posts!</Tab.Panel>
</Tab.Panels> </Tab.Panels>

View File

@@ -0,0 +1,52 @@
export interface AccountPageState {
accountInfoOpen: boolean;
securityOpen: boolean;
deleteAccountOpen: boolean;
}
export type AccountPageAction =
| { type: 'TOGGLE_ACCOUNT_INFO_VISIBILITY' }
| { type: 'TOGGLE_SECURITY_VISIBILITY' }
| { type: 'TOGGLE_DELETE_ACCOUNT_VISIBILITY' }
| { type: 'CLOSE_ALL' };
const accountPageReducer = (
state: AccountPageState,
action: AccountPageAction,
): AccountPageState => {
switch (action.type) {
case 'TOGGLE_ACCOUNT_INFO_VISIBILITY': {
return {
accountInfoOpen: !state.accountInfoOpen,
securityOpen: false,
deleteAccountOpen: false,
};
}
case 'TOGGLE_DELETE_ACCOUNT_VISIBILITY': {
return {
accountInfoOpen: false,
securityOpen: false,
deleteAccountOpen: !state.deleteAccountOpen,
};
}
case 'TOGGLE_SECURITY_VISIBILITY': {
return {
accountInfoOpen: false,
securityOpen: !state.securityOpen,
deleteAccountOpen: false,
};
}
case 'CLOSE_ALL': {
return {
accountInfoOpen: false,
securityOpen: false,
deleteAccountOpen: false,
};
}
default: {
throw new Error('Invalid action type.');
}
}
};
export default accountPageReducer;