mirror of
https://github.com/stoatchat/for-legacy-web.git
synced 2026-03-09 10:15:26 +00:00
feat(modal): port Error and ShowToken
This commit is contained in:
@@ -24,7 +24,7 @@ import {
|
|||||||
import { useApplicationState } from "../../../mobx/State";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
import { Reply } from "../../../mobx/stores/MessageQueue";
|
import { Reply } from "../../../mobx/stores/MessageQueue";
|
||||||
|
|
||||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
import { modalController } from "../../../context/modals";
|
||||||
import {
|
import {
|
||||||
FileUploader,
|
FileUploader,
|
||||||
grabFiles,
|
grabFiles,
|
||||||
@@ -148,7 +148,6 @@ export default observer(({ channel }: Props) => {
|
|||||||
});
|
});
|
||||||
const [typing, setTyping] = useState<boolean | number>(false);
|
const [typing, setTyping] = useState<boolean | number>(false);
|
||||||
const [replies, setReplies] = useState<Reply[]>([]);
|
const [replies, setReplies] = useState<Reply[]>([]);
|
||||||
const { openScreen } = useIntermediate();
|
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const translate = useTranslation();
|
const translate = useTranslation();
|
||||||
|
|
||||||
@@ -473,7 +472,10 @@ export default observer(({ channel }: Props) => {
|
|||||||
files: [...uploadState.files, ...files],
|
files: [...uploadState.files, ...files],
|
||||||
}),
|
}),
|
||||||
() =>
|
() =>
|
||||||
openScreen({ id: "error", error: "FileTooLarge" }),
|
modalController.push({
|
||||||
|
type: "error",
|
||||||
|
error: "FileTooLarge",
|
||||||
|
}),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { internalEmit } from "../../lib/eventEmitter";
|
|
||||||
|
|
||||||
//import { isModalClosing } from "../../components/ui/Modal";
|
//import { isModalClosing } from "../../components/ui/Modal";
|
||||||
import { Screen } from "./Intermediate";
|
import { Screen } from "./Intermediate";
|
||||||
import { ClipboardModal } from "./modals/Clipboard";
|
import { ClipboardModal } from "./modals/Clipboard";
|
||||||
import { ErrorModal } from "./modals/Error";
|
|
||||||
import { ExternalLinkModal } from "./modals/ExternalLinkPrompt";
|
import { ExternalLinkModal } from "./modals/ExternalLinkPrompt";
|
||||||
import { InputModal } from "./modals/Input";
|
import { InputModal } from "./modals/Input";
|
||||||
import { OnboardingModal } from "./modals/Onboarding";
|
import { OnboardingModal } from "./modals/Onboarding";
|
||||||
import { PromptModal } from "./modals/Prompt";
|
import { PromptModal } from "./modals/Prompt";
|
||||||
import { TokenRevealModal } from "./modals/TokenReveal";
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
screen: Screen;
|
screen: Screen;
|
||||||
@@ -26,12 +22,8 @@ export default function Modals({ screen, openScreen }: Props) {
|
|||||||
return <PromptModal onClose={onClose} {...screen} />;
|
return <PromptModal onClose={onClose} {...screen} />;
|
||||||
case "_input":
|
case "_input":
|
||||||
return <InputModal onClose={onClose} {...screen} />;
|
return <InputModal onClose={onClose} {...screen} />;
|
||||||
case "error":
|
|
||||||
return <ErrorModal onClose={onClose} {...screen} />;
|
|
||||||
case "clipboard":
|
case "clipboard":
|
||||||
return <ClipboardModal onClose={onClose} {...screen} />;
|
return <ClipboardModal onClose={onClose} {...screen} />;
|
||||||
case "token_reveal":
|
|
||||||
return <TokenRevealModal onClose={onClose} {...screen} />;
|
|
||||||
case "onboarding":
|
case "onboarding":
|
||||||
return <OnboardingModal onClose={onClose} {...screen} />;
|
return <OnboardingModal onClose={onClose} {...screen} />;
|
||||||
case "external_link_prompt":
|
case "external_link_prompt":
|
||||||
|
|||||||
@@ -2,23 +2,23 @@ import { Text } from "preact-i18n";
|
|||||||
|
|
||||||
import { Modal } from "@revoltchat/ui";
|
import { Modal } from "@revoltchat/ui";
|
||||||
|
|
||||||
interface Props {
|
import { noopTrue } from "../../../lib/js";
|
||||||
onClose: () => void;
|
|
||||||
error: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ErrorModal({ onClose, error }: Props) {
|
import { ModalProps } from "../types";
|
||||||
|
|
||||||
|
export function Error({ error, ...props }: ModalProps<"error">) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
onClose={onClose}
|
{...props}
|
||||||
title={<Text id="app.special.modals.error" />}
|
title={<Text id="app.special.modals.error" />}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
onClick: onClose,
|
onClick: noopTrue,
|
||||||
confirmation: true,
|
confirmation: true,
|
||||||
children: <Text id="app.special.modals.actions.ok" />,
|
children: <Text id="app.special.modals.actions.ok" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
palette: "plain-secondary",
|
||||||
onClick: () => location.reload(),
|
onClick: () => location.reload(),
|
||||||
children: <Text id="app.special.modals.actions.reload" />,
|
children: <Text id="app.special.modals.actions.reload" />,
|
||||||
},
|
},
|
||||||
@@ -2,25 +2,23 @@ import { Text } from "preact-i18n";
|
|||||||
|
|
||||||
import { Modal } from "@revoltchat/ui";
|
import { Modal } from "@revoltchat/ui";
|
||||||
|
|
||||||
interface Props {
|
import { noopTrue } from "../../../lib/js";
|
||||||
onClose: () => void;
|
|
||||||
token: string;
|
|
||||||
username: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TokenRevealModal({ onClose, token, username }: Props) {
|
import { ModalProps } from "../types";
|
||||||
|
|
||||||
|
export function ShowToken({ name, token, ...props }: ModalProps<"show_token">) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
onClose={onClose}
|
{...props}
|
||||||
title={
|
title={
|
||||||
<Text
|
<Text
|
||||||
id={"app.special.modals.token_reveal"}
|
id={"app.special.modals.token_reveal"}
|
||||||
fields={{ name: username }}
|
fields={{ name }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
onClick: onClose,
|
onClick: noopTrue,
|
||||||
confirmation: true,
|
confirmation: true,
|
||||||
children: <Text id="app.special.modals.actions.close" />,
|
children: <Text id="app.special.modals.actions.close" />,
|
||||||
},
|
},
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { Modal } from "@revoltchat/ui";
|
|
||||||
|
|
||||||
import { ModalProps } from "../types";
|
|
||||||
|
|
||||||
export default function Test({ onClose }: ModalProps<"test">) {
|
|
||||||
return <Modal title="I am a sub modal!" onClose={onClose} />;
|
|
||||||
}
|
|
||||||
@@ -9,13 +9,14 @@ import type { Client, API } from "revolt.js";
|
|||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
|
||||||
import Changelog from "./components/Changelog";
|
import Changelog from "./components/Changelog";
|
||||||
|
import { Error } from "./components/Error";
|
||||||
import MFAEnableTOTP from "./components/MFAEnableTOTP";
|
import MFAEnableTOTP from "./components/MFAEnableTOTP";
|
||||||
import MFAFlow from "./components/MFAFlow";
|
import MFAFlow from "./components/MFAFlow";
|
||||||
import MFARecovery from "./components/MFARecovery";
|
import MFARecovery from "./components/MFARecovery";
|
||||||
import OutOfDate from "./components/OutOfDate";
|
import OutOfDate from "./components/OutOfDate";
|
||||||
|
import { ShowToken } from "./components/ShowToken";
|
||||||
import { SignOutSessions } from "./components/SignOutSessions";
|
import { SignOutSessions } from "./components/SignOutSessions";
|
||||||
import { SignedOut } from "./components/SignedOut";
|
import { SignedOut } from "./components/SignedOut";
|
||||||
import Test from "./components/Test";
|
|
||||||
import { Modal } from "./types";
|
import { Modal } from "./types";
|
||||||
|
|
||||||
type Components = Record<string, React.FC<any>>;
|
type Components = Record<string, React.FC<any>>;
|
||||||
@@ -92,6 +93,9 @@ class ModalController<T extends Modal> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a modal is currently visible
|
||||||
|
*/
|
||||||
get isVisible() {
|
get isVisible() {
|
||||||
return this.stack.length > 0;
|
return this.stack.length > 0;
|
||||||
}
|
}
|
||||||
@@ -140,11 +144,12 @@ class ModalControllerExtended extends ModalController<Modal> {
|
|||||||
|
|
||||||
export const modalController = new ModalControllerExtended({
|
export const modalController = new ModalControllerExtended({
|
||||||
changelog: Changelog,
|
changelog: Changelog,
|
||||||
|
error: Error,
|
||||||
mfa_flow: MFAFlow,
|
mfa_flow: MFAFlow,
|
||||||
mfa_recovery: MFARecovery,
|
mfa_recovery: MFARecovery,
|
||||||
mfa_enable_totp: MFAEnableTOTP,
|
mfa_enable_totp: MFAEnableTOTP,
|
||||||
out_of_date: OutOfDate,
|
out_of_date: OutOfDate,
|
||||||
|
show_token: ShowToken,
|
||||||
signed_out: SignedOut,
|
signed_out: SignedOut,
|
||||||
sign_out_sessions: SignOutSessions,
|
sign_out_sessions: SignOutSessions,
|
||||||
test: Test,
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,13 +33,22 @@ export type Modal = {
|
|||||||
initial?: number;
|
initial?: number;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
type: "sign_out_sessions";
|
||||||
client: Client;
|
client: Client;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onDeleting: () => void;
|
onDeleting: () => void;
|
||||||
type: "sign_out_sessions";
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "test" | "signed_out";
|
type: "show_token";
|
||||||
|
name: string;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "error";
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "signed_out";
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { IconButton, Preloader } from "@revoltchat/ui";
|
|||||||
import { determineFileSize } from "../../lib/fileSize";
|
import { determineFileSize } from "../../lib/fileSize";
|
||||||
|
|
||||||
import { useIntermediate } from "../intermediate/Intermediate";
|
import { useIntermediate } from "../intermediate/Intermediate";
|
||||||
|
import { modalController } from "../modals";
|
||||||
import { AppContext } from "./RevoltClient";
|
import { AppContext } from "./RevoltClient";
|
||||||
import { takeError } from "./util";
|
import { takeError } from "./util";
|
||||||
|
|
||||||
@@ -139,12 +140,19 @@ export function FileUploader(props: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return openScreen({ id: "error", error: takeError(err) });
|
return modalController.push({
|
||||||
|
type: "error",
|
||||||
|
error: takeError(err),
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => openScreen({ id: "error", error: "FileTooLarge" }),
|
() =>
|
||||||
|
modalController.push({
|
||||||
|
type: "error",
|
||||||
|
error: "FileTooLarge",
|
||||||
|
}),
|
||||||
props.behaviour === "multi",
|
props.behaviour === "multi",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -180,8 +188,8 @@ export function FileUploader(props: Props) {
|
|||||||
const blob = item.getAsFile();
|
const blob = item.getAsFile();
|
||||||
if (blob) {
|
if (blob) {
|
||||||
if (blob.size > props.maxFileSize) {
|
if (blob.size > props.maxFileSize) {
|
||||||
openScreen({
|
modalController.push({
|
||||||
id: "error",
|
type: "error",
|
||||||
error: "FileTooLarge",
|
error: "FileTooLarge",
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
@@ -212,7 +220,10 @@ export function FileUploader(props: Props) {
|
|||||||
const files = [];
|
const files = [];
|
||||||
for (const item of dropped) {
|
for (const item of dropped) {
|
||||||
if (item.size > props.maxFileSize) {
|
if (item.size > props.maxFileSize) {
|
||||||
openScreen({ id: "error", error: "FileTooLarge" });
|
modalController.push({
|
||||||
|
type: "error",
|
||||||
|
error: "FileTooLarge",
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { Preloader } from "@revoltchat/ui";
|
|||||||
|
|
||||||
import { useApplicationState } from "../../mobx/State";
|
import { useApplicationState } from "../../mobx/State";
|
||||||
|
|
||||||
import { useIntermediate } from "../intermediate/Intermediate";
|
|
||||||
import { modalController } from "../modals";
|
import { modalController } from "../modals";
|
||||||
import { registerEvents } from "./events";
|
import { registerEvents } from "./events";
|
||||||
import { takeError } from "./util";
|
import { takeError } from "./util";
|
||||||
@@ -30,7 +29,7 @@ export interface ClientOperations {
|
|||||||
|
|
||||||
export const AppContext = createContext<Client>(null!);
|
export const AppContext = createContext<Client>(null!);
|
||||||
export const StatusContext = createContext<ClientStatus>(null!);
|
export const StatusContext = createContext<ClientStatus>(null!);
|
||||||
export const LogOutContext = createContext((avoidReq?: boolean) => {});
|
export const LogOutContext = createContext<(avoidReq?: boolean) => void>(null!);
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: Children;
|
children: Children;
|
||||||
@@ -38,7 +37,6 @@ type Props = {
|
|||||||
|
|
||||||
export default observer(({ children }: Props) => {
|
export default observer(({ children }: Props) => {
|
||||||
const state = useApplicationState();
|
const state = useApplicationState();
|
||||||
const { openScreen } = useIntermediate();
|
|
||||||
const [client, setClient] = useState<Client>(null!);
|
const [client, setClient] = useState<Client>(null!);
|
||||||
const [status, setStatus] = useState(ClientStatus.LOADING);
|
const [status, setStatus] = useState(ClientStatus.LOADING);
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
@@ -72,7 +70,10 @@ export default observer(({ children }: Props) => {
|
|||||||
modalController.push({ type: "signed_out" });
|
modalController.push({ type: "signed_out" });
|
||||||
} else {
|
} else {
|
||||||
setStatus(ClientStatus.DISCONNECTED);
|
setStatus(ClientStatus.DISCONNECTED);
|
||||||
openScreen({ id: "error", error });
|
modalController.push({
|
||||||
|
type: "error",
|
||||||
|
error,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoaded(true));
|
.finally(() => setLoaded(true));
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ import { ChevronRight, Trash } from "@styled-icons/boxicons-regular";
|
|||||||
import { Cog, UserVoice } from "@styled-icons/boxicons-solid";
|
import { Cog, UserVoice } from "@styled-icons/boxicons-solid";
|
||||||
import { isFirefox } from "react-device-detect";
|
import { isFirefox } from "react-device-detect";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { Channel, Message, Server, User, API } from "revolt.js";
|
import {
|
||||||
import { Permission, UserPermission } from "revolt.js";
|
Channel,
|
||||||
|
Message,
|
||||||
|
Server,
|
||||||
|
User,
|
||||||
|
API,
|
||||||
|
Permission,
|
||||||
|
UserPermission,
|
||||||
|
} from "revolt.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ContextMenuWithData,
|
ContextMenuWithData,
|
||||||
@@ -20,6 +27,7 @@ import { QueuedMessage } from "../mobx/stores/MessageQueue";
|
|||||||
import { NotificationState } from "../mobx/stores/NotificationOptions";
|
import { NotificationState } from "../mobx/stores/NotificationOptions";
|
||||||
|
|
||||||
import { Screen, useIntermediate } from "../context/intermediate/Intermediate";
|
import { Screen, useIntermediate } from "../context/intermediate/Intermediate";
|
||||||
|
import { modalController } from "../context/modals";
|
||||||
import {
|
import {
|
||||||
AppContext,
|
AppContext,
|
||||||
ClientStatus,
|
ClientStatus,
|
||||||
@@ -431,7 +439,10 @@ export default function ContextMenus() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})().catch((err) => {
|
})().catch((err) => {
|
||||||
openScreen({ id: "error", error: takeError(err) });
|
modalController.push({
|
||||||
|
type: "error",
|
||||||
|
error: takeError(err),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useContext, useEffect } from "preact/hooks";
|
|||||||
|
|
||||||
import { Header } from "@revoltchat/ui";
|
import { Header } from "@revoltchat/ui";
|
||||||
|
|
||||||
import { useIntermediate } from "../context/intermediate/Intermediate";
|
import { modalController } from "../context/modals";
|
||||||
import {
|
import {
|
||||||
AppContext,
|
AppContext,
|
||||||
ClientStatus,
|
ClientStatus,
|
||||||
@@ -18,7 +18,6 @@ export default function Open() {
|
|||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const status = useContext(StatusContext);
|
const status = useContext(StatusContext);
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const { openScreen } = useIntermediate();
|
|
||||||
|
|
||||||
if (status !== ClientStatus.ONLINE) {
|
if (status !== ClientStatus.ONLINE) {
|
||||||
return (
|
return (
|
||||||
@@ -40,7 +39,12 @@ export default function Open() {
|
|||||||
client
|
client
|
||||||
.user!.openDM()
|
.user!.openDM()
|
||||||
.then((channel) => history.push(`/channel/${channel?._id}`))
|
.then((channel) => history.push(`/channel/${channel?._id}`))
|
||||||
.catch((error) => openScreen({ id: "error", error }));
|
.catch((error) =>
|
||||||
|
modalController.push({
|
||||||
|
type: "error",
|
||||||
|
error,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -62,7 +66,12 @@ export default function Open() {
|
|||||||
.get(id)
|
.get(id)
|
||||||
?.openDM()
|
?.openDM()
|
||||||
.then((channel) => history.push(`/channel/${channel?._id}`))
|
.then((channel) => history.push(`/channel/${channel?._id}`))
|
||||||
.catch((error) => openScreen({ id: "error", error }));
|
.catch((error) =>
|
||||||
|
modalController.push({
|
||||||
|
type: "error",
|
||||||
|
error,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { useTranslation } from "../../../lib/i18n";
|
|||||||
import { stopPropagation } from "../../../lib/stopPropagation";
|
import { stopPropagation } from "../../../lib/stopPropagation";
|
||||||
|
|
||||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||||
|
import { modalController } from "../../../context/modals";
|
||||||
import { FileUploader } from "../../../context/revoltjs/FileUploads";
|
import { FileUploader } from "../../../context/revoltjs/FileUploads";
|
||||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
@@ -366,10 +367,10 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
|||||||
onClick={(ev) =>
|
onClick={(ev) =>
|
||||||
stopPropagation(
|
stopPropagation(
|
||||||
ev,
|
ev,
|
||||||
openScreen({
|
modalController.push({
|
||||||
id: "token_reveal",
|
type: "show_token",
|
||||||
token: bot.token,
|
token: bot.token,
|
||||||
username: user!.username,
|
name: user!.username,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}>
|
}>
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ import { urlBase64ToUint8Array } from "../../../lib/conversion";
|
|||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
import { modalController } from "../../../context/modals";
|
||||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
export const Notifications = observer(() => {
|
export const Notifications = observer(() => {
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const { openScreen } = useIntermediate();
|
|
||||||
const settings = useApplicationState().settings;
|
const settings = useApplicationState().settings;
|
||||||
const [pushEnabled, setPushEnabled] = useState<undefined | boolean>(
|
const [pushEnabled, setPushEnabled] = useState<undefined | boolean>(
|
||||||
undefined,
|
undefined,
|
||||||
@@ -52,8 +51,8 @@ export const Notifications = observer(() => {
|
|||||||
await Notification.requestPermission();
|
await Notification.requestPermission();
|
||||||
|
|
||||||
if (permission !== "granted") {
|
if (permission !== "granted") {
|
||||||
return openScreen({
|
return modalController.push({
|
||||||
id: "error",
|
type: "error",
|
||||||
error: "DeniedNotification",
|
error: "DeniedNotification",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user