Loading packages/client/src/components/App.tsx +2 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ import { ProfileModal } from "./Profile/ProfileModal"; import { WelcomeModal } from "./Welcome/WelcomeModal"; import { InfoSidebar } from "./Info/InfoSidebar"; import { ModModal } from "./Moderation/ModModal"; import { DynamicModals } from "./DynamicModals"; const Chat = lazy(() => import("./Chat/Chat")); Loading Loading @@ -152,6 +153,7 @@ const AppInner = () => { <ModModal /> <ToastContainer position="top-left" /> <DynamicModals /> </> ); }; Loading packages/client/src/components/AuthErrors.tsx +40 −3 Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ export const AuthErrors = () => { const onClose = () => { const url = new URL(window.location.href); url.search = ""; // window.history.replaceState({}, "", url.toString()); window.history.replaceState({}, "", url.toString()); setParams(new URLSearchParams(window.location.search)); }; Loading @@ -45,7 +45,44 @@ export const AuthErrors = () => { onClose={onClose} params={params} /> <BannedError isOpen={params.get(Params.TYPE) === "banned"} onClose={onClose} params={params} /> </> ); }; const BannedError = ({ isOpen, onClose, params, }: { isOpen: boolean; onClose: () => void; params: URLSearchParams; }) => { return ( <Modal isOpen={isOpen} onClose={onClose} isDismissable={false}> <ModalContent> {(onClose) => ( <> <ModalHeader>Login Error</ModalHeader> <ModalBody> <b>Your instance is banned.</b> You cannot proceed. <br /> <br /> {params.has(Params.ERROR_DESC) ? ( <>Reason: {params.get(Params.ERROR_DESC)}</> ) : ( <>No reason provided</> )} </ModalBody> </> )} </ModalContent> </Modal> ); }; Loading @@ -67,7 +104,7 @@ const RPError = ({ params: URLSearchParams; }) => { return ( <Modal isOpen={isOpen} onOpenChange={onClose} isDismissable={false}> <Modal isOpen={isOpen} onClose={onClose} isDismissable={false}> <ModalContent> {(onClose) => ( <> Loading Loading @@ -117,7 +154,7 @@ const OPError = ({ }, [params]); return ( <Modal isOpen={isOpen} onOpenChange={onClose} isDismissable={false}> <Modal isOpen={isOpen} onClose={onClose} isDismissable={false}> <ModalContent> {(onClose) => ( <> Loading packages/client/src/components/DynamicModals.tsx 0 → 100644 +99 −0 Original line number Diff line number Diff line import { useCallback, useEffect, useState } from "react"; import { DynamicModal, IDynamicModal } from "../lib/alerts"; import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, } from "@nextui-org/react"; interface IModal { id: number; open: boolean; modal: IDynamicModal; } /** * React base to hold dynamic modals * * Dynamic modals are created via lib/alerts.tsx * * @returns */ export const DynamicModals = () => { const [modals, setModals] = useState<IModal[]>([]); const handleShowModal = useCallback( (modal: IDynamicModal) => { setModals((modals) => [ ...modals, { id: Math.floor(Math.random() * 9999), open: true, modal, }, ]); }, [setModals] ); const handleHideModal = useCallback( (modalId: number) => { setModals((modals_) => { const modals = [...modals_]; if (modals.find((m) => m.id === modalId)) { modals.find((m) => m.id === modalId)!.open = false; } return modals; }); setTimeout(() => { setModals((modals_) => { const modals = [...modals_]; if (modals.find((m) => m.id === modalId)) { modals.splice( modals.indexOf(modals.find((m) => m.id === modalId)!), 1 ); } return modals; }); }, 1000); }, [setModals] ); useEffect(() => { DynamicModal.on("showModal", handleShowModal); return () => { DynamicModal.off("showModal", handleShowModal); }; }, []); return ( <> {modals.map(({ id, open, modal }) => ( <Modal key={id} isOpen={open} onClose={() => handleHideModal(id)}> <ModalContent> {(onClose) => ( <> <ModalHeader>{modal.title}</ModalHeader> <ModalBody>{modal.body}</ModalBody> <ModalFooter> <Button onClick={onClose}>Close</Button> </ModalFooter> </> )} </ModalContent> </Modal> ))} </> ); }; packages/client/src/components/Header/AccountStanding.tsx 0 → 100644 +75 −0 Original line number Diff line number Diff line import { IAccountStanding } from "@sc07-canvas/lib/src/net"; import { useCallback, useEffect, useState } from "react"; import network from "../../lib/network"; import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, } from "@nextui-org/react"; export const AccountStanding = () => { const [standingInfo, setStandingInfo] = useState(false); const [standing, setStanding] = useState<IAccountStanding | undefined>( network.getState("standing")?.[0] ); const handleStanding = useCallback( (standing: IAccountStanding) => { setStanding(standing); }, [setStanding] ); useEffect(() => { network.on("standing", handleStanding); return () => { network.off("standing", handleStanding); }; }, []); return ( <> {standing?.banned && ( <div className="bg-red-500 bg-opacity-85 border-red-700 border-1 rounded-md p-1 flex items-center gap-2"> You are banned <br /> <Button size="sm" onPress={() => setStandingInfo(true)}> Details </Button> </div> )} <Modal isOpen={standingInfo} onClose={() => setStandingInfo(false)}> <ModalContent> {(onClose) => ( <> <ModalHeader>Account Standing</ModalHeader> <ModalBody> {standing?.banned ? ( <> You are banned until {standing.until} <br /> {standing.reason ? ( <>Public reason given: {standing.reason}</> ) : ( <>No reason given</> )} </> ) : ( <>Your account is in good standing</> )} </ModalBody> <ModalFooter> <Button onPress={onClose}>Close</Button> </ModalFooter> </> )} </ModalContent> </Modal> </> ); }; packages/client/src/components/Header/Header.tsx +2 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ import { useAppContext } from "../../contexts/AppContext"; import { User } from "./User"; import { Debug } from "@sc07-canvas/lib/src/debug"; import React, { lazy } from "react"; import { AccountStanding } from "./AccountStanding"; const OpenChatButton = lazy(() => import("../Chat/OpenChatButton")); Loading Loading @@ -37,6 +38,7 @@ const HeaderLeft = () => { return ( <div className="box"> <AccountStanding /> <Button onPress={() => setInfoSidebar(true)}>Info</Button> <Button onPress={() => Debug.openDebugTools()}>Debug Tools</Button> </div> Loading Loading
packages/client/src/components/App.tsx +2 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ import { ProfileModal } from "./Profile/ProfileModal"; import { WelcomeModal } from "./Welcome/WelcomeModal"; import { InfoSidebar } from "./Info/InfoSidebar"; import { ModModal } from "./Moderation/ModModal"; import { DynamicModals } from "./DynamicModals"; const Chat = lazy(() => import("./Chat/Chat")); Loading Loading @@ -152,6 +153,7 @@ const AppInner = () => { <ModModal /> <ToastContainer position="top-left" /> <DynamicModals /> </> ); }; Loading
packages/client/src/components/AuthErrors.tsx +40 −3 Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ export const AuthErrors = () => { const onClose = () => { const url = new URL(window.location.href); url.search = ""; // window.history.replaceState({}, "", url.toString()); window.history.replaceState({}, "", url.toString()); setParams(new URLSearchParams(window.location.search)); }; Loading @@ -45,7 +45,44 @@ export const AuthErrors = () => { onClose={onClose} params={params} /> <BannedError isOpen={params.get(Params.TYPE) === "banned"} onClose={onClose} params={params} /> </> ); }; const BannedError = ({ isOpen, onClose, params, }: { isOpen: boolean; onClose: () => void; params: URLSearchParams; }) => { return ( <Modal isOpen={isOpen} onClose={onClose} isDismissable={false}> <ModalContent> {(onClose) => ( <> <ModalHeader>Login Error</ModalHeader> <ModalBody> <b>Your instance is banned.</b> You cannot proceed. <br /> <br /> {params.has(Params.ERROR_DESC) ? ( <>Reason: {params.get(Params.ERROR_DESC)}</> ) : ( <>No reason provided</> )} </ModalBody> </> )} </ModalContent> </Modal> ); }; Loading @@ -67,7 +104,7 @@ const RPError = ({ params: URLSearchParams; }) => { return ( <Modal isOpen={isOpen} onOpenChange={onClose} isDismissable={false}> <Modal isOpen={isOpen} onClose={onClose} isDismissable={false}> <ModalContent> {(onClose) => ( <> Loading Loading @@ -117,7 +154,7 @@ const OPError = ({ }, [params]); return ( <Modal isOpen={isOpen} onOpenChange={onClose} isDismissable={false}> <Modal isOpen={isOpen} onClose={onClose} isDismissable={false}> <ModalContent> {(onClose) => ( <> Loading
packages/client/src/components/DynamicModals.tsx 0 → 100644 +99 −0 Original line number Diff line number Diff line import { useCallback, useEffect, useState } from "react"; import { DynamicModal, IDynamicModal } from "../lib/alerts"; import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, } from "@nextui-org/react"; interface IModal { id: number; open: boolean; modal: IDynamicModal; } /** * React base to hold dynamic modals * * Dynamic modals are created via lib/alerts.tsx * * @returns */ export const DynamicModals = () => { const [modals, setModals] = useState<IModal[]>([]); const handleShowModal = useCallback( (modal: IDynamicModal) => { setModals((modals) => [ ...modals, { id: Math.floor(Math.random() * 9999), open: true, modal, }, ]); }, [setModals] ); const handleHideModal = useCallback( (modalId: number) => { setModals((modals_) => { const modals = [...modals_]; if (modals.find((m) => m.id === modalId)) { modals.find((m) => m.id === modalId)!.open = false; } return modals; }); setTimeout(() => { setModals((modals_) => { const modals = [...modals_]; if (modals.find((m) => m.id === modalId)) { modals.splice( modals.indexOf(modals.find((m) => m.id === modalId)!), 1 ); } return modals; }); }, 1000); }, [setModals] ); useEffect(() => { DynamicModal.on("showModal", handleShowModal); return () => { DynamicModal.off("showModal", handleShowModal); }; }, []); return ( <> {modals.map(({ id, open, modal }) => ( <Modal key={id} isOpen={open} onClose={() => handleHideModal(id)}> <ModalContent> {(onClose) => ( <> <ModalHeader>{modal.title}</ModalHeader> <ModalBody>{modal.body}</ModalBody> <ModalFooter> <Button onClick={onClose}>Close</Button> </ModalFooter> </> )} </ModalContent> </Modal> ))} </> ); };
packages/client/src/components/Header/AccountStanding.tsx 0 → 100644 +75 −0 Original line number Diff line number Diff line import { IAccountStanding } from "@sc07-canvas/lib/src/net"; import { useCallback, useEffect, useState } from "react"; import network from "../../lib/network"; import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, } from "@nextui-org/react"; export const AccountStanding = () => { const [standingInfo, setStandingInfo] = useState(false); const [standing, setStanding] = useState<IAccountStanding | undefined>( network.getState("standing")?.[0] ); const handleStanding = useCallback( (standing: IAccountStanding) => { setStanding(standing); }, [setStanding] ); useEffect(() => { network.on("standing", handleStanding); return () => { network.off("standing", handleStanding); }; }, []); return ( <> {standing?.banned && ( <div className="bg-red-500 bg-opacity-85 border-red-700 border-1 rounded-md p-1 flex items-center gap-2"> You are banned <br /> <Button size="sm" onPress={() => setStandingInfo(true)}> Details </Button> </div> )} <Modal isOpen={standingInfo} onClose={() => setStandingInfo(false)}> <ModalContent> {(onClose) => ( <> <ModalHeader>Account Standing</ModalHeader> <ModalBody> {standing?.banned ? ( <> You are banned until {standing.until} <br /> {standing.reason ? ( <>Public reason given: {standing.reason}</> ) : ( <>No reason given</> )} </> ) : ( <>Your account is in good standing</> )} </ModalBody> <ModalFooter> <Button onPress={onClose}>Close</Button> </ModalFooter> </> )} </ModalContent> </Modal> </> ); };
packages/client/src/components/Header/Header.tsx +2 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ import { useAppContext } from "../../contexts/AppContext"; import { User } from "./User"; import { Debug } from "@sc07-canvas/lib/src/debug"; import React, { lazy } from "react"; import { AccountStanding } from "./AccountStanding"; const OpenChatButton = lazy(() => import("../Chat/OpenChatButton")); Loading Loading @@ -37,6 +38,7 @@ const HeaderLeft = () => { return ( <div className="box"> <AccountStanding /> <Button onPress={() => setInfoSidebar(true)}>Info</Button> <Button onPress={() => Debug.openDebugTools()}>Debug Tools</Button> </div> Loading