diff --git a/app/components/ChangeLanguage.tsx b/app/components/ChangeLanguage.tsx
new file mode 100644
index 000000000..c5f965071
--- /dev/null
+++ b/app/components/ChangeLanguage.tsx
@@ -0,0 +1,17 @@
+import * as React from "react";
+import { useTranslation } from "react-i18next";
+import { changeLanguage } from "~/utils/language";
+
+type Props = {
+ locale: string;
+};
+
+export default function ChangeLanguage({ locale }: Props) {
+ const { i18n } = useTranslation();
+
+ React.useEffect(() => {
+ void changeLanguage(locale, i18n);
+ }, [locale, i18n]);
+
+ return null;
+}
diff --git a/app/scenes/Login/AuthenticationProvider.tsx b/app/scenes/Login/components/AuthenticationProvider.tsx
similarity index 100%
rename from app/scenes/Login/AuthenticationProvider.tsx
rename to app/scenes/Login/components/AuthenticationProvider.tsx
diff --git a/app/scenes/Login/components/BackButton.tsx b/app/scenes/Login/components/BackButton.tsx
new file mode 100644
index 000000000..28ce8aa6b
--- /dev/null
+++ b/app/scenes/Login/components/BackButton.tsx
@@ -0,0 +1,51 @@
+import { BackIcon } from "outline-icons";
+import * as React from "react";
+import { useTranslation } from "react-i18next";
+import styled from "styled-components";
+import { parseDomain } from "@shared/utils/domains";
+import { Config } from "~/stores/AuthStore";
+import env from "~/env";
+import Desktop from "~/utils/Desktop";
+import isCloudHosted from "~/utils/isCloudHosted";
+
+type Props = {
+ config?: Config;
+};
+
+export default function BackButton({ config }: Props) {
+ const { t } = useTranslation();
+ const isSubdomain = !!config?.hostname;
+
+ if (!isCloudHosted || parseDomain(window.location.origin).custom) {
+ return null;
+ }
+
+ if (Desktop.isElectron() && !isSubdomain) {
+ return null;
+ }
+
+ return (
+
+ {Desktop.isElectron() ? t("Back") : t("Back to home")}
+
+ );
+}
+
+const Link = styled.a`
+ display: flex;
+ align-items: center;
+ color: inherit;
+ padding: ${Desktop.isElectron() ? "48px 32px" : "32px"};
+ font-weight: 500;
+ position: absolute;
+
+ svg {
+ transition: transform 100ms ease-in-out;
+ }
+
+ &:hover {
+ svg {
+ transform: translateX(-4px);
+ }
+ }
+`;
diff --git a/app/scenes/Login/Notices.tsx b/app/scenes/Login/components/Notices.tsx
similarity index 100%
rename from app/scenes/Login/Notices.tsx
rename to app/scenes/Login/components/Notices.tsx
diff --git a/app/scenes/Login/index.tsx b/app/scenes/Login/index.tsx
index ca9500614..de4245890 100644
--- a/app/scenes/Login/index.tsx
+++ b/app/scenes/Login/index.tsx
@@ -1,6 +1,6 @@
import find from "lodash/find";
import { observer } from "mobx-react";
-import { BackIcon, EmailIcon } from "outline-icons";
+import { EmailIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { useLocation, Link, Redirect } from "react-router-dom";
@@ -11,6 +11,7 @@ import { UserPreference } from "@shared/types";
import { parseDomain } from "@shared/utils/domains";
import { Config } from "~/stores/AuthStore";
import ButtonLarge from "~/components/ButtonLarge";
+import ChangeLanguage from "~/components/ChangeLanguage";
import Fade from "~/components/Fade";
import Flex from "~/components/Flex";
import Heading from "~/components/Heading";
@@ -27,28 +28,10 @@ import useStores from "~/hooks/useStores";
import { draggableOnDesktop } from "~/styles";
import Desktop from "~/utils/Desktop";
import isCloudHosted from "~/utils/isCloudHosted";
-import { changeLanguage, detectLanguage } from "~/utils/language";
-import AuthenticationProvider from "./AuthenticationProvider";
-import Notices from "./Notices";
-
-function Header({ config }: { config?: Config | undefined }) {
- const { t } = useTranslation();
- const isSubdomain = !!config?.hostname;
-
- if (!isCloudHosted || parseDomain(window.location.origin).custom) {
- return null;
- }
-
- if (Desktop.isElectron() && !isSubdomain) {
- return null;
- }
-
- return (
-
- {Desktop.isElectron() ? t("Back") : t("Back to home")}
-
- );
-}
+import { detectLanguage } from "~/utils/language";
+import AuthenticationProvider from "./components/AuthenticationProvider";
+import BackButton from "./components/BackButton";
+import Notices from "./components/Notices";
type Props = {
children?: (config?: Config) => React.ReactNode;
@@ -59,7 +42,7 @@ function Login({ children }: Props) {
const query = useQuery();
const notice = query.get("notice");
- const { t, i18n } = useTranslation();
+ const { t } = useTranslation();
const { auth } = useStores();
const { config } = auth;
const [error, setError] = React.useState(null);
@@ -97,13 +80,6 @@ function Login({ children }: Props) {
auth.fetchConfig().catch(setError);
}, [auth]);
- // TODO: Persist detected language to new user
- // Try to detect the user's language and show the login page on its idiom
- // if translation is available
- React.useEffect(() => {
- void changeLanguage(detectLanguage(), i18n);
- }, [i18n]);
-
React.useEffect(() => {
const entries = Object.fromEntries(query.entries());
const existing = getCookie("signupQueryParams");
@@ -134,7 +110,8 @@ function Login({ children }: Props) {
if (error) {
return (
-
+
+ {t("Error")}
@@ -165,7 +142,8 @@ function Login({ children }: Props) {
if (isCloudHosted && isCustomDomain && !config.name) {
return (
-
+
+ {t("Almost there")}…
@@ -182,7 +160,8 @@ function Login({ children }: Props) {
if (Desktop.isElectron() && notice === "domain-required") {
return (
-
+
+
-
+
@@ -248,7 +227,9 @@ function Login({ children }: Props) {
return (
-
+
+
+
1>If you were invited to a workspace, you will find a link to it in the invite email.": "Unable to sign-in. Please navigate to your workspace's custom URL, then try to sign-in again.<1>1>If you were invited to a workspace, you will find a link to it in the invite email.",
+ "Sorry, a new account cannot be created with a personal Gmail address.<1>1>Please use a Google Workspaces account instead.": "Sorry, a new account cannot be created with a personal Gmail address.<1>1>Please use a Google Workspaces account instead.",
+ "The workspace associated with your user is scheduled for deletion and cannot at accessed at this time.": "The workspace associated with your user is scheduled for deletion and cannot at accessed at this time.",
+ "The workspace you authenticated with is not authorized on this installation. Try another?": "The workspace you authenticated with is not authorized on this installation. Try another?",
+ "We could not read the user info supplied by your identity provider.": "We could not read the user info supplied by your identity provider.",
+ "Your account uses email sign-in, please sign-in with email to continue.": "Your account uses email sign-in, please sign-in with email to continue.",
+ "An email sign-in link was recently sent, please check your inbox or try again in a few minutes.": "An email sign-in link was recently sent, please check your inbox or try again in a few minutes.",
+ "Authentication failed – we were unable to sign you in at this time. Please try again.": "Authentication failed – we were unable to sign you in at this time. Please try again.",
+ "Authentication failed – you do not have permission to access this workspace.": "Authentication failed – you do not have permission to access this workspace.",
+ "Sorry, it looks like that sign-in link is no longer valid, please try requesting another.": "Sorry, it looks like that sign-in link is no longer valid, please try requesting another.",
+ "Your account has been suspended. To re-activate your account, please contact a workspace admin.": "Your account has been suspended. To re-activate your account, please contact a workspace admin.",
+ "Authentication failed – this login method was disabled by a team admin.": "Authentication failed – this login method was disabled by a team admin.",
+ "The workspace you are trying to join requires an invite before you can create an account.<1>1>Please request an invite from your workspace admin and try again.": "The workspace you are trying to join requires an invite before you can create an account.<1>1>Please request an invite from your workspace admin and try again.",
+ "Sorry, your domain is not allowed. Please try again with an allowed workspace domain.": "Sorry, your domain is not allowed. Please try again with an allowed workspace domain.",
"Login": "Login",
"Error": "Error",
"Failed to load configuration.": "Failed to load configuration.",
@@ -681,21 +696,6 @@
"You signed in with {{ authProviderName }} last time.": "You signed in with {{ authProviderName }} last time.",
"Or": "Or",
"Already have an account? Go to <1>login1>.": "Already have an account? Go to <1>login1>.",
- "The domain associated with your email address has not been allowed for this workspace.": "The domain associated with your email address has not been allowed for this workspace.",
- "Unable to sign-in. Please navigate to your workspace's custom URL, then try to sign-in again.<1>1>If you were invited to a workspace, you will find a link to it in the invite email.": "Unable to sign-in. Please navigate to your workspace's custom URL, then try to sign-in again.<1>1>If you were invited to a workspace, you will find a link to it in the invite email.",
- "Sorry, a new account cannot be created with a personal Gmail address.<1>1>Please use a Google Workspaces account instead.": "Sorry, a new account cannot be created with a personal Gmail address.<1>1>Please use a Google Workspaces account instead.",
- "The workspace associated with your user is scheduled for deletion and cannot at accessed at this time.": "The workspace associated with your user is scheduled for deletion and cannot at accessed at this time.",
- "The workspace you authenticated with is not authorized on this installation. Try another?": "The workspace you authenticated with is not authorized on this installation. Try another?",
- "We could not read the user info supplied by your identity provider.": "We could not read the user info supplied by your identity provider.",
- "Your account uses email sign-in, please sign-in with email to continue.": "Your account uses email sign-in, please sign-in with email to continue.",
- "An email sign-in link was recently sent, please check your inbox or try again in a few minutes.": "An email sign-in link was recently sent, please check your inbox or try again in a few minutes.",
- "Authentication failed – we were unable to sign you in at this time. Please try again.": "Authentication failed – we were unable to sign you in at this time. Please try again.",
- "Authentication failed – you do not have permission to access this workspace.": "Authentication failed – you do not have permission to access this workspace.",
- "Sorry, it looks like that sign-in link is no longer valid, please try requesting another.": "Sorry, it looks like that sign-in link is no longer valid, please try requesting another.",
- "Your account has been suspended. To re-activate your account, please contact a workspace admin.": "Your account has been suspended. To re-activate your account, please contact a workspace admin.",
- "Authentication failed – this login method was disabled by a team admin.": "Authentication failed – this login method was disabled by a team admin.",
- "The workspace you are trying to join requires an invite before you can create an account.<1>1>Please request an invite from your workspace admin and try again.": "The workspace you are trying to join requires an invite before you can create an account.<1>1>Please request an invite from your workspace admin and try again.",
- "Sorry, your domain is not allowed. Please try again with an allowed workspace domain.": "Sorry, your domain is not allowed. Please try again with an allowed workspace domain.",
"Any collection": "Any collection",
"Any time": "Any time",
"Past day": "Past day",