Commit 29feba06 authored by Grant's avatar Grant
Browse files

[wip] implement user & instance banning (related #17)

parent 0ad4cd4f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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"));

@@ -152,6 +153,7 @@ const AppInner = () => {
      <ModModal />

      <ToastContainer position="top-left" />
      <DynamicModals />
    </>
  );
};
+40 −3
Original line number Diff line number Diff line
@@ -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));
  };
@@ -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>
  );
};

@@ -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) => (
          <>
@@ -117,7 +154,7 @@ const OPError = ({
  }, [params]);

  return (
    <Modal isOpen={isOpen} onOpenChange={onClose} isDismissable={false}>
    <Modal isOpen={isOpen} onClose={onClose} isDismissable={false}>
      <ModalContent>
        {(onClose) => (
          <>
+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>
      ))}
    </>
  );
};
+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>
    </>
  );
};
+2 −0
Original line number Diff line number Diff line
@@ -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"));

@@ -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