delete account modal

This commit is contained in:
mrjvs 2023-11-22 15:04:58 +01:00
parent fa29da1757
commit 3434074b1e
6 changed files with 100 additions and 67 deletions

View File

@ -1,50 +0,0 @@
import { ReactNode } from "react";
import { createPortal } from "react-dom";
import { Overlay } from "@/components/Overlay";
import { Transition } from "@/components/Transition";
interface Props {
show: boolean;
children?: ReactNode;
}
export function ModalFrame(props: Props) {
return (
<Transition
className="fixed inset-0 z-[9999]"
animation="none"
show={props.show}
>
<Overlay>
<Transition
isChild
className="flex h-full w-full items-center justify-center"
animation="slide-up"
>
{props.children}
</Transition>
</Overlay>
</Transition>
);
}
export function Modal(props: Props) {
return createPortal(
<ModalFrame show={props.show}>{props.children}</ModalFrame>,
document.body
);
}
export function ModalCard(props: { className?: string; children?: ReactNode }) {
return (
<div
className={[
"relative mx-2 w-[500px] overflow-hidden rounded-lg bg-denim-300 px-10 py-10 sm:w-[500px] md:w-[500px] lg:w-[1000px]",
props.className ?? "",
].join(" ")}
>
{props.children}
</div>
);
}

View File

@ -0,0 +1,42 @@
import { ReactNode, useCallback } from "react";
import { Helmet } from "react-helmet-async";
import { OverlayPortal } from "@/components/overlays/OverlayDisplay";
import { useQueryParam } from "@/hooks/useQueryParams";
export function useModal(id: string) {
const [currentModal, setCurrentModal] = useQueryParam("m");
const show = useCallback(() => setCurrentModal(id), [id, setCurrentModal]);
const hide = useCallback(() => setCurrentModal(null), [setCurrentModal]);
return {
id,
isShown: currentModal === id,
show,
hide,
};
}
export function ModalCard(props: { children?: ReactNode }) {
return (
<div className="w-[30rem] max-w-full">
<div className="w-full bg-dropdown-background rounded-xl p-8 m-4 pointer-events-auto">
{props.children}
</div>
</div>
);
}
export function Modal(props: { id: string; children?: ReactNode }) {
const modal = useModal(props.id);
return (
<OverlayPortal darken close={modal.hide} show={modal.isShown}>
<Helmet>
<html data-no-scroll />
</Helmet>
<div className="flex absolute inset-0 items-center justify-center flex-col">
{props.children}
</div>
</OverlayPortal>
);
}

View File

