mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-04 11:51:36 +02:00
Implement new design for Welcome page (#33211)
* Convert welcome.html to React component In advance of changes to use Compound * Fix types * Fix tests * Update styling to match Figma * Fix random capitalisation * Tweak styling * Regenerate i18n * Update tests * Make linter happy * Iterate
This commit is contained in:
parent
7b89d84acb
commit
4b4289e211
2
.github/workflows/static_analysis.yaml
vendored
2
.github/workflows/static_analysis.yaml
vendored
@ -103,7 +103,7 @@ jobs:
|
||||
voip|element_call
|
||||
error|invalid_json
|
||||
error|misconfigured
|
||||
welcome_to_element
|
||||
welcome|title_element
|
||||
devtools|settings|elementCallUrl
|
||||
labs|sliding_sync_description
|
||||
settings|voip|noise_suppression_description
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 957 KiB |
@ -20,7 +20,7 @@ test.use({
|
||||
|
||||
test("Shows the welcome page by default", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page.getByRole("heading", { name: "Welcome to Element!" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Be in your element" })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "Sign in" })).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
@ -126,7 +126,7 @@ test.describe("Login", () => {
|
||||
await page.goto("/");
|
||||
|
||||
// Should give us the welcome page initially
|
||||
await expect(page.getByRole("heading", { name: "Welcome to Element!" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Be in your element" })).toBeVisible();
|
||||
|
||||
// Start the login process
|
||||
await expect(axe).toHaveNoViolations();
|
||||
|
||||
@ -105,6 +105,7 @@
|
||||
@import "./views/auth/_AuthPage.pcss";
|
||||
@import "./views/auth/_CompleteSecurityBody.pcss";
|
||||
@import "./views/auth/_CountryDropdown.pcss";
|
||||
@import "./views/auth/_DefaultWelcome.pcss";
|
||||
@import "./views/auth/_InteractiveAuthEntryComponents.pcss";
|
||||
@import "./views/auth/_LanguageSelector.pcss";
|
||||
@import "./views/auth/_LoginWithQR.pcss";
|
||||
|
||||
43
apps/web/res/css/views/auth/_DefaultWelcome.pcss
Normal file
43
apps/web/res/css/views/auth/_DefaultWelcome.pcss
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_DefaultWelcome {
|
||||
text-align: center;
|
||||
|
||||
.mx_DefaultWelcome_logo img {
|
||||
height: 48px;
|
||||
aspect-ratio: auto;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: var(--cpd-space-4x) 0 var(--cpd-space-2x);
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--cpd-color-text-secondary);
|
||||
margin-top: var(--cpd-space-2x);
|
||||
}
|
||||
|
||||
.mx_DefaultWelcome_buttons {
|
||||
margin: var(--cpd-space-6x) 0 var(--cpd-space-1x);
|
||||
padding-bottom: var(--cpd-space-4x);
|
||||
border-bottom: 1px solid var(--cpd-color-separator-primary);
|
||||
|
||||
a {
|
||||
width: 380px;
|
||||
margin-bottom: var(--cpd-space-4x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_WelcomePage_registrationDisabled {
|
||||
.mx_DefaultWelcome_buttons_register {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: var(--cpd-color-bg-canvas-default);
|
||||
box-sizing: border-box;
|
||||
padding: var(--cpd-space-11x) var(--cpd-space-12x) var(--cpd-space-4x);
|
||||
|
||||
&.mx_WelcomePage_registrationDisabled {
|
||||
.mx_ButtonCreateAccount {
|
||||
display: none;
|
||||
@ -18,7 +22,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
.mx_Welcome .mx_AuthBody_language {
|
||||
width: 160px;
|
||||
margin-bottom: 10px;
|
||||
margin: var(--cpd-space-1x) 0;
|
||||
}
|
||||
|
||||
/* Invert image colours in dark mode. */
|
||||
|
||||
@ -1,191 +0,0 @@
|
||||
<style type="text/css">
|
||||
/* we deliberately inline style here to avoid flash-of-CSS problems, and to avoid
|
||||
* voodoo where we have to set display: none by default
|
||||
*/
|
||||
|
||||
.mx_Header_title::after {
|
||||
content: "!";
|
||||
}
|
||||
|
||||
.mx_Parent {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 25px 35px;
|
||||
}
|
||||
|
||||
.mx_Logo {
|
||||
height: 54px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.mx_ButtonGroup {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mx_ButtonRow {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-justify-content: space-around;
|
||||
-ms-flex-pack: distribute;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
|
||||
.mx_ButtonRow > * {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.mx_ButtonRow > *:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.mx_ButtonRow > *:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.mx_ButtonParent {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 10px 20px;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
width: 150px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 10px center;
|
||||
text-decoration: none;
|
||||
color: #2e2f32 !important;
|
||||
}
|
||||
|
||||
.mx_ButtonLabel {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.mx_Header_title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 0;
|
||||
}
|
||||
|
||||
.mx_Header_subtitle {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
|
||||
.mx_ButtonSignIn {
|
||||
background-color: #368bd6;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.mx_ButtonCreateAccount {
|
||||
background-color: #0dbd8b;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.mx_SecondaryButton {
|
||||
background-color: #ffffff;
|
||||
color: #2e2f32;
|
||||
}
|
||||
|
||||
.mx_Button_iconSignIn {
|
||||
background-image: url("welcome/images/icon-sign-in.svg");
|
||||
}
|
||||
.mx_Button_iconCreateAccount {
|
||||
background-image: url("welcome/images/icon-create-account.svg");
|
||||
}
|
||||
.mx_Button_iconHelp {
|
||||
background-image: url("welcome/images/icon-help.svg");
|
||||
}
|
||||
.mx_Button_iconRoomDirectory {
|
||||
background-image: url("welcome/images/icon-room-directory.svg");
|
||||
}
|
||||
|
||||
/*
|
||||
.mx_WelcomePage_loggedIn is applied by EmbeddedPage from the Welcome component
|
||||
If it is set on the page, we should show the buttons. Otherwise, we have to assume
|
||||
we don't have an account and should hide them. No account == no guest account either.
|
||||
*/
|
||||
.mx_WelcomePage:not(.mx_WelcomePage_loggedIn) .mx_WelcomePage_guestFunctions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_ButtonRow.mx_WelcomePage_guestFunctions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.mx_ButtonRow.mx_WelcomePage_guestFunctions > div {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
.mx_ButtonRow {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_ButtonRow > * {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="mx_Parent">
|
||||
<a href="https://element.io" target="_blank" rel="noopener">
|
||||
<img src="$logoUrl" alt="$brand" class="mx_Logo" />
|
||||
</a>
|
||||
<h1 class="mx_Header_title">_t("welcome_to_element")</h1>
|
||||
<!-- XXX: Our translations system isn't smart enough to recognize variables in the HTML, so we manually do it -->
|
||||
<h2 class="mx_Header_subtitle">_t("powered_by_matrix_with_logo")</h2>
|
||||
<div class="mx_ButtonGroup">
|
||||
<div class="mx_ButtonRow">
|
||||
<a href="#/login" class="mx_ButtonParent mx_ButtonSignIn mx_Button_iconSignIn">
|
||||
<div class="mx_ButtonLabel">_t("action|sign_in")</div>
|
||||
</a>
|
||||
<a href="#/register" class="mx_ButtonParent mx_ButtonCreateAccount mx_Button_iconCreateAccount">
|
||||
<div class="mx_ButtonLabel">_t("action|create_account")</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mx_ButtonRow mx_WelcomePage_guestFunctions">
|
||||
<div>
|
||||
<a href="#/directory" class="mx_ButtonParent mx_SecondaryButton mx_Button_iconRoomDirectory">
|
||||
<div class="mx_ButtonLabel">_t("action|explore_rooms")</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,3 +0,0 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17 9C17 13.4183 13.4183 17 9 17C4.58172 17 1 13.4183 1 9C1 4.58172 4.58172 1 9 1C13.4183 1 17 4.58172 17 9ZM5.25 9C5.25 8.58579 5.58579 8.25 6 8.25H8.25V6C8.25 5.58579 8.58579 5.25 9 5.25C9.41421 5.25 9.75 5.58579 9.75 6V8.25H12C12.4142 8.25 12.75 8.58579 12.75 9C12.75 9.41421 12.4142 9.75 12 9.75H9.75V12C9.75 12.4142 9.41421 12.75 9 12.75C8.58579 12.75 8.25 12.4142 8.25 12V9.75H6C5.58579 9.75 5.25 9.41421 5.25 9Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 587 B |
@ -1,16 +0,0 @@
|
||||
|
||||
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Experiments" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="Home" transform="translate(-672.000000, -577.000000)" stroke="#000000" stroke-width="1.6">
|
||||
<g id="Group-11" transform="translate(621.000000, 176.000000)">
|
||||
<g id="Group-10" transform="translate(39.000000, 391.000000)">
|
||||
<g id="help-circle" transform="translate(13.000000, 11.000000)">
|
||||
<circle id="Oval" cx="10" cy="10" r="10"></circle>
|
||||
<path d="M7.09,7 C7.57543688,5.62004444 8.98538362,4.79140632 10.4271763,5.0387121 C11.868969,5.28601788 12.9221794,6.53715293 12.92,8 C12.92,10 9.92,11 9.92,11" id="Path"></path>
|
||||
<path d="M10,15 L10.0050017,15.0050017" id="Path"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,4 +0,0 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17 9C17 13.4183 13.4183 17 9 17C4.58172 17 1 13.4183 1 9C1 4.58172 4.58172 1 9 1C13.4183 1 17 4.58172 17 9ZM13.375 5.3266C13.5583 4.92826 13.0716 4.44152 12.6733 4.62491L7.66968 6.9285C7.33893 7.08077 7.08014 7.33956 6.92787 7.67031L4.62428 12.6739C4.44089 13.0722 4.92763 13.559 5.32597 13.3756L10.3295 11.072C10.6603 10.9197 10.9191 10.6609 11.0714 10.3302L13.375 5.3266Z" fill="black"/>
|
||||
<path d="M9.8835 9.88413C9.39534 10.3723 8.60389 10.3723 8.11573 9.88413C7.62757 9.39597 7.62757 8.60452 8.11573 8.11636C8.60389 7.62821 9.39534 7.62821 9.8835 8.11636C10.3717 8.60452 10.3717 9.39597 9.8835 9.88413Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 775 B |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 53 KiB |
@ -52,3 +52,13 @@ export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[k
|
||||
export type Assignable<Object, Item> = {
|
||||
[Key in keyof Object]: Object[Key] extends Item ? Key : never;
|
||||
}[keyof Object];
|
||||
|
||||
/**
|
||||
* Like `Partial` but for applied to all nested objects.
|
||||
* Based on https://dev.to/perennialautodidact/adventures-in-typescript-deeppartial-2f2a
|
||||
*/
|
||||
export type DeepPartial<T> = T extends object
|
||||
? {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
}
|
||||
: T;
|
||||
|
||||
@ -52,8 +52,9 @@ export interface IConfigOptions {
|
||||
disable_3pid_login?: boolean;
|
||||
|
||||
brand: string;
|
||||
branding?: {
|
||||
welcome_background_url?: string | string[]; // chosen at random if array
|
||||
branding: {
|
||||
welcome_background_url: string | string[]; // chosen at random if array
|
||||
logo_link_url: string;
|
||||
auth_header_logo_url?: string;
|
||||
auth_footer_links?: { text: string; url: string }[];
|
||||
};
|
||||
|
||||
@ -12,11 +12,16 @@ import { mergeWith } from "lodash";
|
||||
import { SnakedObject } from "./utils/SnakedObject";
|
||||
import { type IConfigOptions } from "./IConfigOptions";
|
||||
import { isObject, objectClone } from "./utils/objects";
|
||||
import { type DeepReadonly, type Defaultize } from "./@types/common";
|
||||
import { type DeepPartial, type DeepReadonly, type Defaultize } from "./@types/common";
|
||||
|
||||
// see element-web config.md for docs, or the IConfigOptions interface for dev docs
|
||||
export const DEFAULTS: DeepReadonly<IConfigOptions> = {
|
||||
brand: "Element",
|
||||
branding: {
|
||||
logo_link_url: "https://element.io",
|
||||
auth_header_logo_url: "themes/element/img/logos/element-logo.svg",
|
||||
welcome_background_url: "themes/element/img/backgrounds/lake.jpg",
|
||||
},
|
||||
help_url: "https://element.io/help",
|
||||
help_encryption_url: "https://element.io/help#encryption",
|
||||
help_key_storage_url: "https://element.io/help#encryption5",
|
||||
@ -70,7 +75,7 @@ export type ConfigOptions = Defaultize<IConfigOptions, typeof DEFAULTS>;
|
||||
|
||||
function mergeConfig(
|
||||
config: DeepReadonly<IConfigOptions>,
|
||||
changes: DeepReadonly<Partial<IConfigOptions>>,
|
||||
changes: DeepReadonly<DeepPartial<IConfigOptions>>,
|
||||
): DeepReadonly<IConfigOptions> {
|
||||
// return { ...config, ...changes };
|
||||
return mergeWith(objectClone(config), changes, (objValue, srcValue) => {
|
||||
@ -136,7 +141,7 @@ export default class SdkConfig {
|
||||
SdkConfig.setInstance(mergeConfig(DEFAULTS, {})); // safe to cast - defaults will be applied
|
||||
}
|
||||
|
||||
public static add(cfg: Partial<ConfigOptions>): void {
|
||||
public static add(cfg: DeepPartial<ConfigOptions>): void {
|
||||
SdkConfig.put(mergeConfig(SdkConfig.get(), cfg));
|
||||
}
|
||||
}
|
||||
|
||||
20
apps/web/src/branding.ts
Normal file
20
apps/web/src/branding.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import SdkConfig from "./SdkConfig.ts";
|
||||
|
||||
const ELEMENT_BRAND = "Element";
|
||||
|
||||
/**
|
||||
* Returns whether the app is currently branded.
|
||||
* This is currently a naive check of whether the `brand` config starts with the substring `Element ` or is the literal `Element`,
|
||||
* which correctly covers `Element` (release), `Element Nightly` & `Element Pro`.
|
||||
*/
|
||||
export const isElementBranded = (): boolean => {
|
||||
const brand = SdkConfig.get("brand");
|
||||
return brand === ELEMENT_BRAND || brand.startsWith(ELEMENT_BRAND + " ");
|
||||
};
|
||||
@ -31,16 +31,13 @@ export default class AuthPage extends React.PureComponent<React.PropsWithChildre
|
||||
if (AuthPage.welcomeBackgroundUrl) return AuthPage.welcomeBackgroundUrl;
|
||||
|
||||
const brandingConfig = SdkConfig.getObject("branding");
|
||||
AuthPage.welcomeBackgroundUrl = "themes/element/img/backgrounds/lake.jpg";
|
||||
|
||||
const configuredUrl = brandingConfig?.get("welcome_background_url");
|
||||
if (configuredUrl) {
|
||||
if (Array.isArray(configuredUrl)) {
|
||||
const index = Math.floor(Math.random() * configuredUrl.length);
|
||||
AuthPage.welcomeBackgroundUrl = configuredUrl[index];
|
||||
} else {
|
||||
AuthPage.welcomeBackgroundUrl = configuredUrl;
|
||||
}
|
||||
const urls = brandingConfig.get("welcome_background_url");
|
||||
if (Array.isArray(urls)) {
|
||||
const index = Math.floor(Math.random() * urls.length);
|
||||
AuthPage.welcomeBackgroundUrl = urls[index];
|
||||
} else {
|
||||
AuthPage.welcomeBackgroundUrl = urls;
|
||||
}
|
||||
|
||||
return AuthPage.welcomeBackgroundUrl;
|
||||
|
||||
51
apps/web/src/components/views/auth/DefaultWelcome.tsx
Normal file
51
apps/web/src/components/views/auth/DefaultWelcome.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Button, Heading, Text } from "@vector-im/compound-web";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import SdkConfig from "../../../SdkConfig.ts";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg.ts";
|
||||
import { isElementBranded } from "../../../branding.ts";
|
||||
|
||||
const DefaultWelcome: React.FC = () => {
|
||||
const brand = SdkConfig.get("brand");
|
||||
const branding = SdkConfig.getObject("branding");
|
||||
const logoUrl = branding.get("auth_header_logo_url");
|
||||
|
||||
const showGuestFunctions = !!MatrixClientPeg.get();
|
||||
const isElement = isElementBranded();
|
||||
|
||||
return (
|
||||
<div className="mx_DefaultWelcome">
|
||||
<a href={branding.get("logo_link_url")} target="_blank" rel="noopener" className="mx_DefaultWelcome_logo">
|
||||
<img src={logoUrl} alt={brand} />
|
||||
</a>
|
||||
<Heading as="h1" weight="semibold">
|
||||
{isElement ? _t("welcome|title_element") : _t("welcome|title_generic", { brand })}
|
||||
</Heading>
|
||||
{isElement && <Text size="md">{_t("welcome|tagline_element")}</Text>}
|
||||
|
||||
<div className="mx_DefaultWelcome_buttons">
|
||||
<Button as="a" href="#/login" kind="primary" size="sm">
|
||||
{_t("action|sign_in")}
|
||||
</Button>
|
||||
<Button as="a" href="#/register" kind="secondary" size="sm">
|
||||
{_t("action|create_account")}
|
||||
</Button>
|
||||
{showGuestFunctions && (
|
||||
<Button as="a" href="#/directory" kind="tertiary" size="sm">
|
||||
{_t("action|explore_rooms")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DefaultWelcome;
|
||||
@ -5,9 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { type ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import { type EmptyObject } from "matrix-js-sdk/src/matrix";
|
||||
import { Glass } from "@vector-im/compound-web";
|
||||
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import AuthPage from "./AuthPage";
|
||||
@ -16,14 +17,12 @@ import { UIFeature } from "../../../settings/UIFeature";
|
||||
import LanguageSelector from "./LanguageSelector";
|
||||
import EmbeddedPage from "../../structures/EmbeddedPage";
|
||||
import { MATRIX_LOGO_HTML } from "../../structures/static-page-vars";
|
||||
import DefaultWelcome from "./DefaultWelcome.tsx";
|
||||
|
||||
export default class Welcome extends React.PureComponent<EmptyObject> {
|
||||
public render(): React.ReactNode {
|
||||
const pagesConfig = SdkConfig.getObject("embedded_pages");
|
||||
let pageUrl: string | undefined;
|
||||
if (pagesConfig) {
|
||||
pageUrl = pagesConfig.get("welcome_url");
|
||||
}
|
||||
const pageUrl = pagesConfig?.get("welcome_url");
|
||||
|
||||
const replaceMap: Record<string, string> = {
|
||||
"$brand": SdkConfig.get("brand"),
|
||||
@ -33,25 +32,25 @@ export default class Welcome extends React.PureComponent<EmptyObject> {
|
||||
"[matrix]": MATRIX_LOGO_HTML,
|
||||
};
|
||||
|
||||
if (!pageUrl) {
|
||||
// Fall back to default and replace $logoUrl in welcome.html
|
||||
const brandingConfig = SdkConfig.getObject("branding");
|
||||
const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg";
|
||||
replaceMap["$logoUrl"] = logoUrl;
|
||||
pageUrl = "welcome.html";
|
||||
let body: ReactNode;
|
||||
if (pageUrl) {
|
||||
body = <EmbeddedPage className="mx_WelcomePage" url={pageUrl} replaceMap={replaceMap} />;
|
||||
} else {
|
||||
body = <DefaultWelcome />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthPage>
|
||||
<div
|
||||
className={classNames("mx_Welcome", {
|
||||
mx_WelcomePage_registrationDisabled: !SettingsStore.getValue(UIFeature.Registration),
|
||||
})}
|
||||
data-testid="mx_welcome_screen"
|
||||
>
|
||||
<EmbeddedPage className="mx_WelcomePage" url={pageUrl} replaceMap={replaceMap} />
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<AuthPage addBlur={false}>
|
||||
<Glass>
|
||||
<div
|
||||
className={classNames("mx_Welcome", {
|
||||
mx_WelcomePage_registrationDisabled: !SettingsStore.getValue(UIFeature.Registration),
|
||||
})}
|
||||
>
|
||||
{body}
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
</Glass>
|
||||
</AuthPage>
|
||||
);
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
"copy_link": "Copy link",
|
||||
"create": "Create",
|
||||
"create_a_room": "Create a room",
|
||||
"create_account": "Create Account",
|
||||
"create_account": "Create account",
|
||||
"decline": "Decline",
|
||||
"decline_and_block": "Decline and block",
|
||||
"decline_invite": "Decline invite",
|
||||
@ -1816,7 +1816,6 @@
|
||||
"restricted": "Restricted"
|
||||
},
|
||||
"powered_by_matrix": "Powered by Matrix",
|
||||
"powered_by_matrix_with_logo": "Decentralised, encrypted chat & collaboration powered by $matrixLogo",
|
||||
"presence": {
|
||||
"away": "Away",
|
||||
"busy": "Busy",
|
||||
@ -3981,7 +3980,11 @@
|
||||
"you_are_presenting": "You are presenting"
|
||||
},
|
||||
"web_default_device_name": "%(appName)s: %(browserName)s on %(osName)s",
|
||||
"welcome_to_element": "Welcome to Element",
|
||||
"welcome": {
|
||||
"tagline_element": "Supercharged for speed and simplicity.",
|
||||
"title_element": "Be in your element",
|
||||
"title_generic": "Welcome to %(brand)s"
|
||||
},
|
||||
"widget": {
|
||||
"added_by": "Widget added by",
|
||||
"capabilities_dialog": {
|
||||
|
||||
@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
import "fake-indexeddb/auto";
|
||||
import React, { type ComponentProps } from "react";
|
||||
import { fireEvent, render, type RenderResult, screen, waitFor, within, act } from "jest-matrix-react";
|
||||
import fetchMock from "@fetch-mock/jest";
|
||||
import { type Mocked, mocked } from "jest-mock";
|
||||
import { ClientEvent, type MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix";
|
||||
import { type MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
|
||||
@ -1637,7 +1636,6 @@ describe("<MatrixChat />", () => {
|
||||
|
||||
// Flaky test, see https://github.com/element-hq/element-web/issues/30337
|
||||
it("waits for other tab to stop during startup", async () => {
|
||||
fetchMock.get("end:/welcome.html", { body: "<h1>Hello</h1>" });
|
||||
jest.spyOn(Lifecycle, "attemptDelegatedAuthLogin");
|
||||
|
||||
// simulate an active window
|
||||
@ -1668,7 +1666,7 @@ describe("<MatrixChat />", () => {
|
||||
expect(Lifecycle.attemptDelegatedAuthLogin).toHaveBeenCalled();
|
||||
|
||||
// should just show the welcome screen
|
||||
await rendered.findByText("Hello");
|
||||
await rendered.findByText("Welcome to Test");
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
@ -124,13 +124,9 @@ exports[`<MatrixChat /> Multi-tab lockout waits for other tab to stop during sta
|
||||
class="mx_AuthPage"
|
||||
>
|
||||
<div
|
||||
class="mx_AuthPage_modal mx_AuthPage_modal_withBlur"
|
||||
class="mx_AuthPage_modal"
|
||||
style="position: relative;"
|
||||
>
|
||||
<div
|
||||
class="mx_AuthPage_modalBlur"
|
||||
style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; filter: blur(40px);"
|
||||
/>
|
||||
<main
|
||||
aria-live="polite"
|
||||
class="mx_AuthPage_modalContent"
|
||||
@ -138,53 +134,99 @@ exports[`<MatrixChat /> Multi-tab lockout waits for other tab to stop during sta
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_Welcome"
|
||||
data-testid="mx_welcome_screen"
|
||||
class="_glass_sepwu_8"
|
||||
>
|
||||
<div
|
||||
class="mx_WelcomePage mx_WelcomePage_loggedIn"
|
||||
class="mx_Welcome"
|
||||
>
|
||||
<div
|
||||
class="mx_WelcomePage_body"
|
||||
class="mx_DefaultWelcome"
|
||||
>
|
||||
<h1>
|
||||
Hello
|
||||
<a
|
||||
class="mx_DefaultWelcome_logo"
|
||||
href="https://element.io"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
alt="Test"
|
||||
src="themes/element/img/logos/element-logo.svg"
|
||||
/>
|
||||
</a>
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
|
||||
>
|
||||
Welcome to Test
|
||||
</h1>
|
||||
<div
|
||||
class="mx_DefaultWelcome_buttons"
|
||||
>
|
||||
<a
|
||||
class="_button_13vu4_8"
|
||||
data-kind="primary"
|
||||
data-size="sm"
|
||||
href="#/login"
|
||||
role="link"
|
||||
tabindex="0"
|
||||
>
|
||||
Sign in
|
||||
</a>
|
||||
<a
|
||||
class="_button_13vu4_8"
|
||||
data-kind="secondary"
|
||||
data-size="sm"
|
||||
href="#/register"
|
||||
role="link"
|
||||
tabindex="0"
|
||||
>
|
||||
Create account
|
||||
</a>
|
||||
<a
|
||||
class="_button_13vu4_8"
|
||||
data-kind="tertiary"
|
||||
data-size="sm"
|
||||
href="#/directory"
|
||||
role="link"
|
||||
tabindex="0"
|
||||
>
|
||||
Explore rooms
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dropdown mx_LanguageDropdown mx_AuthBody_language"
|
||||
>
|
||||
<div
|
||||
aria-describedby="mx_LanguageDropdown_value"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Language Dropdown"
|
||||
aria-owns="mx_LanguageDropdown_input"
|
||||
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="mx_Dropdown mx_LanguageDropdown mx_AuthBody_language"
|
||||
>
|
||||
<div
|
||||
class="mx_Dropdown_option"
|
||||
id="mx_LanguageDropdown_value"
|
||||
aria-describedby="mx_LanguageDropdown_value"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Language Dropdown"
|
||||
aria-owns="mx_LanguageDropdown_input"
|
||||
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div>
|
||||
English
|
||||
<div
|
||||
class="mx_Dropdown_option"
|
||||
id="mx_LanguageDropdown_value"
|
||||
>
|
||||
<div>
|
||||
English
|
||||
</div>
|
||||
</div>
|
||||
<svg
|
||||
class="mx_Dropdown_arrow"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<svg
|
||||
class="mx_Dropdown_arrow"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -711,8 +711,6 @@ export default (env: string, argv: Record<string, any>): webpack.Configuration =
|
||||
"res/jitsi_external_api.min.js",
|
||||
"res/jitsi_external_api.min.js.LICENSE.txt",
|
||||
"res/manifest.json",
|
||||
"res/welcome.html",
|
||||
{ from: "welcome/**", context: path.resolve(__dirname, "res") },
|
||||
{ from: "themes/**", context: path.resolve(__dirname, "res") },
|
||||
{ from: "vector-icons/**", context: path.resolve(__dirname, "res") },
|
||||
{ from: "decoder-ring/**", context: path.resolve(__dirname, "res") },
|
||||
|
||||
@ -214,14 +214,15 @@ Starting with `branding`, the following subproperties are available:
|
||||
|
||||
1. `welcome_background_url`: When a string, the URL for the full-page image background of the login, registration, and welcome
|
||||
pages. This property can additionally be an array to have the app choose an image at random from the selections.
|
||||
2. `auth_header_logo_url`: A URL to the logo used on the login, registration, etc pages.
|
||||
3. `auth_footer_links`: A list of links to add to the footer during login, registration, etc. Each entry must have a `text` and
|
||||
2. `logo_link_url`: When rendering the a brand Logo, if it is linkified, this is the link it should direct to. Defaults to `https://element.io`.
|
||||
3. `auth_header_logo_url`: A URL to the logo used on the login, registration, etc pages.
|
||||
4. `auth_footer_links`: A list of links to add to the footer during login, registration, etc. Each entry must have a `text` and
|
||||
`url` property.
|
||||
|
||||
`embedded_pages` can be configured as such:
|
||||
|
||||
1. `welcome_url`: A URL to an HTML page to show as a welcome page (landing on `#/welcome`). When not specified, the default
|
||||
`welcome.html` that ships with Element will be used instead.
|
||||
1. `welcome_url`: A URL to an HTML page to show as a welcome page (landing on `#/welcome`).
|
||||
When not specified, a default internal component will be used instead.
|
||||
2. `home_url`: A URL to an HTML page to show within the app as the "home" page. When the app doesn't have a room/screen to
|
||||
show the user, it will use the home page instead. The home page is additionally accessible from the user menu. By default,
|
||||
no home page is set and therefore a hardcoded landing screen is used. More documentation and examples are [here](./custom-home.md).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user