Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare account deletion #67

Merged
merged 13 commits into from
Mar 20, 2023
Empty file removed src/features/settings/.gitkeep
Empty file.
79 changes: 79 additions & 0 deletions src/features/settings/settingsManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { auth, db } from 'src/firebase';
import {
collection,
deleteDoc,
doc,
getDocs,
query,
where,
} from 'firebase/firestore';
import toast from 'react-hot-toast';
import { useApplicationContext } from 'src/features/application/context';
import { useState } from 'react';
import { signOut, User } from 'firebase/auth';
import { useRouter } from 'next/router';

interface SettingsManager {
loading: boolean;
error: Error;
user: User;
isOpen: boolean;
closeDeleteModal: () => void;
openDeleteModal: () => void;
handleOnAccountDelete: () => void;
}

export const useSettingsManager = (): SettingsManager => {
const { loading, error, user } = useApplicationContext();
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();

function closeDeleteModal() {
setIsOpen(false);
}

function openDeleteModal() {
setIsOpen(true);
}

const handleOnAccountDelete = async () => {
try {
const q = query(
collection(db, 'surveys'),
where('creatorId', '==', user?.uid)
);
const surveysCollection = await getDocs(q);
surveysCollection.forEach(async (survey) => {
await deleteDoc(doc(db, 'surveys', survey.id));
});
const answersCollection = await getDocs(
query(collection(db, 'answers'), where('creatorId', '==', user?.uid))
);
answersCollection.forEach(async (answer) => {
await deleteDoc(doc(db, 'answers', answer.id));
});

await deleteDoc(doc(db, 'users', user.uid));

await user.delete();

closeDeleteModal();
toast.success('Account deleted');
signOut(auth);
router.replace('/');
} catch (error) {
toast.error('Error deleting account');
console.error(error);
}
};

return {
loading,
error,
user,
isOpen,
closeDeleteModal,
openDeleteModal,
handleOnAccountDelete,
};
};
99 changes: 35 additions & 64 deletions src/features/surveys/components/SurveyRow/SurveyRow.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useState, Fragment } from 'react';
import { useState } from 'react';
import { LinkIcon, TrashIcon } from '@heroicons/react/outline';