@ -29,14 +29,16 @@ export function OverlayDisplay(props: { children: ReactNode }) {
return <div className="popout-location">{props.children}</div>; return <div className="popout-location">{props.children}</div>;
} }
export function Overlay(props: OverlayProps) { export function OverlayPortal(props: {
const router = useInternalOverlayRouter(props.id); children?: ReactNode;
darken?: boolean;
show?: boolean;
close?: () => void;
}) {
const [portalElement, setPortalElement] = useState<Element | null>(null); const [portalElement, setPortalElement] = useState<Element | null>(null);
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const target = useRef<Element | null>(null); const target = useRef<Element | null>(null);
const close = props.close;
// listen for anchor updates
useRouterAnchorUpdate(props.id);
useEffect(() => { useEffect(() => {
function listen(e: MouseEvent) { function listen(e: MouseEvent) {
@ -55,9 +57,9 @@ export function Overlay(props: OverlayProps) {
if (e.currentTarget !== e.target) return; if (e.currentTarget !== e.target) return;
if (!startedTarget) return; if (!startedTarget) return;
if (!startedTarget.isEqualNode(e.currentTarget as Element)) return; if (!startedTarget.isEqualNode(e.currentTarget as Element)) return;
router.close(); close?.();
}, },
[router] [close]
); );
useEffect(() => { useEffect(() => {
@ -69,8 +71,8 @@ export function Overlay(props: OverlayProps) {
<div ref={ref}> <div ref={ref}>
{portalElement {portalElement
? createPortal( ? createPortal(
<Transition show={router.isOverlayActive()} animation="none"> <Transition show={props.show} animation="none">
<div className="popout-wrapper absolute overflow-hidden pointer-events-auto inset-0 z-[999] select-none"> <div className="popout-wrapper fixed overflow-hidden pointer-events-auto inset-0 z-[999] select-none">
<Transition animation="fade" isChild> <Transition animation="fade" isChild>
<div <div
onClick={click} onClick={click}
@ -95,3 +97,20 @@ export function Overlay(props: OverlayProps) {
</div> </div>
); );
} }
export function Overlay(props: OverlayProps) {
const router = useInternalOverlayRouter(props.id);
// listen for anchor updates
useRouterAnchorUpdate(props.id);
return (
<OverlayPortal
close={router.close}
show={router.isOverlayActive()}
darken={props.darken}
>
{props.children}
</OverlayPortal>
);
}

View File

@ -13,8 +13,8 @@ function joinPath(path: string[]): string {
} }
export function useRouterAnchorUpdate(id: string) { export function useRouterAnchorUpdate(id: string) {
const setAnchorPoint = useOverlayStore((s) => s.setAnchorPoint);
const [route] = useQueryParam("r"); const [route] = useQueryParam("r");
const setAnchorPoint = useOverlayStore((s) => s.setAnchorPoint);
const routerActive = useMemo( const routerActive = useMemo(
() => !!route && route.startsWith(`/${id}`), () => !!route && route.startsWith(`/${id}`),
[route, id] [route, id]

View File

@ -3,7 +3,8 @@ import { useAsyncFn } from "react-use";
import { deleteUser } from "@/backend/accounts/user"; import { deleteUser } from "@/backend/accounts/user";
import { Button } from "@/components/Button"; import { Button } from "@/components/Button";
import { SolidSettingsCard } from "@/components/layout/SettingsCard"; import { SolidSettingsCard } from "@/components/layout/SettingsCard";
import { Heading2, Heading3 } from "@/components/utils/Text"; import { Modal, ModalCard, useModal } from "@/components/overlays/Modal";
import { Heading2, Heading3, Paragraph } from "@/components/utils/Text";
import { useAuthData } from "@/hooks/auth/useAuthData"; import { useAuthData } from "@/hooks/auth/useAuthData";
import { useBackendUrl } from "@/hooks/auth/useBackendUrl"; import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
@ -12,13 +13,14 @@ export function AccountActionsPart() {
const url = useBackendUrl(); const url = useBackendUrl();
const account = useAuthStore((s) => s.account); const account = useAuthStore((s) => s.account);
const { logout } = useAuthData(); const { logout } = useAuthData();
const deleteModal = useModal("account-delete");
const [deleteResult, deleteExec] = useAsyncFn(async () => { const [deleteResult, deleteExec] = useAsyncFn(async () => {
if (!account) return; if (!account) return;
// eslint-disable-next-line no-restricted-globals
if (!confirm("You sure bro?")) return;
await deleteUser(url, account); await deleteUser(url, account);
logout(); await logout();
}, [logout, account, url]); deleteModal.hide();
}, [logout, account, url, deleteModal.hide]);
if (!account) return null; if (!account) return null;
@ -39,13 +41,29 @@ export function AccountActionsPart() {
<div className="flex justify-start lg:justify-end items-center"> <div className="flex justify-start lg:justify-end items-center">
<Button <Button
theme="danger" theme="danger"
onClick={deleteExec}
loading={deleteResult.loading} loading={deleteResult.loading}
onClick={deleteModal.show}
> >
Delete account Delete account
</Button> </Button>
</div> </div>
</SolidSettingsCard> </SolidSettingsCard>
<Modal id={deleteModal.id}>
<ModalCard>
<Heading2 className="!mt-0">Are you sure?</Heading2>
<Paragraph>
Are you sure you want to delete your account? All your data will be
lost!
</Paragraph>
<Button
theme="danger"
loading={deleteResult.loading}
onClick={deleteExec}
>
Delete account
</Button>
</ModalCard>
</Modal>
</div> </div>
); );
} }

View File

@ -31,6 +31,10 @@ body[data-no-select] {
user-select: none; user-select: none;
} }
html[data-no-scroll], html[data-no-scroll] body {
overflow: hidden;
}
.roll { .roll {
animation: roll 1s; animation: roll 1s;
} }