Adapt restricted guests module for Login UX
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type FC } from "react";
|
||||||
|
import { Button } from "@vector-im/compound-web";
|
||||||
|
import { type AccountAuthInfo, type Api } from "@element-hq/element-web-module-api";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { type ModuleConfig } from "./config.ts";
|
||||||
|
import RegisterDialog from "./RegisterDialog.tsx";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
api: Api;
|
||||||
|
config: ModuleConfig;
|
||||||
|
onLoggedIn(data: AccountAuthInfo): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled.aside`
|
||||||
|
margin: var(--cpd-space-3x) 0;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AuthFooter: FC<Props> = ({ api, config, onLoggedIn }) => {
|
||||||
|
const onTryJoin = async (): Promise<void> => {
|
||||||
|
const { finished } = api.openDialog(
|
||||||
|
{
|
||||||
|
title: api.i18n.translate("register_dialog_title"),
|
||||||
|
},
|
||||||
|
RegisterDialog,
|
||||||
|
{
|
||||||
|
api,
|
||||||
|
config,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { model: accountAuthInfo, ok } = await finished;
|
||||||
|
|
||||||
|
if (ok && accountAuthInfo) {
|
||||||
|
onLoggedIn(accountAuthInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Button onClick={onTryJoin} size="sm" kind="secondary">
|
||||||
|
{api.i18n.translate("join_cta")}
|
||||||
|
</Button>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthFooter;
|
||||||
@@ -15,6 +15,7 @@ import { type ModuleConfig } from "./config.ts";
|
|||||||
interface RegisterDialogProps extends DialogProps<AccountAuthInfo> {
|
interface RegisterDialogProps extends DialogProps<AccountAuthInfo> {
|
||||||
api: Api;
|
api: Api;
|
||||||
config: ModuleConfig;
|
config: ModuleConfig;
|
||||||
|
showLoginLink?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const enum State {
|
const enum State {
|
||||||
@@ -29,7 +30,7 @@ const StyledFormRoot = styled(Form.Root)`
|
|||||||
font-feature-settings: normal;
|
font-feature-settings: normal;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const RegisterDialog: FC<RegisterDialogProps> = ({ api, config, onCancel, onSubmit }) => {
|
const RegisterDialog: FC<RegisterDialogProps> = ({ api, config, onCancel, onSubmit, showLoginLink }) => {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [state, setState] = useState<State>(State.Idle);
|
const [state, setState] = useState<State>(State.Idle);
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ const RegisterDialog: FC<RegisterDialogProps> = ({ api, config, onCancel, onSubm
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledFormRoot onSubmit={trySubmit}>
|
<StyledFormRoot onSubmit={trySubmit}>
|
||||||
<Form.Field name="mxid">
|
<Form.Field name="name">
|
||||||
<Form.Label>{api.i18n.translate("register_dialog_register_username_label")}</Form.Label>
|
<Form.Label>{api.i18n.translate("register_dialog_register_username_label")}</Form.Label>
|
||||||
<Form.TextControl
|
<Form.TextControl
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -82,9 +83,11 @@ const RegisterDialog: FC<RegisterDialogProps> = ({ api, config, onCancel, onSubm
|
|||||||
{message}
|
{message}
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
|
||||||
<a href={config.skip_single_sign_on ? "/#/login" : "/#/start_sso"} onClick={onCancel}>
|
{showLoginLink && (
|
||||||
{api.i18n.translate("register_dialog_existing_account")}
|
<a href={config.skip_single_sign_on ? "/#/login" : "/#/start_sso"} onClick={onCancel}>
|
||||||
</a>
|
{api.i18n.translate("register_dialog_existing_account")}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
<Form.Submit disabled={disabled || !username}>
|
<Form.Submit disabled={disabled || !username}>
|
||||||
{api.i18n.translate("register_dialog_continue_label")}
|
{api.i18n.translate("register_dialog_continue_label")}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const RoomPreviewBar: FC<RoomPreviewBarProps> = ({ api, config, roomId, roomAlia
|
|||||||
{
|
{
|
||||||
api,
|
api,
|
||||||
config,
|
config,
|
||||||
|
showLoginLink: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -40,5 +40,8 @@ export const CONFIG_KEY = "io.element.element-web-modules.restricted-guests";
|
|||||||
declare module "@element-hq/element-web-module-api" {
|
declare module "@element-hq/element-web-module-api" {
|
||||||
export interface Config {
|
export interface Config {
|
||||||
[CONFIG_KEY]: input<ConfigSchema>;
|
[CONFIG_KEY]: input<ConfigSchema>;
|
||||||
|
sso_redirect_options?: {
|
||||||
|
immediate?: boolean; // incompatible option
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Translations from "./translations.json";
|
|||||||
import { ModuleConfig, CONFIG_KEY } from "./config";
|
import { ModuleConfig, CONFIG_KEY } from "./config";
|
||||||
import { name as ModuleName } from "../package.json";
|
import { name as ModuleName } from "../package.json";
|
||||||
import RoomPreviewBar from "./RoomPreviewBar.tsx";
|
import RoomPreviewBar from "./RoomPreviewBar.tsx";
|
||||||
|
import AuthFooter from "./AuthFooter.tsx";
|
||||||
|
|
||||||
const GUEST_INVISIBLE_COMPONENTS = [
|
const GUEST_INVISIBLE_COMPONENTS = [
|
||||||
"UIComponent.sendInvites",
|
"UIComponent.sendInvites",
|
||||||
@@ -41,14 +42,26 @@ class RestrictedGuestsModule implements Module {
|
|||||||
throw new Error(`Errors in module configuration for "${ModuleName}"`);
|
throw new Error(`Errors in module configuration for "${ModuleName}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const appConfig = this.api.config.get();
|
||||||
|
if (appConfig.sso_redirect_options?.immediate) {
|
||||||
|
console.warn(`${ModuleName} found incompatible option 'sso_redirect_options.immediate', turning it off.`);
|
||||||
|
appConfig.sso_redirect_options.immediate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Room preview bar customisations (for Matrix guest support)
|
||||||
this.api.customComponents.registerRoomPreviewBar((props, OriginalComponent) => (
|
this.api.customComponents.registerRoomPreviewBar((props, OriginalComponent) => (
|
||||||
<RoomPreviewBar {...props} api={this.api} config={this.config!}>
|
<RoomPreviewBar {...props} api={this.api} config={this.config!}>
|
||||||
<OriginalComponent {...props} />
|
<OriginalComponent {...props} />
|
||||||
</RoomPreviewBar>
|
</RoomPreviewBar>
|
||||||
));
|
));
|
||||||
|
this.api.customisations.registerShouldShowComponent(this.shouldShowComponent);
|
||||||
|
|
||||||
// TODO replace this with a more generic API
|
// Login component customisations (for no guest support)
|
||||||
this.api._registerLegacyComponentVisibilityCustomisations(this);
|
this.api.customComponents.registerLoginComponent((props, OriginalComponent) => (
|
||||||
|
<OriginalComponent {...props}>
|
||||||
|
<AuthFooter onLoggedIn={props.onLoggedIn} api={this.api} config={this.config!} />
|
||||||
|
</OriginalComponent>
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,11 +71,12 @@ class RestrictedGuestsModule implements Module {
|
|||||||
* @returns true, if the user should see the component
|
* @returns true, if the user should see the component
|
||||||
*/
|
*/
|
||||||
public readonly shouldShowComponent = (component: string): boolean => {
|
public readonly shouldShowComponent = (component: string): boolean => {
|
||||||
if (!this.config || !this.api.profile.value.userId?.startsWith(this.config.guest_user_prefix)) {
|
const profile = this.api.profile.value;
|
||||||
return true;
|
if (this.config && (profile.isGuest || profile.userId?.startsWith(this.config.guest_user_prefix))) {
|
||||||
|
return GUEST_INVISIBLE_COMPONENTS.includes(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GUEST_INVISIBLE_COMPONENTS.includes(component);
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"de": "Benutzername"
|
"de": "Benutzername"
|
||||||
},
|
},
|
||||||
"register_dialog_title": {
|
"register_dialog_title": {
|
||||||
"en": "Request room access",
|
"en": "Request access",
|
||||||
"de": "Raumbeitritt anfragen"
|
"de": "Zugriff anfordern"
|
||||||
},
|
},
|
||||||
"register_dialog_busy": {
|
"register_dialog_busy": {
|
||||||
"en": "Creating your account...",
|
"en": "Creating your account...",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"de": "Treten Sie dem Raum bei, um teilzunehmen"
|
"de": "Treten Sie dem Raum bei, um teilzunehmen"
|
||||||
},
|
},
|
||||||
"join_cta": {
|
"join_cta": {
|
||||||
"en": "Join",
|
"en": "Join as guest",
|
||||||
"de": "Verbinden"
|
"de": "Als Gast beitreten"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user