import toast from 'react-hot-toast';
import useCopyToClipboard from '../../../../shared/hooks/useCopyToClipboard';
import { Dialog, Transition } from '@headlessui/react';
import { db } from '../../../../firebase';
import { deleteDoc, doc } from 'firebase/firestore';
import { useRouter } from 'next/router';
Expand All @@ -13,6 +12,7 @@ import Button, {
import IconButton, {
IconButtonVariant,
} from '../../../../shared/components/IconButton/IconButton';
import StyledDialog from 'src/shared/components/StyledDialog/StyledDialog';

interface SurveyRowProps {
question: string;
Expand Down Expand Up @@ -64,68 +64,6 @@ export default function SurveyRow({

return (
<div className="flex flex-col mb-4 w-[600px] max-w-full md:flex-row">
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeDeleteModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-zinc-900 bg-opacity-25" />
</Transition.Child>

<div className="overflow-y-auto fixed inset-0">
<div className="flex justify-center items-center p-4 min-h-full text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="flex overflow-hidden flex-col justify-center p-6 w-auto max-w-md text-left bg-white rounded-md shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-zinc-900"
>
Delete survey
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-red-500">
Are you sure you want to&nbsp;
<span className="font-bold">delete</span> this survey?
</p>
</div>

<div className="flex justify-between mt-6">
<Button
variant={ButtonVariant.SECONDARY}
onClick={closeDeleteModal}
className="uppercase"
>
Cancel
</Button>
<IconButton
variant={IconButtonVariant.DANGER}
onClick={handleOnDelete(id)}
icon={<TrashIcon className="w-5 h-5" />}
className="uppercase"
>
Delete
</IconButton>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
<div className="flex justify-between items-center py-3 px-4 w-full bg-white rounded-md rounded-b-none shadow-sm md:rounded-b-md">
<div title={question} className="w-36 text-left truncate">
{question}
Expand Down Expand Up @@ -162,6 +100,39 @@ export default function SurveyRow({
icon={<TrashIcon className="w-5 h-5" />}
/>
</div>
<StyledDialog
isOpen={isOpen}
onClose={closeDeleteModal}
title="Delete survey"
content={
<>
<div className="mt-2">
<p className="text-sm text-red-500">
Are you sure you want to&nbsp;
<span className="font-bold">delete</span> this survey?
</p>
</div>

<div className="flex justify-between mt-6">
<Button
variant={ButtonVariant.SECONDARY}
onClick={closeDeleteModal}
className="uppercase"
>
Cancel
</Button>
<IconButton
variant={IconButtonVariant.DANGER}
onClick={handleOnDelete(id)}
icon={<TrashIcon className="w-5 h-5" />}
className="uppercase"
>
Delete
</IconButton>
</div>
</>
}
/>
</div>
);
}
28 changes: 24 additions & 4 deletions src/layout/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LogoutIcon, MenuIcon } from '@heroicons/react/outline';
import { CogIcon, LogoutIcon, MenuIcon } from '@heroicons/react/outline';
import Link from 'next/link';
import { Fragment, useState } from 'react';
import { signOut } from 'firebase/auth';
Expand All @@ -13,10 +13,10 @@ import IconButton, {
} from '../../shared/components/IconButton/IconButton';
import Image from 'next/image';
import { useApplicationContext } from 'src/features/application/context';
import IconButtonLink from 'src/shared/components/IconButtonLink/IconButtonLink';
import AvatarIcon from '../../../public/images/avatar.svg';
import GithubCorner from '../GithubCorner/GithubCorner';


function Navigation() {
const { user, loading, displayName } = useApplicationContext();

Expand Down Expand Up @@ -89,7 +89,17 @@ function Navigation() {
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 mt-2 bg-white rounded-md divide-y divide-zinc-100 focus:outline-none ring-1 ring-black ring-opacity-5 shadow-lg origin-top-right">
<div className="flex justify-end p-1">
<div className="flex flex-col justify-end p-1">
<Menu.Item>
<Link href={'/settings'} passHref>
<IconButtonLink
variant={ButtonVariant.FLAT}
icon={<CogIcon className="w-5 h-5" />}
>
Settings
xdk78 marked this conversation as resolved.
Show resolved Hide resolved
</IconButtonLink>
</Link>
</Menu.Item>
xdk78 marked this conversation as resolved.
Show resolved Hide resolved
<Menu.Item>
<IconButton
onClick={logoutDesktop}
Expand Down Expand Up @@ -130,13 +140,23 @@ function Navigation() {
</Link>
<Link href={'/surveys'} passHref>
<ButtonLink
className="mb-3 w-[95%] lg:w-auto"
className="mb-3 w-[95%] lg:w-auto"
onClick={() => setIsOpen(!isOpen)}
variant={ButtonVariant.FLAT}
>
My Surveys
</ButtonLink>
</Link>
<Link href={'/settings'} passHref>
<IconButtonLink
className="justify-center mb-3 w-[95%] lg:w-auto"
onClick={() => setIsOpen(!isOpen)}
variant={ButtonVariant.FLAT}
icon={<CogIcon className="w-5 h-5" />}
>
Settings
</IconButtonLink>
</Link>
<IconButton
onClick={logoutMobile}
variant={IconButtonVariant.FLAT}
Expand Down
84 changes: 84 additions & 0 deletions src/pages/settings/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import withAnimation from '../../shared/HOC/withAnimation';
import Head from 'next/head';
import Header from 'src/shared/components/Header/Header';
import withProtectedRoute from 'src/shared/HOC/withProtectedRoute';
import IconButton, {
IconButtonVariant,
} from 'src/shared/components/IconButton/IconButton';
import { TrashIcon } from '@heroicons/react/outline';
import Button, { ButtonVariant } from 'src/shared/components/Button/Button';
import { useSettingsManager } from 'src/features/settings/settingsManager';
import StyledDialog from 'src/shared/components/StyledDialog/StyledDialog';

function SettingsPage() {
const {
user,
loading,
isOpen,
openDeleteModal,
closeDeleteModal,
handleOnAccountDelete,
} = useSettingsManager();

return (
<>
<Head>
<title>Settings</title>
<meta name="description" content="Settings - Employee Pulse" />
</Head>
<div className="container px-4 m-auto text-center md:px-8">
<Header>Hi {user.displayName}!</Header>
<div className="flex flex-col justify-center items-center space-y-2">
<div className="flex w-full md:ml-2 md:w-auto">
<IconButton
variant={IconButtonVariant.DANGER}
title="Delete my account"
className="justify-center px-3 mt-2 ml-2 w-full sm:mt-0 md:w-auto"
onClick={openDeleteModal}
icon={<TrashIcon className="w-5 h-5" />}
>
Delete my account
</IconButton>
</div>
</div>
<StyledDialog
isOpen={isOpen}
onClose={closeDeleteModal}
title="Delete my account"
content={
<>
<div className="mt-2">
<p className="text-sm text-red-500">
Are you sure you want to&nbsp;
<span className="font-bold">delete</span> your account?
</p>
</div>
<div className="flex justify-between mt-6 space-x-3">
<Button
variant={ButtonVariant.SECONDARY}
onClick={closeDeleteModal}
className="uppercase"
>
Cancel
</Button>
<IconButton
variant={IconButtonVariant.DANGER}
onClick={handleOnAccountDelete}
icon={<TrashIcon className="w-5 h-5" />}
className="uppercase"
>
Confirm
</IconButton>
</div>
</>
}
/>
{loading && (
<div className="text-sm text-center text-zinc-600">Loading...</div>
)}
</div>
</>
);
}

export default withProtectedRoute(withAnimation(SettingsPage));
2 changes: 1 addition & 1 deletion src/shared/components/IconButton/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ButtonProps, ButtonSize, ButtonVariant } from '../Button/Button';
export const IconButtonVariant = ButtonVariant;
export const IconButtonSize = ButtonSize;

type IconButtonProps = ButtonProps & {
export type IconButtonProps = ButtonProps & {
icon?: React.ReactNode;
};

Expand Down
Loading