mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Update: Implement delete account feature + package updates
This commit is contained in:
868
package-lock.json
generated
868
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@@ -13,41 +13,41 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/iron": "^7.0.1",
|
||||
"@headlessui/react": "^1.7.14",
|
||||
"@headlessui/react": "^1.7.15",
|
||||
"@headlessui/tailwindcss": "^0.1.3",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@mapbox/mapbox-sdk": "^0.15.1",
|
||||
"@next/bundle-analyzer": "^13.4.3",
|
||||
"@prisma/client": "^4.13.0",
|
||||
"@next/bundle-analyzer": "^13.4.4",
|
||||
"@prisma/client": "^4.15.0",
|
||||
"@react-email/components": "^0.0.6",
|
||||
"@react-email/render": "^0.0.7",
|
||||
"@react-email/tailwind": "^0.0.8",
|
||||
"@vercel/analytics": "^1.0.0",
|
||||
"@vercel/analytics": "^1.0.1",
|
||||
"argon2": "^0.30.3",
|
||||
"cloudinary": "^1.36.4",
|
||||
"cloudinary": "^1.37.0",
|
||||
"cookie": "^0.5.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"dotenv": "^16.1.3",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mapbox-gl": "^2.14.1",
|
||||
"mapbox-gl": "^2.15.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"multer-storage-cloudinary": "^4.0.0",
|
||||
"next": "^13.3.4",
|
||||
"next": "^13.4.4",
|
||||
"next-connect": "^1.0.0-next.3",
|
||||
"passport": "^0.6.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"pino": "^8.12.0",
|
||||
"pino": "^8.14.1",
|
||||
"pino-pretty": "^10.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-daisyui": "^3.1.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-email": "^1.9.3",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-hook-form": "^7.44.3",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-intersection-observer": "^9.4.3",
|
||||
"react-map-gl": "^7.0.23",
|
||||
"react-icons": "^4.9.0",
|
||||
"react-intersection-observer": "^9.4.4",
|
||||
"react-map-gl": "^7.0.25",
|
||||
"react-responsive-carousel": "^3.2.23",
|
||||
"sparkpost": "^2.1.4",
|
||||
"swr": "^2.1.5",
|
||||
@@ -55,37 +55,37 @@
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/jsonwebtoken": "^9.0.2",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/lodash": "^4.14.195",
|
||||
"@types/mapbox__mapbox-sdk": "^0.13.4",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^18.16.3",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/passport-local": "^1.0.35",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.1",
|
||||
"@types/react": "^18.2.8",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"@types/sparkpost": "^2.1.5",
|
||||
"@vercel/fetch": "^6.2.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"daisyui": "^2.51.6",
|
||||
"daisyui": "^3.0.0",
|
||||
"dotenv-cli": "^7.2.1",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "17.0.0",
|
||||
"eslint-config-next": "^13.3.4",
|
||||
"eslint-config-next": "^13.4.4",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss": "^8.4.24",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-jsdoc": "^0.4.2",
|
||||
"prettier-plugin-tailwindcss": "^0.2.8",
|
||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||
"onchange": "^7.1.0",
|
||||
"prisma": "^4.13.0",
|
||||
"prisma": "^4.15.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tailwindcss-animate": "^1.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4"
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"prisma": {
|
||||
"schema": "./src/prisma/schema.prisma",
|
||||
|
||||
70
src/components/Account/DeleteAccount.tsx
Normal file
70
src/components/Account/DeleteAccount.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Switch } from '@headlessui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { FunctionComponent, useRef, useState } from 'react';
|
||||
|
||||
const DeleteAccount: FunctionComponent = () => {
|
||||
const [deleteToggled, setDeleteToggled] = useState(false);
|
||||
|
||||
const deleteRef = useRef<null | HTMLDialogElement>(null);
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="card w-full space-y-4">
|
||||
<div className="card-body">
|
||||
<div className="flex w-full items-center justify-between space-x-5">
|
||||
<div className="">
|
||||
<h1 className="text-lg font-bold">Delete Your Account</h1>
|
||||
<p>Want to leave? Delete your account here.</p>
|
||||
</div>
|
||||
<div>
|
||||
<Switch
|
||||
className="toggle"
|
||||
id="edit-toggle"
|
||||
checked={deleteToggled}
|
||||
onClick={() => {
|
||||
setDeleteToggled((val) => !val);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{deleteToggled && (
|
||||
<>
|
||||
<div className="mt-3">
|
||||
<button
|
||||
className="btn-primary btn w-full"
|
||||
onClick={() => deleteRef.current!.showModal()}
|
||||
>
|
||||
Delete my account
|
||||
</button>
|
||||
<dialog id="delete-modal" className="modal" ref={deleteRef}>
|
||||
<div className="modal-box text-center">
|
||||
<h3 className="text-lg font-bold">{`You're about to delete your account.`}</h3>
|
||||
<p className="">This action is permanent and cannot be reversed.</p>
|
||||
<div className="modal-action flex-col space-x-0 space-y-3">
|
||||
<button
|
||||
className="btn-error btn-sm btn w-full"
|
||||
onClick={async () => {
|
||||
deleteRef.current!.close();
|
||||
await router.replace('/api/users/logout');
|
||||
}}
|
||||
>
|
||||
Okay, delete my account
|
||||
</button>
|
||||
<button
|
||||
className="btn-success btn-sm btn w-full"
|
||||
onClick={() => deleteRef.current!.close()}
|
||||
>
|
||||
Go back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteAccount;
|
||||
@@ -20,7 +20,7 @@ const Security: FunctionComponent = () => {
|
||||
|
||||
const onSubmit: SubmitHandler<z.infer<typeof UpdatePasswordSchema>> = async (data) => {
|
||||
await sendUpdatePasswordRequest(data);
|
||||
setEditToggled(value => !value)
|
||||
setEditToggled((value) => !value);
|
||||
reset();
|
||||
};
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ const CustomToast: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const alertType = toastToClassName(t.type);
|
||||
return (
|
||||
<div
|
||||
className={`alert ${alertType} w-11/12 flex-row items-center shadow-lg animate-in fade-in duration-200 lg:w-2/12`}
|
||||
className={`alert ${alertType} w-11/12 flex-row items-center shadow-lg animate-in fade-in duration-200 lg:w-4/12`}
|
||||
>
|
||||
<p className='text-sm'>{resolveValue(t.message, t)}</p>
|
||||
<p>{resolveValue(t.message, t)}</p>
|
||||
{t.type !== 'loading' && (
|
||||
<div>
|
||||
<button
|
||||
|
||||
@@ -12,7 +12,7 @@ const DesktopLinks: FC = () => {
|
||||
|
||||
return (
|
||||
<div className="block flex-none">
|
||||
<ul className="menu menu-horizontal p-0">
|
||||
<ul className="menu menu-horizontal menu-sm px-1">
|
||||
{pages.map((page) => {
|
||||
return (
|
||||
<li key={page.slug}>
|
||||
@@ -43,7 +43,7 @@ const MobileLinks: FC = () => {
|
||||
</label>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="dropdown-content menu rounded-box menu-compact mt-3 w-48 bg-base-100 p-2 shadow"
|
||||
className="menu-compact dropdown-content menu rounded-box mt-3 w-48 bg-base-100 p-2 shadow"
|
||||
>
|
||||
{pages.map((page) => (
|
||||
<li key={page.slug}>
|
||||
|
||||
@@ -14,7 +14,8 @@ const Button: FunctionComponent<FormButtonProps> = ({
|
||||
// eslint-disable-next-line react/button-has-type
|
||||
<button
|
||||
type={type}
|
||||
className={`btn-primary btn w-full rounded-xl ${isSubmitting ? 'loading' : ''}`}
|
||||
className={`btn-primary btn w-full rounded-xl`}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
|
||||
@@ -7,6 +7,7 @@ import AccountInfo from '@/components/Account/AccountInfo';
|
||||
import { useContext } from 'react';
|
||||
import UserContext from '@/contexts/UserContext';
|
||||
import Security from '@/components/Account/Security';
|
||||
import DeleteAccount from '@/components/Account/DeleteAccount';
|
||||
|
||||
const AccountPage: NextPage = () => {
|
||||
const { user } = useContext(UserContext);
|
||||
@@ -48,6 +49,7 @@ const AccountPage: NextPage = () => {
|
||||
<Tab.Panel className="h-full space-y-5">
|
||||
<AccountInfo />
|
||||
<Security />
|
||||
<DeleteAccount />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>Your posts!</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
|
||||
@@ -5,6 +5,7 @@ import Welcome from '@/emails/Welcome';
|
||||
import { render } from '@react-email/render';
|
||||
import { z } from 'zod';
|
||||
import { BASE_URL } from '@/config/env';
|
||||
import { ReactElement } from 'react';
|
||||
import GetUserSchema from './schema/GetUserSchema';
|
||||
|
||||
type UserSchema = z.infer<typeof GetUserSchema>;
|
||||
@@ -17,8 +18,10 @@ const sendConfirmationEmail = async ({ id, username, email }: UserSchema) => {
|
||||
const url = `${BASE_URL}/users/confirm?token=${confirmationToken}`;
|
||||
const address = email;
|
||||
|
||||
const html = render(Welcome({ name, url, subject })!);
|
||||
const text = render(Welcome({ name, url, subject })!, { plainText: true });
|
||||
const component = Welcome({ name, url, subject })! as ReactElement<unknown, string>;
|
||||
|
||||
const html = render(component);
|
||||
const text = render(component, { plainText: true });
|
||||
|
||||
await sendEmail({ address, subject, text, html });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user