mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-21 22:50:59 -05:00
feat(mobx): migrate auth and config
This commit is contained in:
parent
bc799931a8
commit
f8b8d96d3d
22 changed files with 342 additions and 279 deletions
10
index.html
10
index.html
|
@ -1,10 +1,13 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" background="#191919">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|
||||||
|
<!--App Title-->
|
||||||
<title>Revolt</title>
|
<title>Revolt</title>
|
||||||
<meta name="apple-mobile-web-app-title" content="Revolt" />
|
<meta name="apple-mobile-web-app-title" content="Revolt" />
|
||||||
|
|
||||||
|
<!--App Scaling-->
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1.0, user-scalable=no"
|
content="width=device-width, initial-scale=1.0, user-scalable=no"
|
||||||
|
@ -74,9 +77,4 @@
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
<style>
|
|
||||||
html {
|
|
||||||
background-color: #191919;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -49,7 +49,10 @@
|
||||||
"FunctionExpression": false
|
"FunctionExpression": false
|
||||||
},
|
},
|
||||||
"ignore": {
|
"ignore": {
|
||||||
"MethodDefinition": ["toJSON", "hydrate"]
|
"MethodDefinition": [
|
||||||
|
"toJSON",
|
||||||
|
"hydrate"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -140,7 +143,7 @@
|
||||||
"react-virtuoso": "^1.10.4",
|
"react-virtuoso": "^1.10.4",
|
||||||
"redux": "^4.1.0",
|
"redux": "^4.1.0",
|
||||||
"revolt-api": "0.5.3-alpha.10",
|
"revolt-api": "0.5.3-alpha.10",
|
||||||
"revolt.js": "^5.1.0-alpha.10",
|
"revolt.js": "5.1.0-alpha.15",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.35.1",
|
"sass": "^1.35.1",
|
||||||
"shade-blend-color": "^1.0.0",
|
"shade-blend-color": "^1.0.0",
|
||||||
|
|
|
@ -276,13 +276,13 @@ export const ServerListSidebar = observer(({ unreads }: Props) => {
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
homeActive && history.push("/settings")
|
homeActive && history.push("/settings")
|
||||||
}>
|
}>
|
||||||
<UserHover user={client.user}>
|
<UserHover user={client.user ?? undefined}>
|
||||||
<Icon
|
<Icon
|
||||||
size={42}
|
size={42}
|
||||||
unread={homeUnread}
|
unread={homeUnread}
|
||||||
count={alertCount}>
|
count={alertCount}>
|
||||||
<UserIcon
|
<UserIcon
|
||||||
target={client.user}
|
target={client.user ?? undefined}
|
||||||
size={32}
|
size={32}
|
||||||
status
|
status
|
||||||
hover
|
hover
|
||||||
|
|
|
@ -205,7 +205,6 @@ export const Languages: { [key in Language]: LanguageEntry } = {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: JSX.Element | JSX.Element[];
|
children: JSX.Element | JSX.Element[];
|
||||||
locale: Language;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Dictionary {
|
export interface Dictionary {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Redirect } from "react-router-dom";
|
import { Redirect } from "react-router-dom";
|
||||||
|
|
||||||
import { useContext } from "preact/hooks";
|
import { useApplicationState } from "../../mobx/State";
|
||||||
|
|
||||||
import { Children } from "../../types/Preact";
|
import { Children } from "../../types/Preact";
|
||||||
import { OperationsContext } from "./RevoltClient";
|
import { useClient } from "./RevoltClient";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
auth?: boolean;
|
auth?: boolean;
|
||||||
|
@ -11,11 +11,13 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CheckAuth = (props: Props) => {
|
export const CheckAuth = (props: Props) => {
|
||||||
const operations = useContext(OperationsContext);
|
const auth = useApplicationState().auth;
|
||||||
|
const client = useClient();
|
||||||
|
const ready = auth.isLoggedIn() && typeof client?.user !== "undefined";
|
||||||
|
|
||||||
if (props.auth && !operations.ready()) {
|
if (props.auth && !ready) {
|
||||||
return <Redirect to="/login" />;
|
return <Redirect to="/login" />;
|
||||||
} else if (!props.auth && operations.ready()) {
|
} else if (!props.auth && ready) {
|
||||||
return <Redirect to="/" />;
|
return <Redirect to="/" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
/* eslint-disable react-hooks/rules-of-hooks */
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
import { Session } from "revolt-api/types/Auth";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Client } from "revolt.js";
|
import { Client } from "revolt.js";
|
||||||
import { Route } from "revolt.js/dist/api/routes";
|
|
||||||
|
|
||||||
import { createContext } from "preact";
|
import { createContext } from "preact";
|
||||||
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
||||||
|
|
||||||
import { dispatch } from "../../redux";
|
import { useApplicationState } from "../../mobx/State";
|
||||||
import { connectState } from "../../redux/connector";
|
|
||||||
import { AuthState } from "../../redux/reducers/auth";
|
|
||||||
|
|
||||||
import Preloader from "../../components/ui/Preloader";
|
import Preloader from "../../components/ui/Preloader";
|
||||||
|
|
||||||
import { Children } from "../../types/Preact";
|
import { Children } from "../../types/Preact";
|
||||||
import { useIntermediate } from "../intermediate/Intermediate";
|
import { useIntermediate } from "../intermediate/Intermediate";
|
||||||
import { registerEvents, setReconnectDisallowed } from "./events";
|
import { registerEvents } from "./events";
|
||||||
import { takeError } from "./util";
|
import { takeError } from "./util";
|
||||||
|
|
||||||
export enum ClientStatus {
|
export enum ClientStatus {
|
||||||
INIT,
|
|
||||||
LOADING,
|
|
||||||
READY,
|
READY,
|
||||||
|
LOADING,
|
||||||
OFFLINE,
|
OFFLINE,
|
||||||
DISCONNECTED,
|
DISCONNECTED,
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
|
@ -29,179 +25,75 @@ export enum ClientStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientOperations {
|
export interface ClientOperations {
|
||||||
login: (
|
|
||||||
data: Route<"POST", "/auth/session/login">["data"],
|
|
||||||
) => Promise<void>;
|
|
||||||
logout: (shouldRequest?: boolean) => Promise<void>;
|
logout: (shouldRequest?: boolean) => Promise<void>;
|
||||||
loggedIn: () => boolean;
|
|
||||||
ready: () => boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// By the time they are used, they should all be initialized.
|
|
||||||
// Currently the app does not render until a client is built and the other two are always initialized on first render.
|
|
||||||
// - insert's words
|
|
||||||
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 OperationsContext = createContext<ClientOperations>(null!);
|
export const OperationsContext = createContext<ClientOperations>(null!);
|
||||||
|
export const LogOutContext = createContext(() => {});
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
auth: AuthState;
|
|
||||||
children: Children;
|
children: Children;
|
||||||
};
|
};
|
||||||
|
|
||||||
function Context({ auth, children }: Props) {
|
export default observer(({ children }: Props) => {
|
||||||
|
const state = useApplicationState();
|
||||||
const { openScreen } = useIntermediate();
|
const { openScreen } = useIntermediate();
|
||||||
const [status, setStatus] = useState(ClientStatus.INIT);
|
const [client, setClient] = useState<Client>(null!);
|
||||||
const [client, setClient] = useState<Client>(
|
const [status, setStatus] = useState(ClientStatus.LOADING);
|
||||||
undefined as unknown as Client,
|
const [loaded, setLoaded] = useState(false);
|
||||||
);
|
|
||||||
|
function logout() {
|
||||||
|
setLoaded(false);
|
||||||
|
client.logout(false);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
if (navigator.onLine) {
|
||||||
const client = new Client({
|
new Client().req("GET", "/").then(state.config.set);
|
||||||
autoReconnect: false,
|
}
|
||||||
apiURL: import.meta.env.VITE_API_URL,
|
|
||||||
debug: import.meta.env.DEV,
|
|
||||||
});
|
|
||||||
|
|
||||||
setClient(client);
|
|
||||||
setStatus(ClientStatus.LOADING);
|
|
||||||
})();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (status === ClientStatus.INIT) return null;
|
|
||||||
|
|
||||||
const operations: ClientOperations = useMemo(() => {
|
|
||||||
return {
|
|
||||||
login: async (data) => {
|
|
||||||
setReconnectDisallowed(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const onboarding = await client.login(data);
|
|
||||||
setReconnectDisallowed(false);
|
|
||||||
const login = () =>
|
|
||||||
dispatch({
|
|
||||||
type: "LOGIN",
|
|
||||||
session: client.session as Session,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (onboarding) {
|
|
||||||
openScreen({
|
|
||||||
id: "onboarding",
|
|
||||||
callback: async (username: string) =>
|
|
||||||
onboarding(username, true).then(login),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
login();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setReconnectDisallowed(false);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
logout: async (shouldRequest) => {
|
|
||||||
dispatch({ type: "LOGOUT" });
|
|
||||||
|
|
||||||
client.reset();
|
|
||||||
dispatch({ type: "RESET" });
|
|
||||||
|
|
||||||
openScreen({ id: "none" });
|
|
||||||
setStatus(ClientStatus.READY);
|
|
||||||
|
|
||||||
client.websocket.disconnect();
|
|
||||||
|
|
||||||
if (shouldRequest) {
|
|
||||||
try {
|
|
||||||
await client.logout();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loggedIn: () => typeof auth.active !== "undefined",
|
|
||||||
ready: () =>
|
|
||||||
operations.loggedIn() && typeof client.user !== "undefined",
|
|
||||||
};
|
|
||||||
}, [client, auth.active, openScreen]);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => registerEvents({ operations }, setStatus, client),
|
|
||||||
[client, operations],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
if (state.auth.isLoggedIn()) {
|
||||||
if (auth.active) {
|
const client = state.config.createClient();
|
||||||
dispatch({ type: "QUEUE_FAIL_ALL" });
|
setClient(client);
|
||||||
|
|
||||||
const active = auth.accounts[auth.active];
|
client
|
||||||
client.user = client.users.get(active.session.user_id);
|
.useExistingSession(state.auth.getSession()!)
|
||||||
if (!navigator.onLine) {
|
.then(() => setLoaded(true))
|
||||||
return setStatus(ClientStatus.OFFLINE);
|
.catch((err) => {
|
||||||
}
|
|
||||||
|
|
||||||
if (operations.ready()) setStatus(ClientStatus.CONNECTING);
|
|
||||||
|
|
||||||
if (navigator.onLine) {
|
|
||||||
await client
|
|
||||||
.fetchConfiguration()
|
|
||||||
.catch(() =>
|
|
||||||
console.error("Failed to connect to API server."),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.fetchConfiguration();
|
|
||||||
const callback = await client.useExistingSession(
|
|
||||||
active.session,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (callback) {
|
|
||||||
openScreen({ id: "onboarding", callback });
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setStatus(ClientStatus.DISCONNECTED);
|
|
||||||
const error = takeError(err);
|
const error = takeError(err);
|
||||||
if (error === "Forbidden" || error === "Unauthorized") {
|
if (error === "Forbidden" || error === "Unauthorized") {
|
||||||
operations.logout(true);
|
client.logout(true);
|
||||||
openScreen({ id: "signed_out" });
|
openScreen({ id: "signed_out" });
|
||||||
} else {
|
} else {
|
||||||
|
setStatus(ClientStatus.DISCONNECTED);
|
||||||
openScreen({ id: "error", error });
|
openScreen({ id: "error", error });
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
setStatus(ClientStatus.READY);
|
||||||
await client.fetchConfiguration();
|
setLoaded(true);
|
||||||
} catch (err) {
|
}
|
||||||
console.error("Failed to connect to API server.");
|
}, [state.auth.getSession()]);
|
||||||
}
|
|
||||||
|
|
||||||
setStatus(ClientStatus.READY);
|
useEffect(() => registerEvents(state.auth, setStatus, client), [client]);
|
||||||
}
|
|
||||||
})();
|
|
||||||
// eslint-disable-next-line
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (status === ClientStatus.LOADING) {
|
if (!loaded || status === ClientStatus.LOADING) {
|
||||||
return <Preloader type="spinner" />;
|
return <Preloader type="spinner" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider value={client}>
|
<AppContext.Provider value={client}>
|
||||||
<StatusContext.Provider value={status}>
|
<StatusContext.Provider value={status}>
|
||||||
<OperationsContext.Provider value={operations}>
|
<LogOutContext.Provider value={logout}>
|
||||||
{children}
|
{children}
|
||||||
</OperationsContext.Provider>
|
</LogOutContext.Provider>
|
||||||
</StatusContext.Provider>
|
</StatusContext.Provider>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
export default connectState<{ children: Children }>(Context, (state) => {
|
|
||||||
return {
|
|
||||||
auth: state.auth,
|
|
||||||
sync: state.sync,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useClient = () => useContext(AppContext);
|
export const useClient = () => useContext(AppContext);
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {
|
||||||
import { Language } from "../Locale";
|
import { Language } from "../Locale";
|
||||||
import { AppContext, ClientStatus, StatusContext } from "./RevoltClient";
|
import { AppContext, ClientStatus, StatusContext } from "./RevoltClient";
|
||||||
|
|
||||||
type Props = {
|
/*type Props = {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
locale: Language;
|
locale: Language;
|
||||||
sync: SyncOptions;
|
sync: SyncOptions;
|
||||||
|
@ -150,4 +150,8 @@ export default connectState(SyncManager, (state) => {
|
||||||
sync: state.sync,
|
sync: state.sync,
|
||||||
notifications: state.notifications,
|
notifications: state.notifications,
|
||||||
};
|
};
|
||||||
});
|
});*/
|
||||||
|
|
||||||
|
function SyncManager() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@ import { ClientboundNotification } from "revolt.js/dist/websocket/notifications"
|
||||||
|
|
||||||
import { StateUpdater } from "preact/hooks";
|
import { StateUpdater } from "preact/hooks";
|
||||||
|
|
||||||
|
import Auth from "../../mobx/stores/Auth";
|
||||||
import { dispatch } from "../../redux";
|
import { dispatch } from "../../redux";
|
||||||
|
|
||||||
import { ClientOperations, ClientStatus } from "./RevoltClient";
|
import { ClientStatus } from "./RevoltClient";
|
||||||
|
|
||||||
export let preventReconnect = false;
|
export let preventReconnect = false;
|
||||||
let preventUntil = 0;
|
let preventUntil = 0;
|
||||||
|
@ -16,10 +17,12 @@ export function setReconnectDisallowed(allowed: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerEvents(
|
export function registerEvents(
|
||||||
{ operations }: { operations: ClientOperations },
|
auth: Auth,
|
||||||
setStatus: StateUpdater<ClientStatus>,
|
setStatus: StateUpdater<ClientStatus>,
|
||||||
client: Client,
|
client: Client,
|
||||||
) {
|
) {
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
function attemptReconnect() {
|
function attemptReconnect() {
|
||||||
if (preventReconnect) return;
|
if (preventReconnect) return;
|
||||||
function reconnect() {
|
function reconnect() {
|
||||||
|
@ -36,14 +39,11 @@ export function registerEvents(
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let listeners: Record<string, (...args: any[]) => void> = {
|
let listeners: Record<string, (...args: any[]) => void> = {
|
||||||
connecting: () =>
|
connecting: () => setStatus(ClientStatus.CONNECTING),
|
||||||
operations.ready() && setStatus(ClientStatus.CONNECTING),
|
|
||||||
|
|
||||||
dropped: () => {
|
dropped: () => {
|
||||||
if (operations.ready()) {
|
setStatus(ClientStatus.DISCONNECTED);
|
||||||
setStatus(ClientStatus.DISCONNECTED);
|
attemptReconnect();
|
||||||
attemptReconnect();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
packet: (packet: ClientboundNotification) => {
|
packet: (packet: ClientboundNotification) => {
|
||||||
|
@ -70,6 +70,11 @@ export function registerEvents(
|
||||||
},
|
},
|
||||||
|
|
||||||
ready: () => setStatus(ClientStatus.ONLINE),
|
ready: () => setStatus(ClientStatus.ONLINE),
|
||||||
|
|
||||||
|
logout: () => {
|
||||||
|
auth.logout();
|
||||||
|
setStatus(ClientStatus.READY);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
|
@ -89,19 +94,15 @@ export function registerEvents(
|
||||||
}
|
}
|
||||||
|
|
||||||
const online = () => {
|
const online = () => {
|
||||||
if (operations.ready()) {
|
setStatus(ClientStatus.RECONNECTING);
|
||||||
setStatus(ClientStatus.RECONNECTING);
|
setReconnectDisallowed(false);
|
||||||
setReconnectDisallowed(false);
|
attemptReconnect();
|
||||||
attemptReconnect();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const offline = () => {
|
const offline = () => {
|
||||||
if (operations.ready()) {
|
setReconnectDisallowed(true);
|
||||||
setReconnectDisallowed(true);
|
client.websocket.disconnect();
|
||||||
client.websocket.disconnect();
|
setStatus(ClientStatus.OFFLINE);
|
||||||
setStatus(ClientStatus.OFFLINE);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("online", online);
|
window.addEventListener("online", online);
|
||||||
|
|
|
@ -105,14 +105,14 @@ type Action =
|
||||||
| { action: "create_channel"; target: Server }
|
| { action: "create_channel"; target: Server }
|
||||||
| { action: "create_category"; target: Server }
|
| { action: "create_category"; target: Server }
|
||||||
| {
|
| {
|
||||||
action: "create_invite";
|
action: "create_invite";
|
||||||
target: Channel;
|
target: Channel;
|
||||||
}
|
}
|
||||||
| { action: "leave_group"; target: Channel }
|
| { action: "leave_group"; target: Channel }
|
||||||
| {
|
| {
|
||||||
action: "delete_channel";
|
action: "delete_channel";
|
||||||
target: Channel;
|
target: Channel;
|
||||||
}
|
}
|
||||||
| { action: "close_dm"; target: Channel }
|
| { action: "close_dm"; target: Channel }
|
||||||
| { action: "leave_server"; target: Server }
|
| { action: "leave_server"; target: Server }
|
||||||
| { action: "delete_server"; target: Server }
|
| { action: "delete_server"; target: Server }
|
||||||
|
@ -123,10 +123,10 @@ type Action =
|
||||||
| { action: "open_server_settings"; id: string }
|
| { action: "open_server_settings"; id: string }
|
||||||
| { action: "open_server_channel_settings"; server: string; id: string }
|
| { action: "open_server_channel_settings"; server: string; id: string }
|
||||||
| {
|
| {
|
||||||
action: "set_notification_state";
|
action: "set_notification_state";
|
||||||
key: string;
|
key: string;
|
||||||
state?: NotificationState;
|
state?: NotificationState;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notifications: Notifications;
|
notifications: Notifications;
|
||||||
|
@ -488,8 +488,9 @@ function ContextMenus(props: Props) {
|
||||||
elements.push(
|
elements.push(
|
||||||
<MenuItem data={action} disabled={disabled}>
|
<MenuItem data={action} disabled={disabled}>
|
||||||
<Text
|
<Text
|
||||||
id={`app.context_menu.${locale ?? action.action
|
id={`app.context_menu.${
|
||||||
}`}
|
locale ?? action.action
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
{tip && <div className="tip">{tip}</div>}
|
{tip && <div className="tip">{tip}</div>}
|
||||||
</MenuItem>,
|
</MenuItem>,
|
||||||
|
@ -545,8 +546,8 @@ function ContextMenus(props: Props) {
|
||||||
const user = uid ? client.users.get(uid) : undefined;
|
const user = uid ? client.users.get(uid) : undefined;
|
||||||
const serverChannel =
|
const serverChannel =
|
||||||
targetChannel &&
|
targetChannel &&
|
||||||
(targetChannel.channel_type === "TextChannel" ||
|
(targetChannel.channel_type === "TextChannel" ||
|
||||||
targetChannel.channel_type === "VoiceChannel")
|
targetChannel.channel_type === "VoiceChannel")
|
||||||
? targetChannel
|
? targetChannel
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
@ -558,8 +559,8 @@ function ContextMenus(props: Props) {
|
||||||
(server
|
(server
|
||||||
? server.permission
|
? server.permission
|
||||||
: serverChannel
|
: serverChannel
|
||||||
? serverChannel.server?.permission
|
? serverChannel.server?.permission
|
||||||
: 0) || 0;
|
: 0) || 0;
|
||||||
const userPermissions = (user ? user.permission : 0) || 0;
|
const userPermissions = (user ? user.permission : 0) || 0;
|
||||||
|
|
||||||
if (unread) {
|
if (unread) {
|
||||||
|
@ -705,7 +706,8 @@ function ContextMenus(props: Props) {
|
||||||
if (message && !queued) {
|
if (message && !queued) {
|
||||||
const sendPermission =
|
const sendPermission =
|
||||||
message.channel &&
|
message.channel &&
|
||||||
message.channel.permission & ChannelPermission.SendMessage
|
message.channel.permission &
|
||||||
|
ChannelPermission.SendMessage;
|
||||||
|
|
||||||
if (sendPermission) {
|
if (sendPermission) {
|
||||||
generateAction({
|
generateAction({
|
||||||
|
@ -741,7 +743,7 @@ function ContextMenus(props: Props) {
|
||||||
if (
|
if (
|
||||||
message.author_id === userId ||
|
message.author_id === userId ||
|
||||||
channelPermissions &
|
channelPermissions &
|
||||||
ChannelPermission.ManageMessages
|
ChannelPermission.ManageMessages
|
||||||
) {
|
) {
|
||||||
generateAction({
|
generateAction({
|
||||||
action: "delete_message",
|
action: "delete_message",
|
||||||
|
@ -765,8 +767,8 @@ function ContextMenus(props: Props) {
|
||||||
type === "Image"
|
type === "Image"
|
||||||
? "open_image"
|
? "open_image"
|
||||||
: type === "Video"
|
: type === "Video"
|
||||||
? "open_video"
|
? "open_video"
|
||||||
: "open_file",
|
: "open_file",
|
||||||
);
|
);
|
||||||
|
|
||||||
generateAction(
|
generateAction(
|
||||||
|
@ -777,8 +779,8 @@ function ContextMenus(props: Props) {
|
||||||
type === "Image"
|
type === "Image"
|
||||||
? "save_image"
|
? "save_image"
|
||||||
: type === "Video"
|
: type === "Video"
|
||||||
? "save_video"
|
? "save_video"
|
||||||
: "save_file",
|
: "save_file",
|
||||||
);
|
);
|
||||||
|
|
||||||
generateAction(
|
generateAction(
|
||||||
|
@ -930,9 +932,9 @@ function ContextMenus(props: Props) {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
serverPermissions &
|
serverPermissions &
|
||||||
ServerPermission.ChangeNickname ||
|
ServerPermission.ChangeNickname ||
|
||||||
serverPermissions &
|
serverPermissions &
|
||||||
ServerPermission.ChangeAvatar
|
ServerPermission.ChangeAvatar
|
||||||
)
|
)
|
||||||
generateAction(
|
generateAction(
|
||||||
{ action: "edit_identity", target: server },
|
{ action: "edit_identity", target: server },
|
||||||
|
@ -976,10 +978,10 @@ function ContextMenus(props: Props) {
|
||||||
sid
|
sid
|
||||||
? "copy_sid"
|
? "copy_sid"
|
||||||
: cid
|
: cid
|
||||||
? "copy_cid"
|
? "copy_cid"
|
||||||
: message
|
: message
|
||||||
? "copy_mid"
|
? "copy_mid"
|
||||||
: "copy_uid",
|
: "copy_uid",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Draft from "./stores/Draft";
|
||||||
import Experiments from "./stores/Experiments";
|
import Experiments from "./stores/Experiments";
|
||||||
import Layout from "./stores/Layout";
|
import Layout from "./stores/Layout";
|
||||||
import LocaleOptions from "./stores/LocaleOptions";
|
import LocaleOptions from "./stores/LocaleOptions";
|
||||||
|
import ServerConfig from "./stores/ServerConfig";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles global application state.
|
* Handles global application state.
|
||||||
|
@ -20,6 +21,7 @@ export default class State {
|
||||||
locale: LocaleOptions;
|
locale: LocaleOptions;
|
||||||
experiments: Experiments;
|
experiments: Experiments;
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
|
config: ServerConfig;
|
||||||
|
|
||||||
private persistent: [string, Persistent<unknown>][] = [];
|
private persistent: [string, Persistent<unknown>][] = [];
|
||||||
|
|
||||||
|
@ -32,12 +34,16 @@ export default class State {
|
||||||
this.locale = new LocaleOptions();
|
this.locale = new LocaleOptions();
|
||||||
this.experiments = new Experiments();
|
this.experiments = new Experiments();
|
||||||
this.layout = new Layout();
|
this.layout = new Layout();
|
||||||
|
this.config = new ServerConfig();
|
||||||
|
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
this.registerListeners = this.registerListeners.bind(this);
|
this.registerListeners = this.registerListeners.bind(this);
|
||||||
this.register();
|
this.register();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Categorise and register stores referenced on this object.
|
||||||
|
*/
|
||||||
private register() {
|
private register() {
|
||||||
for (const key of Object.keys(this)) {
|
for (const key of Object.keys(this)) {
|
||||||
const obj = (
|
const obj = (
|
||||||
|
@ -65,12 +71,22 @@ export default class State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register reaction listeners for persistent data stores.
|
||||||
|
* @returns Function to dispose of listeners
|
||||||
|
*/
|
||||||
registerListeners() {
|
registerListeners() {
|
||||||
const listeners = this.persistent.map(([id, store]) => {
|
const listeners = this.persistent.map(([id, store]) => {
|
||||||
return reaction(
|
return reaction(
|
||||||
() => store.toJSON(),
|
() => store.toJSON(),
|
||||||
(value) => {
|
async (value) => {
|
||||||
localforage.setItem(id, value);
|
try {
|
||||||
|
await localforage.setItem(id, value);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to serialise!");
|
||||||
|
console.error(err);
|
||||||
|
console.error(value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -78,6 +94,9 @@ export default class State {
|
||||||
return () => listeners.forEach((x) => x());
|
return () => listeners.forEach((x) => x());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load data stores from local storage.
|
||||||
|
*/
|
||||||
async hydrate() {
|
async hydrate() {
|
||||||
for (const [id, store] of this.persistent) {
|
for (const [id, store] of this.persistent) {
|
||||||
const data = await localforage.getItem(id);
|
const data = await localforage.getItem(id);
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import { makeAutoObservable, ObservableMap } from "mobx";
|
import { action, computed, makeAutoObservable, ObservableMap } from "mobx";
|
||||||
import { Session } from "revolt-api/types/Auth";
|
import { Session } from "revolt-api/types/Auth";
|
||||||
import { Nullable } from "revolt.js/dist/util/null";
|
import { Nullable } from "revolt.js/dist/util/null";
|
||||||
|
|
||||||
|
import { mapToRecord } from "../../lib/conversion";
|
||||||
|
|
||||||
import Persistent from "../interfaces/Persistent";
|
import Persistent from "../interfaces/Persistent";
|
||||||
import Store from "../interfaces/Store";
|
import Store from "../interfaces/Store";
|
||||||
|
|
||||||
|
interface Account {
|
||||||
|
session: Session;
|
||||||
|
}
|
||||||
|
|
||||||
interface Data {
|
interface Data {
|
||||||
sessions: Record<string, Session>;
|
sessions: Record<string, Account> | [string, Account][];
|
||||||
current?: string;
|
current?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +21,7 @@ interface Data {
|
||||||
* accounts and their sessions.
|
* accounts and their sessions.
|
||||||
*/
|
*/
|
||||||
export default class Auth implements Store, Persistent<Data> {
|
export default class Auth implements Store, Persistent<Data> {
|
||||||
private sessions: ObservableMap<string, Session>;
|
private sessions: ObservableMap<string, Account>;
|
||||||
private current: Nullable<string>;
|
private current: Nullable<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,17 +37,27 @@ export default class Auth implements Store, Persistent<Data> {
|
||||||
return "auth";
|
return "auth";
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
@action toJSON() {
|
||||||
return {
|
return {
|
||||||
sessions: [...this.sessions],
|
sessions: JSON.parse(JSON.stringify(this.sessions)),
|
||||||
current: this.current ?? undefined,
|
current: this.current ?? undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate(data: Data) {
|
@action hydrate(data: Data) {
|
||||||
Object.keys(data.sessions).forEach((id) =>
|
if (Array.isArray(data.sessions)) {
|
||||||
this.sessions.set(id, data.sessions[id]),
|
data.sessions.forEach(([key, value]) =>
|
||||||
);
|
this.sessions.set(key, value),
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
typeof data.sessions === "object" &&
|
||||||
|
data.sessions !== null
|
||||||
|
) {
|
||||||
|
let v = data.sessions;
|
||||||
|
Object.keys(data.sessions).forEach((id) =>
|
||||||
|
this.sessions.set(id, v[id]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (data.current && this.sessions.has(data.current)) {
|
if (data.current && this.sessions.has(data.current)) {
|
||||||
this.current = data.current;
|
this.current = data.current;
|
||||||
|
@ -52,8 +68,8 @@ export default class Auth implements Store, Persistent<Data> {
|
||||||
* Add a new session to the auth manager.
|
* Add a new session to the auth manager.
|
||||||
* @param session Session
|
* @param session Session
|
||||||
*/
|
*/
|
||||||
setSession(session: Session) {
|
@action setSession(session: Session) {
|
||||||
this.sessions.set(session.user_id, session);
|
this.sessions.set(session.user_id, { session });
|
||||||
this.current = session.user_id;
|
this.current = session.user_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,11 +77,28 @@ export default class Auth implements Store, Persistent<Data> {
|
||||||
* Remove existing session by user ID.
|
* Remove existing session by user ID.
|
||||||
* @param user_id User ID tied to session
|
* @param user_id User ID tied to session
|
||||||
*/
|
*/
|
||||||
removeSession(user_id: string) {
|
@action removeSession(user_id: string) {
|
||||||
this.sessions.delete(user_id);
|
|
||||||
|
|
||||||
if (user_id == this.current) {
|
if (user_id == this.current) {
|
||||||
this.current = null;
|
this.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.sessions.delete(user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action logout() {
|
||||||
|
this.current && this.removeSession(this.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed getSession() {
|
||||||
|
if (!this.current) return;
|
||||||
|
return this.sessions.get(this.current)!.session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether we are currently logged in.
|
||||||
|
* @returns Whether we are logged in
|
||||||
|
*/
|
||||||
|
@computed isLoggedIn() {
|
||||||
|
return this.current !== null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
75
src/mobx/stores/ServerConfig.ts
Normal file
75
src/mobx/stores/ServerConfig.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import { action, computed, makeAutoObservable } from "mobx";
|
||||||
|
import { RevoltConfiguration } from "revolt-api/types/Core";
|
||||||
|
import { Client } from "revolt.js";
|
||||||
|
import { Nullable } from "revolt.js/dist/util/null";
|
||||||
|
|
||||||
|
import Persistent from "../interfaces/Persistent";
|
||||||
|
import Store from "../interfaces/Store";
|
||||||
|
|
||||||
|
interface Data {
|
||||||
|
config?: RevoltConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores server configuration data.
|
||||||
|
*/
|
||||||
|
export default class ServerConfig
|
||||||
|
implements Store, Persistent<RevoltConfiguration>
|
||||||
|
{
|
||||||
|
private config: Nullable<RevoltConfiguration>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct new ServerConfig store.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.config = null;
|
||||||
|
makeAutoObservable(this);
|
||||||
|
this.set = this.set.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return "server_conf";
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return JSON.parse(JSON.stringify(this.config));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action hydrate(data: RevoltConfiguration) {
|
||||||
|
this.config = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Revolt client.
|
||||||
|
* @returns Revolt client
|
||||||
|
*/
|
||||||
|
createClient() {
|
||||||
|
const client = new Client({
|
||||||
|
autoReconnect: false,
|
||||||
|
apiURL: import.meta.env.VITE_API_URL,
|
||||||
|
debug: import.meta.env.DEV,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.config !== null) {
|
||||||
|
client.configuration = this.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get server configuration.
|
||||||
|
* @returns Server configuration
|
||||||
|
*/
|
||||||
|
@computed get() {
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set server configuration.
|
||||||
|
* @param config Server configuration
|
||||||
|
*/
|
||||||
|
@action set(config: RevoltConfiguration) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { Route, Switch } from "react-router-dom";
|
import { Route, Switch } from "react-router-dom";
|
||||||
import { LIBRARY_VERSION } from "revolt.js";
|
import { LIBRARY_VERSION } from "revolt.js";
|
||||||
|
@ -6,22 +7,24 @@ import styles from "./Login.module.scss";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useContext } from "preact/hooks";
|
import { useContext } from "preact/hooks";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../mobx/State";
|
||||||
|
|
||||||
import { ThemeContext } from "../../context/Theme";
|
import { ThemeContext } from "../../context/Theme";
|
||||||
import { AppContext } from "../../context/revoltjs/RevoltClient";
|
import { AppContext } from "../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
import LocaleSelector from "../../components/common/LocaleSelector";
|
import LocaleSelector from "../../components/common/LocaleSelector";
|
||||||
|
import background from "./background.jpg";
|
||||||
|
|
||||||
import { Titlebar } from "../../components/native/Titlebar";
|
import { Titlebar } from "../../components/native/Titlebar";
|
||||||
import { APP_VERSION } from "../../version";
|
import { APP_VERSION } from "../../version";
|
||||||
import background from "./background.jpg";
|
|
||||||
import { FormCreate } from "./forms/FormCreate";
|
import { FormCreate } from "./forms/FormCreate";
|
||||||
import { FormLogin } from "./forms/FormLogin";
|
import { FormLogin } from "./forms/FormLogin";
|
||||||
import { FormReset, FormSendReset } from "./forms/FormReset";
|
import { FormReset, FormSendReset } from "./forms/FormReset";
|
||||||
import { FormResend, FormVerify } from "./forms/FormVerify";
|
import { FormResend, FormVerify } from "./forms/FormVerify";
|
||||||
|
|
||||||
export default function Login() {
|
export default observer(() => {
|
||||||
const theme = useContext(ThemeContext);
|
const theme = useContext(ThemeContext);
|
||||||
const client = useContext(AppContext);
|
const configuration = useApplicationState().config.get();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -35,8 +38,7 @@ export default function Login() {
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.attribution}>
|
<div className={styles.attribution}>
|
||||||
<span>
|
<span>
|
||||||
API:{" "}
|
API: <code>{configuration?.revolt ?? "???"}</code>{" "}
|
||||||
<code>{client.configuration?.revolt ?? "???"}</code>{" "}
|
|
||||||
· revolt.js: <code>{LIBRARY_VERSION}</code>{" "}
|
· revolt.js: <code>{LIBRARY_VERSION}</code>{" "}
|
||||||
· App: <code>{APP_VERSION}</code>
|
· App: <code>{APP_VERSION}</code>
|
||||||
</span>
|
</span>
|
||||||
|
@ -80,4 +82,4 @@ export default function Login() {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
import styles from "../Login.module.scss";
|
import styles from "../Login.module.scss";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useContext, useEffect } from "preact/hooks";
|
import { useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
import Preloader from "../../../components/ui/Preloader";
|
import Preloader from "../../../components/ui/Preloader";
|
||||||
|
|
||||||
|
@ -13,22 +14,22 @@ export interface CaptchaProps {
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CaptchaBlock(props: CaptchaProps) {
|
export const CaptchaBlock = observer((props: CaptchaProps) => {
|
||||||
const client = useContext(AppContext);
|
const configuration = useApplicationState().config.get();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!client.configuration?.features.captcha.enabled) {
|
if (!configuration?.features.captcha.enabled) {
|
||||||
props.onSuccess();
|
props.onSuccess();
|
||||||
}
|
}
|
||||||
}, [client.configuration?.features.captcha.enabled, props]);
|
}, [configuration?.features.captcha.enabled, props]);
|
||||||
|
|
||||||
if (!client.configuration?.features.captcha.enabled)
|
if (!configuration?.features.captcha.enabled)
|
||||||
return <Preloader type="spinner" />;
|
return <Preloader type="spinner" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<HCaptcha
|
<HCaptcha
|
||||||
sitekey={client.configuration.features.captcha.key}
|
sitekey={configuration.features.captcha.key}
|
||||||
onVerify={(token) => props.onSuccess(token)}
|
onVerify={(token) => props.onSuccess(token)}
|
||||||
/>
|
/>
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
|
@ -38,4 +39,4 @@ export function CaptchaBlock(props: CaptchaProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -6,6 +6,8 @@ import styles from "../Login.module.scss";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useContext, useState } from "preact/hooks";
|
import { useContext, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||||
import { takeError } from "../../../context/revoltjs/util";
|
import { takeError } from "../../../context/revoltjs/util";
|
||||||
|
|
||||||
|
@ -44,7 +46,7 @@ interface FormInputs {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Form({ page, callback }: Props) {
|
export function Form({ page, callback }: Props) {
|
||||||
const client = useContext(AppContext);
|
const configuration = useApplicationState().config.get();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [success, setSuccess] = useState<string | undefined>(undefined);
|
const [success, setSuccess] = useState<string | undefined>(undefined);
|
||||||
|
@ -80,10 +82,7 @@ export function Form({ page, callback }: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (configuration?.features.captcha.enabled && page !== "reset") {
|
||||||
client.configuration?.features.captcha.enabled &&
|
|
||||||
page !== "reset"
|
|
||||||
) {
|
|
||||||
setCaptcha({
|
setCaptcha({
|
||||||
onSuccess: async (captcha) => {
|
onSuccess: async (captcha) => {
|
||||||
setCaptcha(undefined);
|
setCaptcha(undefined);
|
||||||
|
@ -111,7 +110,7 @@ export function Form({ page, callback }: Props) {
|
||||||
if (typeof success !== "undefined") {
|
if (typeof success !== "undefined") {
|
||||||
return (
|
return (
|
||||||
<div className={styles.success}>
|
<div className={styles.success}>
|
||||||
{client.configuration?.features.email ? (
|
{configuration?.features.email ? (
|
||||||
<>
|
<>
|
||||||
<Envelope size={72} />
|
<Envelope size={72} />
|
||||||
<h2>
|
<h2>
|
||||||
|
@ -172,15 +171,14 @@ export function Form({ page, callback }: Props) {
|
||||||
error={errors.password?.message}
|
error={errors.password?.message}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{client.configuration?.features.invite_only &&
|
{configuration?.features.invite_only && page === "create" && (
|
||||||
page === "create" && (
|
<FormField
|
||||||
<FormField
|
type="invite"
|
||||||
type="invite"
|
register={register}
|
||||||
register={register}
|
showOverline
|
||||||
showOverline
|
error={errors.invite?.message}
|
||||||
error={errors.invite?.message}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
{error && (
|
{error && (
|
||||||
<Overline type="error" error={error}>
|
<Overline type="error" error={error}>
|
||||||
<Text id={`login.error.${page}`} />
|
<Text id={`login.error.${page}`} />
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { useContext } from "preact/hooks";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
|
||||||
|
|
||||||
import { Form } from "./Form";
|
import { Form } from "./Form";
|
||||||
|
|
||||||
export function FormCreate() {
|
export function FormCreate() {
|
||||||
const client = useContext(AppContext);
|
const config = useApplicationState().config;
|
||||||
|
const client = config.createClient();
|
||||||
return <Form page="create" callback={(data) => client.register(data)} />;
|
return <Form page="create" callback={(data) => client.register(data)} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import { detect } from "detect-browser";
|
import { detect } from "detect-browser";
|
||||||
import { useHistory } from "react-router-dom";
|
import { Session } from "revolt-api/types/Auth";
|
||||||
|
import { Client } from "revolt.js";
|
||||||
|
|
||||||
import { useContext } from "preact/hooks";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
import { OperationsContext } from "../../../context/revoltjs/RevoltClient";
|
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||||
|
|
||||||
import { Form } from "./Form";
|
import { Form } from "./Form";
|
||||||
|
|
||||||
export function FormLogin() {
|
export function FormLogin() {
|
||||||
const { login } = useContext(OperationsContext);
|
const auth = useApplicationState().auth;
|
||||||
const history = useHistory();
|
const { openScreen } = useIntermediate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
|
@ -34,8 +35,40 @@ export function FormLogin() {
|
||||||
friendly_name = "Unknown Device";
|
friendly_name = "Unknown Device";
|
||||||
}
|
}
|
||||||
|
|
||||||
await login({ ...data, friendly_name });
|
// ! FIXME: temporary login flow code
|
||||||
history.push("/");
|
// This should be replaced in the future.
|
||||||
|
const client = new Client();
|
||||||
|
await client.fetchConfiguration();
|
||||||
|
const session = (await client.req(
|
||||||
|
"POST",
|
||||||
|
"/auth/session/login",
|
||||||
|
{ ...data, friendly_name },
|
||||||
|
)) as unknown as Session;
|
||||||
|
|
||||||
|
client.session = session;
|
||||||
|
(client as any).Axios.defaults.headers = {
|
||||||
|
"x-session-token": session?.token,
|
||||||
|
};
|
||||||
|
|
||||||
|
function login() {
|
||||||
|
auth.setSession(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { onboarding } = await client.req(
|
||||||
|
"GET",
|
||||||
|
"/onboard/hello",
|
||||||
|
);
|
||||||
|
if (onboarding) {
|
||||||
|
openScreen({
|
||||||
|
id: "onboarding",
|
||||||
|
callback: async (username: string) =>
|
||||||
|
client
|
||||||
|
.completeOnboarding({ username }, false)
|
||||||
|
.then(login),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
login();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,12 +2,15 @@ import { useHistory, useParams } from "react-router-dom";
|
||||||
|
|
||||||
import { useContext } from "preact/hooks";
|
import { useContext } from "preact/hooks";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
import { Form } from "./Form";
|
import { Form } from "./Form";
|
||||||
|
|
||||||
export function FormSendReset() {
|
export function FormSendReset() {
|
||||||
const client = useContext(AppContext);
|
const config = useApplicationState().config;
|
||||||
|
const client = config.createClient();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { useHistory, useParams } from "react-router-dom";
|
||||||
|
|
||||||
import { useContext, useEffect, useState } from "preact/hooks";
|
import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||||
import { takeError } from "../../../context/revoltjs/util";
|
import { takeError } from "../../../context/revoltjs/util";
|
||||||
|
|
||||||
|
@ -11,7 +13,8 @@ import Preloader from "../../../components/ui/Preloader";
|
||||||
import { Form } from "./Form";
|
import { Form } from "./Form";
|
||||||
|
|
||||||
export function FormResend() {
|
export function FormResend() {
|
||||||
const client = useContext(AppContext);
|
const config = useApplicationState().config;
|
||||||
|
const client = config.createClient();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
|
|
|
@ -32,7 +32,6 @@ function mapMailProvider(email?: string): [string, string] | undefined {
|
||||||
case "outlook.com.br":
|
case "outlook.com.br":
|
||||||
case "outlook.cl":
|
case "outlook.cl":
|
||||||
case "outlook.cz":
|
case "outlook.cz":
|
||||||
case "outlook.dk":
|
|
||||||
case "outlook.com.gr":
|
case "outlook.com.gr":
|
||||||
case "outlook.co.il":
|
case "outlook.co.il":
|
||||||
case "outlook.in":
|
case "outlook.in":
|
||||||
|
|
|
@ -29,10 +29,7 @@ import { useContext } from "preact/hooks";
|
||||||
import { useApplicationState } from "../../mobx/State";
|
import { useApplicationState } from "../../mobx/State";
|
||||||
|
|
||||||
import RequiresOnline from "../../context/revoltjs/RequiresOnline";
|
import RequiresOnline from "../../context/revoltjs/RequiresOnline";
|
||||||
import {
|
import { AppContext, LogOutContext } from "../../context/revoltjs/RevoltClient";
|
||||||
AppContext,
|
|
||||||
OperationsContext,
|
|
||||||
} from "../../context/revoltjs/RevoltClient";
|
|
||||||
|
|
||||||
import LineDivider from "../../components/ui/LineDivider";
|
import LineDivider from "../../components/ui/LineDivider";
|
||||||
|
|
||||||
|
@ -57,7 +54,7 @@ import { ThemeShop } from "./panes/ThemeShop";
|
||||||
export default observer(() => {
|
export default observer(() => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const operations = useContext(OperationsContext);
|
const logout = useContext(LogOutContext);
|
||||||
const experiments = useApplicationState().experiments;
|
const experiments = useApplicationState().experiments;
|
||||||
|
|
||||||
function switchPage(to?: string) {
|
function switchPage(to?: string) {
|
||||||
|
@ -220,7 +217,7 @@ export default observer(() => {
|
||||||
</a>
|
</a>
|
||||||
<LineDivider />
|
<LineDivider />
|
||||||
<ButtonItem
|
<ButtonItem
|
||||||
onClick={() => operations.logout()}
|
onClick={logout}
|
||||||
className={styles.logOut}
|
className={styles.logOut}
|
||||||
compact>
|
compact>
|
||||||
<LogOut size={20} />
|
<LogOut size={20} />
|
||||||
|
|
|
@ -3765,10 +3765,10 @@ revolt-api@^0.5.3-alpha.9:
|
||||||
resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-alpha.9.tgz#46e75b7d8f9c6702df39039b829dddbb7897f237"
|
resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-alpha.9.tgz#46e75b7d8f9c6702df39039b829dddbb7897f237"
|
||||||
integrity sha512-L8K9uPV3ME8bLdtWm8L9iPQvFM0GghA+5LzmWFjd6Gbn56u22ZYub2lABi4iHrWgeA2X41dGSsuSBgHSlts9Og==
|
integrity sha512-L8K9uPV3ME8bLdtWm8L9iPQvFM0GghA+5LzmWFjd6Gbn56u22ZYub2lABi4iHrWgeA2X41dGSsuSBgHSlts9Og==
|
||||||
|
|
||||||
revolt.js@^5.1.0-alpha.10:
|
revolt.js@5.1.0-alpha.15:
|
||||||
version "5.1.0-alpha.10"
|
version "5.1.0-alpha.15"
|
||||||
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.1.0-alpha.10.tgz#e393ac8524e629d3359135651b23b044c0cc9b7b"
|
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.1.0-alpha.15.tgz#a2be1f29de93f1ec18f0e502ecb65ade55c0070d"
|
||||||
integrity sha512-wEmBMJkZE/oWy6mzVZg1qw5QC9CE+Gb7sTFlJl+C4pbXfTJWAtY311Tjbd2tX8w3ohYDmN338bVfCW4cOQ8GXQ==
|
integrity sha512-1gGcGDv1+J5NlmnX099XafKugCebACg9ke0NA754I4hLTNMMwkZyphyvYWWWkI394qn2mA3NG7WgEmrIoZUtgw==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^0.21.4"
|
axios "^0.21.4"
|
||||||
eventemitter3 "^4.0.7"
|
eventemitter3 "^4.0.7"
|
||||||
|
|
Loading…
Reference in a new issue