mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +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>;
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -218,4 +222,4 @@ input[type=range].styled-slider.slider-progress::-ms-fill-lower {
|
||||||
.tabbable:focus-visible {
|
.tabbable:focus-visible {
|
||||||
outline: 2px solid theme('colors.themePreview.primary');
|
outline: 2px solid theme('colors.themePreview.primary');
|
||||||
box-shadow: 0 0 10px theme('colors.themePreview.secondary');
|
box-shadow: 0 0 10px theme('colors.themePreview.secondary');
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue