mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
update: implement account page reducer to manage account page state
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) => {
|
||||||
await sendUpdatePasswordRequest(data);
|
const loadingToast = toast.loading('Changing password.');
|
||||||
setEditToggled((value) => !value);
|
try {
|
||||||
reset();
|
await sendUpdatePasswordRequest(data);
|
||||||
|
toast.remove(loadingToast);
|
||||||
|
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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
52
src/reducers/accountPageReducer.ts
Normal file
52
src/reducers/accountPageReducer.ts
Normal 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;
|
||||||
Reference in New Issue
Block a user