mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
delete account modal
This commit is contained in:
parent
fa29da1757
commit
3434074b1e
6 changed files with 100 additions and 67 deletions
|
@ -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>
|
||||
);
|
||||
}
|
42
src/components/overlays/Modal.tsx
Normal file
42
src/components/overlays/Modal.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -29,14 +29,16 @@ export function OverlayDisplay(props: { children: ReactNode }) {
|
|||
return <div className="popout-location">{props.children}</div>;
|
||||
}
|
||||
|
||||
export function Overlay(props: OverlayProps) {
|
||||
const router = useInternalOverlayRouter(props.id);
|
||||
export function OverlayPortal(props: {
|
||||
children?: ReactNode;
|
||||
darken?: boolean;
|
||||
show?: boolean;
|
||||
close?: () => void;
|
||||
}) {
|
||||
const [portalElement, setPortalElement] = useState<Element | null>(null);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const target = useRef<Element | null>(null);
|
||||
|
||||
// listen for anchor updates
|
||||
useRouterAnchorUpdate(props.id);
|
||||
const close = props.close;
|
||||
|
||||
useEffect(() => {
|
||||
function listen(e: MouseEvent) {
|
||||
|
@ -55,9 +57,9 @@ export function Overlay(props: OverlayProps) {
|
|||
if (e.currentTarget !== e.target) return;
|
||||
if (!startedTarget) return;
|
||||
if (!startedTarget.isEqualNode(e.currentTarget as Element)) return;
|
||||
router.close();
|
||||
close?.();
|
||||
},
|
||||
[router]
|
||||
[close]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -69,8 +71,8 @@ export function Overlay(props: OverlayProps) {
|
|||
<div ref={ref}>
|
||||
{portalElement
|
||||
? createPortal(
|
||||
<Transition show={router.isOverlayActive()} animation="none">
|
||||
<div className="popout-wrapper absolute overflow-hidden pointer-events-auto inset-0 z-[999] select-none">
|
||||
<Transition show={props.show} animation="none">
|
||||
<div className="popout-wrapper fixed overflow-hidden pointer-events-auto inset-0 z-[999] select-none">
|
||||
<Transition animation="fade" isChild>
|
||||
<div
|
||||
onClick={click}
|
||||
|
@ -95,3 +97,20 @@ export function Overlay(props: OverlayProps) {
|
|||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ function joinPath(path: string[]): string {
|
|||
}
|
||||
|
||||
export function useRouterAnchorUpdate(id: string) {
|
||||
const setAnchorPoint = useOverlayStore((s) => s.setAnchorPoint);
|
||||
const [route] = useQueryParam("r");
|
||||
const setAnchorPoint = useOverlayStore((s) => s.setAnchorPoint);
|
||||
const routerActive = useMemo(
|
||||
() => !!route && route.startsWith(`/${id}`),
|
||||
[route, id]
|
||||
|
|
|
@ -3,7 +3,8 @@ import { useAsyncFn } from "react-use";
|
|||
import { deleteUser } from "@/backend/accounts/user";
|
||||
import { Button } from "@/components/Button";
|
||||
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 { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
@ -12,13 +13,14 @@ export function AccountActionsPart() {
|
|||
const url = useBackendUrl();
|
||||
const account = useAuthStore((s) => s.account);
|
||||
const { logout } = useAuthData();
|
||||
const deleteModal = useModal("account-delete");
|
||||
|
||||
const [deleteResult, deleteExec] = useAsyncFn(async () => {
|
||||
if (!account) return;
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
if (!confirm("You sure bro?")) return;
|
||||
await deleteUser(url, account);
|
||||
logout();
|
||||
}, [logout, account, url]);
|
||||
await logout();
|
||||
deleteModal.hide();
|
||||
}, [logout, account, url, deleteModal.hide]);
|
||||
|
||||
if (!account) return null;
|
||||
|
||||
|
@ -39,13 +41,29 @@ export function AccountActionsPart() {
|
|||
<div className="flex justify-start lg:justify-end items-center">
|
||||
<Button
|
||||
theme="danger"
|
||||
onClick={deleteExec}
|
||||
loading={deleteResult.loading}
|
||||
onClick={deleteModal.show}
|
||||
>
|
||||
Delete account
|
||||
</Button>
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,10 @@ body[data-no-select] {
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
html[data-no-scroll], html[data-no-scroll] body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.roll {
|
||||
animation: roll 1s;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue