chore: delete account

chore/delete-account
Arik Chakma 1 year ago
parent 37107c495f
commit 8a94609fb9
  1. 8
      src/components/AccountSidebar.astro
  2. 2
      src/components/Authenticator/authenticator.ts
  3. 54
      src/components/DeleteAccount/DeleteAccount.tsx
  4. 93
      src/components/DeleteAccount/DeleteAccountForm.tsx
  5. 24
      src/components/DeleteAccount/DeleteAccountPopup.astro
  6. 4
      src/icons/cog.svg
  7. 22
      src/pages/account/settings.astro
  8. 16
      src/pages/account/update-password.astro

@ -40,12 +40,12 @@ const sidebarLinks = [
}, },
}, },
{ {
href: '/account/update-password', href: '/account/settings',
title: 'Security', title: 'Settings',
id: 'change-password', id: 'settings',
isNew: false, isNew: false,
icon: { icon: {
glyph: 'security', glyph: 'cog',
classes: 'h-4 w-4', classes: 'h-4 w-4',
}, },
}, },

@ -33,7 +33,7 @@ function showHideGuestElements(hideOrShow: 'hide' | 'show' = 'hide') {
function handleGuest() { function handleGuest() {
const authenticatedRoutes = [ const authenticatedRoutes = [
'/account/update-profile', '/account/update-profile',
'/account/update-password', '/account/settings',
'/account/road-card', '/account/road-card',
'/account', '/account',
]; ];

@ -0,0 +1,54 @@
export function showDeleteAccountPopup() {
const popupEl = document.querySelector(`#delete-account-popup`);
if (!popupEl) {
return;
}
popupEl.classList.remove('hidden');
popupEl.classList.add('flex');
const focusEl = popupEl.querySelector<HTMLElement>('[autofocus]');
if (focusEl) {
focusEl.focus();
}
}
export function hideDeleteAccountPopup() {
const popupEl = document.querySelector(`#delete-account-popup`);
if (!popupEl) {
return;
}
popupEl.classList.remove('flex');
popupEl.classList.add('hidden');
const focusEl = popupEl.querySelector<HTMLElement>('[autofocus]');
if (focusEl) {
focusEl.blur();
}
}
export function DeleteAccount() {
return (
<div>
<div>
<h2 className="text-xl font-bold sm:text-2xl">
Delete Account
</h2>
<p className="mt-2 text-gray-400">
Permanently remove your account from the roadmap.sh. This action is
not reversible, so please continue with caution.
</p>
</div>
<div className="mt-4">
<button
className="inline-flex h-10 items-center justify-center rounded-md bg-red-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-500/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-red-200 disabled:pointer-events-none disabled:opacity-50"
onClick={showDeleteAccountPopup}
>
Delete Account
</button>
</div>
</div>
);
}

@ -0,0 +1,93 @@
import { useState } from 'preact/hooks';
import Cookies from 'js-cookie';
import { httpDelete } from '../../lib/http';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
import { hideDeleteAccountPopup } from './DeleteAccount';
const DELETE_ACCOUNT_VERIFICATION = 'delete my account';
export function DeleteAccountForm() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [deleteAccount, setDeleteAccount] = useState('');
const handleSubmit = async (e: Event) => {
e.preventDefault();
setIsLoading(true);
setError('');
if (deleteAccount !== DELETE_ACCOUNT_VERIFICATION) {
setError('Verification text does not match');
setIsLoading(false);
return;
}
const { response, error } = await httpDelete(
`${import.meta.env.PUBLIC_API_URL}/v1-delete-account`
);
if (error || !response) {
setIsLoading(false);
setError(error?.message || 'Something went wrong');
return;
}
Cookies.remove(TOKEN_COOKIE_NAME);
window.location.href = '/';
};
const handleClosePopup = () => {
setIsLoading(false);
setError('');
setDeleteAccount('');
hideDeleteAccountPopup();
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="delete-account" className="text-sm text-gray-500">
To verify, type{' '}
<span className="font-medium text-gray-600">
{DELETE_ACCOUNT_VERIFICATION}
</span>{' '}
below:
</label>
<input
type="text"
name="delete-account"
id="delete-account"
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
required
autoFocus
value={deleteAccount}
onInput={(e) =>
setDeleteAccount((e.target as HTMLInputElement).value)
}
/>
{error && (
<p className="mt-2 rounded-lg bg-red-100 p-2 text-red-700">{error}</p>
)}
</div>
<div className="mt-6 flex items-center justify-between gap-2">
<button
type="button"
disabled={isLoading}
onClick={handleClosePopup}
className="flex h-10 items-center justify-center rounded-md bg-gray-100 px-4 py-2 text-sm font-medium text-black shadow-sm hover:bg-gray-100/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-200 disabled:pointer-events-none disabled:opacity-50"
>
Cancel
</button>
<button
type="submit"
disabled={isLoading}
className="flex h-10 items-center justify-center rounded-md bg-red-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-500/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-red-200 disabled:pointer-events-none disabled:opacity-50"
>
{isLoading ? 'Please wait...' : 'Delete Account'}
</button>
</div>
</form>
);
}

@ -0,0 +1,24 @@
---
import Popup from '../Popup/Popup.astro';
import { DeleteAccountForm } from './DeleteAccountForm';
---
<Popup id='delete-account-popup' title='' subtitle=''>
<div class='-mt-2.5'>
<h2 class='mb-3 text-2xl font-semibold leading-5 text-gray-900'>
Delete Account
</h2>
<p class='text-sm text-gray-600'>
Deleting your account will remove all your data from our servers
(including your progress).
</p>
<p class='rounded-md bg-yellow-300 p-2 text-xs text-yellow-800 mt-6'>
<span class='font-medium'>Warning:</span> This action is not reversible. Please
be certain.
</p>
<div class='mt-6 flex flex-col gap-1.5'>
<DeleteAccountForm client:load />
</div>
</div>
</Popup>

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1,22 @@
---
import AccountSidebar from '../../components/AccountSidebar.astro';
import UpdatePasswordForm from '../../components/UpdatePassword/UpdatePasswordForm';
import AccountLayout from '../../layouts/AccountLayout.astro';
import { DeleteAccount } from '../../components/DeleteAccount/DeleteAccount';
import DeleteAccountPopup from '../../components/DeleteAccount/DeleteAccountPopup.astro';
---
<AccountLayout
title='Settings'
description=''
noIndex={true}
initialLoadingMessage={'Loading settings'}
>
<AccountSidebar activePageId='settings' activePageTitle='Settings'>
<UpdatePasswordForm client:load />
<hr class='my-10' />
<DeleteAccount client:load />
</AccountSidebar>
<DeleteAccountPopup />
</AccountLayout>

@ -1,16 +0,0 @@
---
import AccountSidebar from '../../components/AccountSidebar.astro';
import UpdatePasswordForm from '../../components/UpdatePassword/UpdatePasswordForm';
import AccountLayout from '../../layouts/AccountLayout.astro';
---
<AccountLayout
title='Change Password'
description=''
noIndex={true}
initialLoadingMessage={'Loading profile'}
>
<AccountSidebar activePageId='change-password' activePageTitle='Change Password'>
<UpdatePasswordForm client:load />
</AccountSidebar>
</AccountLayout>
Loading…
Cancel
Save