331 lines
9.0 KiB
TypeScript
331 lines
9.0 KiB
TypeScript
import { Match, Show, Switch, createMemo, createSignal } from "solid-js";
|
|
|
|
import { Trans } from "@lingui-solid/solid/macro";
|
|
|
|
import { useClient, useClientLifecycle } from "@revolt/client";
|
|
import {
|
|
createMfaResource,
|
|
createOwnProfileResource,
|
|
} from "@revolt/client/resources";
|
|
import { useModals } from "@revolt/modal";
|
|
import { CategoryButton, Column, Row, iconSize } from "@revolt/ui";
|
|
|
|
import MdAlternateEmail from "@material-design-icons/svg/outlined/alternate_email.svg?component-solid";
|
|
import MdBlock from "@material-design-icons/svg/outlined/block.svg?component-solid";
|
|
import MdDelete from "@material-design-icons/svg/outlined/delete.svg?component-solid";
|
|
import MdLock from "@material-design-icons/svg/outlined/lock.svg?component-solid";
|
|
import MdMail from "@material-design-icons/svg/outlined/mail.svg?component-solid";
|
|
import MdPassword from "@material-design-icons/svg/outlined/password.svg?component-solid";
|
|
import MdVerifiedUser from "@material-design-icons/svg/outlined/verified_user.svg?component-solid";
|
|
|
|
import { useSettingsNavigation } from "../Settings";
|
|
|
|
import { UserSummary } from "./account/index";
|
|
|
|
/**
|
|
* Account Page
|
|
*/
|
|
export function MyAccount() {
|
|
const client = useClient();
|
|
const profile = createOwnProfileResource();
|
|
const { navigate } = useSettingsNavigation();
|
|
|
|
return (
|
|
<Column gap="lg">
|
|
<UserSummary
|
|
user={client().user!}
|
|
bannerUrl={profile.data?.animatedBannerURL}
|
|
onEdit={() => navigate("profile")}
|
|
showBadges
|
|
/>
|
|
<EditAccount />
|
|
<MultiFactorAuth />
|
|
<ManageAccount />
|
|
</Column>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Edit account details
|
|
*/
|
|
function EditAccount() {
|
|
const client = useClient();
|
|
const { openModal } = useModals();
|
|
const [email, setEmail] = createSignal("•••••••••••@•••••••••••");
|
|
|
|
return (
|
|
<CategoryButton.Group>
|
|
<CategoryButton
|
|
action="chevron"
|
|
onClick={() =>
|
|
openModal({
|
|
type: "edit_username",
|
|
client: client(),
|
|
})
|
|
}
|
|
icon={<MdAlternateEmail {...iconSize(22)} />}
|
|
description={client().user?.username}
|
|
>
|
|
<Trans>Username</Trans>
|
|
</CategoryButton>
|
|
<CategoryButton
|
|
action="chevron"
|
|
onClick={() =>
|
|
openModal({
|
|
type: "edit_email",
|
|
client: client(),
|
|
})
|
|
}
|
|
icon={<MdMail {...iconSize(22)} />}
|
|
description={
|
|
<Row>
|
|
{email()}{" "}
|
|
<Show when={email().startsWith("•")}>
|
|
<a
|
|
onClick={(event) => {
|
|
event.stopPropagation();
|
|
client().account.fetchEmail().then(setEmail);
|
|
}}
|
|
>
|
|
Reveal
|
|
</a>
|
|
</Show>
|
|
</Row>
|
|
}
|
|
>
|
|
<Trans>Email</Trans>
|
|
</CategoryButton>
|
|
<CategoryButton
|
|
action="chevron"
|
|
onClick={() =>
|
|
openModal({
|
|
type: "edit_password",
|
|
client: client(),
|
|
})
|
|
}
|
|
icon={<MdPassword {...iconSize(22)} />}
|
|
description={"•••••••••"}
|
|
>
|
|
<Trans>Password</Trans>
|
|
</CategoryButton>
|
|
</CategoryButton.Group>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Multi-factor authentication
|
|
*/
|
|
function MultiFactorAuth() {
|
|
const client = useClient();
|
|
const mfa = createMfaResource();
|
|
const { openModal, mfaFlow, mfaEnableTOTP, showError } = useModals();
|
|
|
|
/**
|
|
* Show recovery codes
|
|
*/
|
|
async function showRecoveryCodes() {
|
|
const ticket = await mfaFlow(mfa.data!);
|
|
|
|
ticket!.fetchRecoveryCodes().then((codes) =>
|
|
openModal({
|
|
type: "mfa_recovery",
|
|
mfa: mfa.data!,
|
|
codes,
|
|
}),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Generate recovery codes
|
|
*/
|
|
async function generateRecoveryCodes() {
|
|
const ticket = await mfaFlow(mfa.data!);
|
|
|
|
ticket!.generateRecoveryCodes().then((codes) =>
|
|
openModal({
|
|
type: "mfa_recovery",
|
|
mfa: mfa.data!,
|
|
codes,
|
|
}),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Configure authenticator app
|
|
*/
|
|
async function setupAuthenticatorApp() {
|
|
const ticket = await mfaFlow(mfa.data!);
|
|
const secret = await ticket!.generateAuthenticatorSecret();
|
|
|
|
let success;
|
|
while (!success) {
|
|
try {
|
|
const code = await mfaEnableTOTP(secret, client().user!.username);
|
|
|
|
if (code) {
|
|
await mfa.data!.enableAuthenticator(code);
|
|
success = true;
|
|
}
|
|
} catch (err) {
|
|
showError(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disable authenticator app
|
|
*/
|
|
function disableAuthenticatorApp() {
|
|
mfaFlow(mfa.data!).then((ticket) => ticket!.disableAuthenticator());
|
|
}
|
|
|
|
return (
|
|
<CategoryButton.Group>
|
|
<CategoryButton.Collapse
|
|
icon={<MdVerifiedUser {...iconSize(22)} />}
|
|
title={<Trans>Recovery Codes</Trans>}
|
|
description={
|
|
<Trans>
|
|
Configure a way to get back into your account in case your 2FA is
|
|
lost
|
|
</Trans>
|
|
}
|
|
>
|
|
<Switch
|
|
fallback={
|
|
<CategoryButton
|
|
icon="blank"
|
|
disabled={mfa.isLoading}
|
|
onClick={generateRecoveryCodes}
|
|
description={<Trans>Setup recovery codes</Trans>}
|
|
>
|
|
<Trans>Generate Recovery Codes</Trans>
|
|
</CategoryButton>
|
|
}
|
|
>
|
|
<Match when={!mfa.isLoading && mfa.data?.recoveryEnabled}>
|
|
<CategoryButton
|
|
icon="blank"
|
|
description={<Trans>Get active recovery codes</Trans>}
|
|
onClick={showRecoveryCodes}
|
|
>
|
|
<Trans>View Recovery Codes</Trans>
|
|
</CategoryButton>
|
|
<CategoryButton
|
|
icon="blank"
|
|
description={<Trans>Get a new set of recovery codes</Trans>}
|
|
onClick={generateRecoveryCodes}
|
|
>
|
|
<Trans>Reset Recovery Codes</Trans>
|
|
</CategoryButton>
|
|
</Match>
|
|
</Switch>
|
|
</CategoryButton.Collapse>
|
|
<CategoryButton.Collapse
|
|
icon={<MdLock {...iconSize(22)} />}
|
|
title={<Trans>Authenticator App</Trans>}
|
|
description={<Trans>Configure one-time password authentication</Trans>}
|
|
>
|
|
<Switch
|
|
fallback={
|
|
<CategoryButton
|
|
icon="blank"
|
|
disabled={mfa.isLoading}
|
|
onClick={setupAuthenticatorApp}
|
|
description={<Trans>Setup one-time password authenticator</Trans>}
|
|
>
|
|
<Trans>Enable Authenticator</Trans>
|
|
</CategoryButton>
|
|
}
|
|
>
|
|
<Match when={!mfa.isLoading && mfa.data?.authenticatorEnabled}>
|
|
<CategoryButton
|
|
icon="blank"
|
|
description={
|
|
<Trans>Disable one-time password authenticator</Trans>
|
|
}
|
|
onClick={disableAuthenticatorApp}
|
|
>
|
|
<Trans>Remove Authenticator</Trans>
|
|
</CategoryButton>
|
|
</Match>
|
|
</Switch>
|
|
</CategoryButton.Collapse>
|
|
</CategoryButton.Group>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Manage account
|
|
*/
|
|
function ManageAccount() {
|
|
const client = useClient();
|
|
const mfa = createMfaResource();
|
|
const { mfaFlow } = useModals();
|
|
const { logout } = useClientLifecycle();
|
|
|
|
const stillOwnServers = createMemo(
|
|
() =>
|
|
client().servers.filter((server) => server.owner?.self || false).length >
|
|
0,
|
|
);
|
|
|
|
/**
|
|
* Disable account
|
|
*/
|
|
function disableAccount() {
|
|
mfaFlow(mfa.data!).then((ticket) =>
|
|
ticket!.disableAccount().then(() => logout()),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Delete account
|
|
*/
|
|
function deleteAccount() {
|
|
mfaFlow(mfa.data!).then((ticket) =>
|
|
ticket!.deleteAccount().then(() => logout()),
|
|
);
|
|
}
|
|
|
|
return (
|
|
<CategoryButton.Group>
|
|
<CategoryButton
|
|
action="chevron"
|
|
disabled={mfa.isLoading}
|
|
onClick={disableAccount}
|
|
icon={<MdBlock {...iconSize(22)} fill="var(--md-sys-color-error)" />}
|
|
description={
|
|
<Trans>
|
|
You won't be able to access your account unless you contact support
|
|
- however, your data will not be deleted.
|
|
</Trans>
|
|
}
|
|
>
|
|
<Trans>Disable Account</Trans>
|
|
</CategoryButton>
|
|
<CategoryButton
|
|
action={stillOwnServers() ? undefined : "chevron"}
|
|
disabled={mfa.isLoading || stillOwnServers()}
|
|
onClick={deleteAccount}
|
|
icon={<MdDelete {...iconSize(22)} fill="var(--md-sys-color-error)" />}
|
|
description={
|
|
<Trans>
|
|
Your account and all of your data (including your messages and
|
|
friends list) will be queued for deletion. A confirmation email will
|
|
be sent - you can cancel this within 7 days by contacting support.
|
|
</Trans>
|
|
}
|
|
>
|
|
<Switch fallback={<Trans>Delete Account</Trans>}>
|
|
<Match when={stillOwnServers()}>
|
|
<Trans>
|
|
Cannot delete account until servers are deleted or transferred
|
|
</Trans>
|
|
</Match>
|
|
</Switch>
|
|
</CategoryButton>
|
|
</CategoryButton.Group>
|
|
);
|
|
}
|