mirror of
https://github.com/stoatchat/for-legacy-web.git
synced 2026-03-07 09:25:27 +00:00
Port modal / popover context.
This commit is contained in:
32
src/context/intermediate/modals/Clipboard.tsx
Normal file
32
src/context/intermediate/modals/Clipboard.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Text } from "preact-i18n";
|
||||
import Modal from "../../../components/ui/Modal";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export function ClipboardModal({ onClose, text }: Props) {
|
||||
return (
|
||||
<Modal
|
||||
visible={true}
|
||||
onClose={onClose}
|
||||
title={<Text id="app.special.modals.clipboard.unavailable" />}
|
||||
actions={[
|
||||
{
|
||||
onClick: onClose,
|
||||
confirmation: true,
|
||||
text: <Text id="app.special.modals.actions.close" />
|
||||
}
|
||||
]}
|
||||
>
|
||||
{location.protocol !== "https:" && (
|
||||
<p>
|
||||
<Text id="app.special.modals.clipboard.https" />
|
||||
</p>
|
||||
)}
|
||||
<Text id="app.special.modals.clipboard.copy" />{" "}
|
||||
<code style={{ userSelect: "all" }}>{text}</code>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
30
src/context/intermediate/modals/Error.tsx
Normal file
30
src/context/intermediate/modals/Error.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Text } from "preact-i18n";
|
||||
import Modal from "../../../components/ui/Modal";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export function ErrorModal({ onClose, error }: Props) {
|
||||
return (
|
||||
<Modal
|
||||
visible={true}
|
||||
onClose={() => false}
|
||||
title={<Text id="app.special.modals.error" />}
|
||||
actions={[
|
||||
{
|
||||
onClick: onClose,
|
||||
confirmation: true,
|
||||
text: <Text id="app.special.modals.actions.ok" />
|
||||
},
|
||||
{
|
||||
onClick: () => location.reload(),
|
||||
text: <Text id="app.special.modals.actions.reload" />
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Text id={`error.${error}`}>{error}</Text>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
149
src/context/intermediate/modals/Input.tsx
Normal file
149
src/context/intermediate/modals/Input.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import { ulid } from "ulid";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useHistory } from "react-router";
|
||||
import Modal from "../../../components/ui/Modal";
|
||||
import { Children } from "../../../types/Preact";
|
||||
import { takeError } from "../../revoltjs/util";
|
||||
import { useContext, useState } from "preact/hooks";
|
||||
import Overline from '../../../components/ui/Overline';
|
||||
import InputBox from '../../../components/ui/InputBox';
|
||||
import { AppContext } from "../../revoltjs/RevoltClient";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
question: Children;
|
||||
field: Children;
|
||||
defaultValue?: string;
|
||||
callback: (value: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export function InputModal({
|
||||
onClose,
|
||||
question,
|
||||
field,
|
||||
defaultValue,
|
||||
callback
|
||||
}: Props) {
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [value, setValue] = useState(defaultValue ?? "");
|
||||
const [error, setError] = useState<undefined | string>(undefined);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={true}
|
||||
title={question}
|
||||
disabled={processing}
|
||||
actions={[
|
||||
{
|
||||
text: <Text id="app.special.modals.actions.ok" />,
|
||||
onClick: () => {
|
||||
setProcessing(true);
|
||||
callback(value)
|
||||
.then(onClose)
|
||||
.catch(err => {
|
||||
setError(takeError(err));
|
||||
setProcessing(false)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
text: <Text id="app.special.modals.actions.cancel" />,
|
||||
onClick: onClose
|
||||
}
|
||||
]}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Overline error={error} block>
|
||||
{field}
|
||||
</Overline>
|
||||
<InputBox
|
||||
value={value}
|
||||
onChange={e => setValue(e.currentTarget.value)}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
type SpecialProps = { onClose: () => void } & (
|
||||
{ type: "create_group" | "create_server" | "set_custom_status" } |
|
||||
{ type: "create_channel", server: string }
|
||||
)
|
||||
|
||||
export function SpecialInputModal(props: SpecialProps) {
|
||||
const history = useHistory();
|
||||
const client = useContext(AppContext);
|
||||
|
||||
const { onClose } = props;
|
||||
switch (props.type) {
|
||||
case "create_group": {
|
||||
return <InputModal
|
||||
onClose={onClose}
|
||||
question={<Text id="app.main.groups.create" />}
|
||||
field={<Text id="app.main.groups.name" />}
|
||||
callback={async name => {
|
||||
const group = await client.channels.createGroup(
|
||||
{
|
||||
name,
|
||||
nonce: ulid(),
|
||||
users: []
|
||||
}
|
||||
);
|
||||
|
||||
history.push(`/channel/${group._id}`);
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
case "create_server": {
|
||||
return <InputModal
|
||||
onClose={onClose}
|
||||
question={<Text id="app.main.servers.create" />}
|
||||
field={<Text id="app.main.servers.name" />}
|
||||
callback={async name => {
|
||||
const server = await client.servers.createServer(
|
||||
{
|
||||
name,
|
||||
nonce: ulid()
|
||||
}
|
||||
);
|
||||
|
||||
history.push(`/server/${server._id}`);
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
case "create_channel": {
|
||||
return <InputModal
|
||||
onClose={onClose}
|
||||
question={<Text id="app.context_menu.create_channel" />}
|
||||
field={<Text id="app.main.servers.channel_name" />}
|
||||
callback={async name => {
|
||||
const channel = await client.servers.createChannel(
|
||||
props.server,
|
||||
{
|
||||
name,
|
||||
nonce: ulid()
|
||||
}
|
||||
);
|
||||
|
||||
history.push(`/server/${props.server}/channel/${channel._id}`);
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
case "set_custom_status": {
|
||||
return <InputModal
|
||||
onClose={onClose}
|
||||
question={<Text id="app.context_menu.set_custom_status" />}
|
||||
field={<Text id="app.context_menu.custom_status" />}
|
||||
defaultValue={client.user?.status?.text}
|
||||
callback={text =>
|
||||
client.users.editUser({
|
||||
status: {
|
||||
...client.user?.status,
|
||||
text
|
||||
}
|
||||
})
|
||||
}
|
||||
/>;
|
||||
}
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
120
src/context/intermediate/modals/ModifyAccount.tsx
Normal file
120
src/context/intermediate/modals/ModifyAccount.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { Text } from "preact-i18n";
|
||||
import { useForm } from "react-hook-form";
|
||||
import Modal from "../../../components/ui/Modal";
|
||||
import { takeError } from "../../revoltjs/util";
|
||||
import { useContext, useState } from "preact/hooks";
|
||||
import FormField from '../../../pages/login/FormField';
|
||||
import Overline from "../../../components/ui/Overline";
|
||||
import { AppContext } from "../../revoltjs/RevoltClient";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
field: "username" | "email" | "password";
|
||||
}
|
||||
|
||||
export function ModifyAccountModal({ onClose, field }: Props) {
|
||||
const client = useContext(AppContext);
|
||||
const { handleSubmit, register, errors } = useForm();
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
async function onSubmit({
|
||||
password,
|
||||
new_username,
|
||||
new_email,
|
||||
new_password
|
||||
}: {
|
||||
password: string;
|
||||
new_username: string;
|
||||
new_email: string;
|
||||
new_password: string;
|
||||
}) {
|
||||
try {
|
||||
if (field === "email") {
|
||||
await client.req("POST", "/auth/change/email", {
|
||||
password,
|
||||
new_email
|
||||
});
|
||||
onClose();
|
||||
} else if (field === "password") {
|
||||
await client.req("POST", "/auth/change/password", {
|
||||
password,
|
||||
new_password
|
||||
});
|
||||
onClose();
|
||||
} else if (field === "username") {
|
||||
await client.req("PATCH", "/users/id/username", {
|
||||
username: new_username,
|
||||
password
|
||||
});
|
||||
onClose();
|
||||
}
|
||||
} catch (err) {
|
||||
setError(takeError(err));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={true}
|
||||
onClose={onClose}
|
||||
title={<Text id={`app.special.modals.account.change.${field}`} />}
|
||||
actions={[
|
||||
{
|
||||
confirmation: true,
|
||||
onClick: handleSubmit(onSubmit),
|
||||
text:
|
||||
field === "email" ? (
|
||||
<Text id="app.special.modals.actions.send_email" />
|
||||
) : (
|
||||
<Text id="app.special.modals.actions.update" />
|
||||
)
|
||||
},
|
||||
{
|
||||
onClick: onClose,
|
||||
text: <Text id="app.special.modals.actions.close" />
|
||||
}
|
||||
]}
|
||||
>
|
||||
<form onSubmit={handleSubmit(onSubmit) as any}>
|
||||
{field === "email" && (
|
||||
<FormField
|
||||
type="email"
|
||||
name="new_email"
|
||||
register={register}
|
||||
showOverline
|
||||
error={errors.new_email?.message}
|
||||
/>
|
||||
)}
|
||||
{field === "password" && (
|
||||
<FormField
|
||||
type="password"
|
||||
name="new_password"
|
||||
register={register}
|
||||
showOverline
|
||||
error={errors.new_password?.message}
|
||||
/>
|
||||
)}
|
||||
{field === "username" && (
|
||||
<FormField
|
||||
type="username"
|
||||
name="new_username"
|
||||
register={register}
|
||||
showOverline
|
||||
error={errors.new_username?.message}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
type="current_password"
|
||||
register={register}
|
||||
showOverline
|
||||
error={errors.current_password?.message}
|
||||
/>
|
||||
{error && (
|
||||
<Overline type="error" error={error}>
|
||||
<Text id="app.special.modals.account.failed" />
|
||||
</Overline>
|
||||
)}
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
40
src/context/intermediate/modals/Onboarding.module.scss
Normal file
40
src/context/intermediate/modals/Onboarding.module.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
.onboarding {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
div {
|
||||
flex: 1;
|
||||
|
||||
&.header {
|
||||
padding: 3em;
|
||||
text-align: center;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.form {
|
||||
flex-grow: 1;
|
||||
max-width: 420px;
|
||||
|
||||
img {
|
||||
margin: auto;
|
||||
display: block;
|
||||
max-height: 420px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
display: block;
|
||||
margin: 24px 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/context/intermediate/modals/Onboarding.tsx
Normal file
66
src/context/intermediate/modals/Onboarding.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Text } from "preact-i18n";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useForm } from "react-hook-form";
|
||||
import styles from "./Onboarding.module.scss";
|
||||
import { takeError } from "../../revoltjs/util";
|
||||
import Button from "../../../components/ui/Button";
|
||||
import FormField from "../../../pages/login/FormField";
|
||||
import Preloader from "../../../components/ui/Preloader";
|
||||
|
||||
// import WideSvg from "../../../assets/wide.svg";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
callback: (username: string, loginAfterSuccess?: true) => Promise<void>;
|
||||
}
|
||||
|
||||
export function OnboardingModal({ onClose, callback }: Props) {
|
||||
const { handleSubmit, register } = useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
async function onSubmit({ username }: { username: string }) {
|
||||
setLoading(true);
|
||||
callback(username, true)
|
||||
.then(onClose)
|
||||
.catch((err: any) => {
|
||||
setError(takeError(err));
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.onboarding}>
|
||||
<div className={styles.header}>
|
||||
<h1>
|
||||
<Text id="app.special.modals.onboarding.welcome" />
|
||||
</h1>
|
||||
</div>
|
||||
<div className={styles.form}>
|
||||
{loading ? (
|
||||
<Preloader />
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
<Text id="app.special.modals.onboarding.pick" />
|
||||
</p>
|
||||
<form onSubmit={handleSubmit(onSubmit) as any}>
|
||||
<div>
|
||||
<FormField
|
||||
type="username"
|
||||
register={register}
|
||||
showOverline
|
||||
error={error}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit">
|
||||
<Text id="app.special.modals.actions.continue" />
|
||||
</Button>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
src/context/intermediate/modals/Prompt.module.scss
Normal file
18
src/context/intermediate/modals/Prompt.module.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
.invite {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
code {
|
||||
padding: 1em;
|
||||
user-select: all;
|
||||
font-size: 1.4em;
|
||||
text-align: center;
|
||||
font-family: "Fira Mono";
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
234
src/context/intermediate/modals/Prompt.tsx
Normal file
234
src/context/intermediate/modals/Prompt.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
import { Text } from "preact-i18n";
|
||||
import styles from './Prompt.module.scss';
|
||||
import { Children } from "../../../types/Preact";
|
||||
import { IntermediateContext, useIntermediate } from "../Intermediate";
|
||||
import InputBox from "../../../components/ui/InputBox";
|
||||
import Overline from "../../../components/ui/Overline";
|
||||
import UserIcon from "../../../components/common/UserIcon";
|
||||
import Modal, { Action } from "../../../components/ui/Modal";
|
||||
import { Channels, Servers } from "revolt.js/dist/api/objects";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { AppContext } from "../../revoltjs/RevoltClient";
|
||||
import { takeError } from "../../revoltjs/util";
|
||||
|
||||
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 (
|
||||
<Modal
|
||||
visible={true}
|
||||
title={question}
|
||||
actions={actions}
|
||||
onClose={onClose}
|
||||
disabled={disabled}>
|
||||
{ error && <Overline error={error} type="error" /> }
|
||||
{ content }
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
type SpecialProps = { onClose: () => void } & (
|
||||
{ type: "leave_group", target: Channels.GroupChannel } |
|
||||
{ type: "close_dm", target: Channels.DirectMessageChannel } |
|
||||
{ type: "leave_server", target: Servers.Server } |
|
||||
{ type: "delete_server", target: Servers.Server } |
|
||||
{ type: "delete_channel", target: Channels.TextChannel } |
|
||||
{ type: "delete_message", target: Channels.Message } |
|
||||
{ type: "create_invite", target: Channels.TextChannel | Channels.GroupChannel } |
|
||||
{ type: "kick_member", target: Servers.Server, user: string } |
|
||||
{ type: "ban_member", target: Servers.Server, user: string }
|
||||
)
|
||||
|
||||
export function SpecialPromptModal(props: SpecialProps) {
|
||||
const client = useContext(AppContext);
|
||||
const [ processing, setProcessing ] = useState(false);
|
||||
const [ error, setError ] = useState<undefined | string>(undefined);
|
||||
|
||||
const { onClose } = props;
|
||||
switch (props.type) {
|
||||
case 'leave_group':
|
||||
case 'close_dm':
|
||||
case 'leave_server':
|
||||
case 'delete_server':
|
||||
case 'delete_message':
|
||||
case 'delete_channel': {
|
||||
const EVENTS = {
|
||||
'close_dm': 'confirm_close_dm',
|
||||
'delete_server': 'confirm_delete',
|
||||
'delete_channel': 'confirm_delete',
|
||||
'delete_message': 'confirm_delete_message',
|
||||
'leave_group': 'confirm_leave',
|
||||
'leave_server': 'confirm_leave'
|
||||
};
|
||||
|
||||
let event = EVENTS[props.type];
|
||||
let name = props.type === 'close_dm' ? client.users.get(client.channels.getRecipient(props.target._id))?.username :
|
||||
props.type === 'delete_message' ? undefined : props.target.name;
|
||||
|
||||
return (
|
||||
<PromptModal
|
||||
onClose={onClose}
|
||||
question={<Text
|
||||
id={props.type === 'delete_message' ? 'app.context_menu.delete_message' : `app.special.modals.prompt.${event}`}
|
||||
fields={{ name }}
|
||||
/>}
|
||||
actions={[
|
||||
{
|
||||
confirmation: true,
|
||||
style: 'contrast-error',
|
||||
text: <Text id="app.special.modals.actions.delete" />,
|
||||
onClick: async () => {
|
||||
setProcessing(true);
|
||||
|
||||
try {
|
||||
if (props.type === 'leave_group' || props.type === 'close_dm' || props.type === 'delete_channel') {
|
||||
await client.channels.delete(props.target._id);
|
||||
} else if (props.type === 'delete_message') {
|
||||
await client.channels.deleteMessage(props.target.channel, props.target._id);
|
||||
} else {
|
||||
await client.servers.delete(props.target._id);
|
||||
}
|
||||
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(takeError(err));
|
||||
setProcessing(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ text: <Text id="app.special.modals.actions.cancel" />, onClick: onClose }
|
||||
]}
|
||||
content={<Text id={`app.special.modals.prompt.${event}_long`} />}
|
||||
disabled={processing}
|
||||
error={error}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "create_invite": {
|
||||
const [ code, setCode ] = useState('abcdef');
|
||||
const { writeClipboard } = useIntermediate();
|
||||
|
||||
useEffect(() => {
|
||||
setProcessing(true);
|
||||
|
||||
client.channels.createInvite(props.target._id)
|
||||
.then(code => setCode(code))
|
||||
.catch(err => setError(takeError(err)))
|
||||
.finally(() => setProcessing(false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PromptModal
|
||||
onClose={onClose}
|
||||
question={<Text id={`app.context_menu.create_invite`} />}
|
||||
actions={[
|
||||
{
|
||||
text: <Text id="app.special.modals.actions.ok" />,
|
||||
confirmation: true,
|
||||
onClick: onClose
|
||||
},
|
||||
{
|
||||
text: <Text id="app.context_menu.copy_link" />,
|
||||
onClick: () => writeClipboard(`${window.location.protocol}//${window.location.host}/invite/${code}`)
|
||||
}
|
||||
]}
|
||||
content={
|
||||
processing ?
|
||||
<Text id="app.special.modals.prompt.create_invite_generate" />
|
||||
: <div className={styles.invite}>
|
||||
<Text id="app.special.modals.prompt.create_invite_created" />
|
||||
<code>{code}</code>
|
||||
</div>
|
||||
}
|
||||
disabled={processing}
|
||||
error={error}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "kick_member": {
|
||||
const user = client.users.get(props.user);
|
||||
|
||||
return (
|
||||
<PromptModal
|
||||
onClose={onClose}
|
||||
question={<Text id={`app.context_menu.kick_member`} />}
|
||||
actions={[
|
||||
{
|
||||
text: <Text id="app.special.modals.actions.kick" />,
|
||||
style: 'contrast-error',
|
||||
confirmation: true,
|
||||
onClick: async () => {
|
||||
setProcessing(true);
|
||||
|
||||
try {
|
||||
await client.servers.members.kickMember(props.target._id, props.user);
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(takeError(err));
|
||||
setProcessing(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ text: <Text id="app.special.modals.actions.cancel" />, onClick: onClose }
|
||||
]}
|
||||
content={<div className={styles.column}>
|
||||
<UserIcon target={user} size={64} />
|
||||
<Text
|
||||
id="app.special.modals.prompt.confirm_kick"
|
||||
fields={{ name: user?.username }} />
|
||||
</div>}
|
||||
disabled={processing}
|
||||
error={error}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "ban_member": {
|
||||
const [ reason, setReason ] = useState<string | undefined>(undefined);
|
||||
const user = client.users.get(props.user);
|
||||
|
||||
return (
|
||||
<PromptModal
|
||||
onClose={onClose}
|
||||
question={<Text id={`app.context_menu.ban_member`} />}
|
||||
actions={[
|
||||
{
|
||||
text: <Text id="app.special.modals.actions.ban" />,
|
||||
style: 'contrast-error',
|
||||
confirmation: true,
|
||||
onClick: async () => {
|
||||
setProcessing(true);
|
||||
|
||||
try {
|
||||
await client.servers.banUser(props.target._id, props.user, { reason });
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(takeError(err));
|
||||
setProcessing(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ text: <Text id="app.special.modals.actions.cancel" />, onClick: onClose }
|
||||
]}
|
||||
content={<div className={styles.column}>
|
||||
<UserIcon target={user} size={64} />
|
||||
<Text
|
||||
id="app.special.modals.prompt.confirm_ban"
|
||||
fields={{ name: user?.username }} />
|
||||
<Overline><Text id="app.special.modals.prompt.confirm_ban_reason" /></Overline>
|
||||
<InputBox value={reason ?? ''} onChange={e => setReason(e.currentTarget.value)} />
|
||||
</div>}
|
||||
disabled={processing}
|
||||
error={error}
|
||||
/>
|
||||
)
|
||||
}
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
23
src/context/intermediate/modals/SignedOut.tsx
Normal file
23
src/context/intermediate/modals/SignedOut.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Text } from "preact-i18n";
|
||||
import Modal from "../../../components/ui/Modal";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function SignedOutModal({ onClose }: Props) {
|
||||
return (
|
||||
<Modal
|
||||
visible={true}
|
||||
onClose={onClose}
|
||||
title={<Text id="app.special.modals.signed_out" />}
|
||||
actions={[
|
||||
{
|
||||
onClick: onClose,
|
||||
confirmation: true,
|
||||
text: <Text id="app.special.modals.actions.ok" />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user