import { observer } from "mobx-react-lite"; import { useHistory } from "react-router-dom"; import { TextChannel, VoiceChannel } from "revolt-api/types/Channels"; import { Channel } from "revolt.js/dist/maps/Channels"; import { Message as MessageI } from "revolt.js/dist/maps/Messages"; import { Server } from "revolt.js/dist/maps/Servers"; import { User } from "revolt.js/dist/maps/Users"; import { ulid } from "ulid"; import styles from "./Prompt.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; import { TextReact } from "../../../lib/i18n"; import Message from "../../../components/common/messaging/Message"; import UserIcon from "../../../components/common/user/UserIcon"; import InputBox from "../../../components/ui/InputBox"; import Modal, { Action } from "../../../components/ui/Modal"; import Overline from "../../../components/ui/Overline"; import Radio from "../../../components/ui/Radio"; import { Children } from "../../../types/Preact"; import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; import { useIntermediate } from "../Intermediate"; interface Props { onClose: () => void; question: Children; content?: Children; disabled?: boolean; actions: Action[]; error?: string; } export function PromptModal({ onClose, question, content, actions, disabled, error, }: Props) { return ( {error && } {content} ); } type SpecialProps = { onClose: () => void } & ( | { type: "leave_group"; target: Channel } | { type: "close_dm"; target: Channel } | { type: "leave_server"; target: Server } | { type: "delete_server"; target: Server } | { type: "delete_channel"; target: Channel } | { type: "delete_bot"; target: string; name: string; cb?: () => void } | { type: "delete_message"; target: MessageI } | { type: "create_invite"; target: Channel; } | { type: "kick_member"; target: Server; user: User } | { type: "ban_member"; target: Server; user: User } | { type: "unfriend_user"; target: User } | { type: "block_user"; target: User } | { type: "create_channel"; target: Server; cb?: (channel: TextChannel | VoiceChannel) => void; } | { type: "create_category"; target: Server } ); export const SpecialPromptModal = observer((props: SpecialProps) => { const client = useContext(AppContext); const history = useHistory(); const [processing, setProcessing] = useState(false); const [error, setError] = useState(undefined); const { onClose } = props; switch (props.type) { case "leave_group": case "close_dm": case "leave_server": case "delete_server": case "delete_channel": case "delete_bot": case "unfriend_user": case "block_user": { const EVENTS = { close_dm: ["confirm_close_dm", "close"], delete_server: ["confirm_delete", "delete"], delete_channel: ["confirm_delete", "delete"], delete_bot: ["confirm_delete", "delete"], leave_group: ["confirm_leave", "leave"], leave_server: ["confirm_leave", "leave"], unfriend_user: ["unfriend_user", "remove"], block_user: ["block_user", "block"], }; const event = EVENTS[props.type]; let name; switch (props.type) { case "unfriend_user": case "block_user": name = props.target.username; break; case "close_dm": name = props.target.recipient?.username; break; case "delete_bot": name = props.name; break; default: name = props.target.name; } return ( } actions={[ { confirmation: true, contrast: true, error: true, children: ( ), onClick: async () => { setProcessing(true); try { switch (props.type) { case "unfriend_user": await props.target.removeFriend(); break; case "block_user": await props.target.blockUser(); break; case "leave_group": case "close_dm": case "delete_channel": case "leave_server": case "delete_server": if (props.type != "delete_channel") history.push("/"); props.target.delete(); break; case "delete_bot": client.bots.delete(props.target); props.cb?.(); break; } onClose(); } catch (err) { setError(takeError(err)); setProcessing(false); } }, }, { children: ( ), onClick: onClose, }, ]} content={ {name} }} /> } disabled={processing} error={error} /> ); } case "delete_message": { return ( } actions={[ { confirmation: true, contrast: true, error: true, children: ( ), onClick: async () => { setProcessing(true); try { props.target.delete(); onClose(); } catch (err) { setError(takeError(err)); setProcessing(false); } }, }, { children: ( ), onClick: onClose, plain: true, }, ]} content={ <>
} disabled={processing} error={error} /> ); } case "create_invite": { const [code, setCode] = useState("abcdef"); const { writeClipboard } = useIntermediate(); useEffect(() => { setProcessing(true); props.target .createInvite() .then((code) => setCode(code)) .catch((err) => setError(takeError(err))) .finally(() => setProcessing(false)); }, [props.target]); return ( } actions={[ { children: ( ), confirmation: true, onClick: onClose, }, { children: , onClick: () => writeClipboard( `${window.location.protocol}//${window.location.host}/invite/${code}`, ), }, ]} content={ processing ? ( ) : (
{code}
) } disabled={processing} error={error} /> ); } case "kick_member": { return ( } actions={[ { children: ( ), contrast: true, error: true, confirmation: true, onClick: async () => { setProcessing(true); try { client.members .getKey({ server: props.target._id, user: props.user._id, }) ?.kick(); onClose(); } catch (err) { setError(takeError(err)); setProcessing(false); } }, }, { children: ( ), onClick: onClose, }, ]} content={
} disabled={processing} error={error} /> ); } case "ban_member": { const [reason, setReason] = useState(undefined); return ( } actions={[ { children: ( ), contrast: true, error: true, confirmation: true, onClick: async () => { setProcessing(true); try { await props.target.banUser(props.user._id, { reason, }); onClose(); } catch (err) { setError(takeError(err)); setProcessing(false); } }, }, { children: ( ), onClick: onClose, }, ]} content={
setReason(e.currentTarget.value) } />
} disabled={processing} error={error} /> ); } case "create_channel": { const [name, setName] = useState(""); const [type, setType] = useState<"Text" | "Voice">("Text"); const history = useHistory(); return ( } actions={[ { confirmation: true, contrast: true, children: ( ), onClick: async () => { setProcessing(true); try { const channel = await props.target.createChannel({ type, name, nonce: ulid(), }); if (props.cb) { props.cb(channel); } else { history.push( `/server/${props.target._id}/channel/${channel._id}`, ); } onClose(); } catch (err) { setError(takeError(err)); setProcessing(false); } }, }, { children: ( ), onClick: onClose, }, ]} content={ <> setType("Text")}> setType("Voice")}> setName(e.currentTarget.value)} /> } disabled={processing} error={error} /> ); } case "create_category": { const [name, setName] = useState(""); return ( } actions={[ { confirmation: true, contrast: true, children: ( ), onClick: async () => { setProcessing(true); try { props.target.edit({ categories: [ ...(props.target.categories ?? []), { id: ulid(), title: name, channels: [], }, ], }); onClose(); setProcessing(false); } catch (err) { setError(takeError(err)); setProcessing(false); } }, }, { children: ( ), onClick: onClose, }, ]} content={ <> setName(e.currentTarget.value)} /> } disabled={processing} error={error} /> ); } default: return null; } });