Login: move thirdparty requests for server and fix image blinking

This commit is contained in:
Timofey Boyko 2024-08-08 17:48:54 +03:00
parent 28c4beb89f
commit 9e040104ec
15 changed files with 85 additions and 105 deletions

View File

@ -144,7 +144,7 @@ const SocialNetworks = (props) => {
return (
<div key={`${item.provider}ProviderItem`}>
<SocialButton
iconName={icon}
IconComponent={icon}
label={getProviderTranslation(label, t, item.linked)}
$iconOptions={iconOptions}
onClick={onClick}

View File

@ -24,11 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { cookies } from "next/headers";
import dynamic from "next/dynamic";
import { SYSTEM_THEME_KEY } from "@docspace/shared/constants";
import { ThemeKeys, WhiteLabelLogoType } from "@docspace/shared/enums";
import { getBgPattern, getLogoUrl } from "@docspace/shared/utils/common";
import { Scrollbar } from "@docspace/shared/components/scrollbar";
import { ColorTheme, ThemeId } from "@docspace/shared/components/color-theme";
@ -56,10 +53,6 @@ export default async function Layout({
getColorTheme(),
]);
const cookieStore = cookies();
const systemTheme = cookieStore.get(SYSTEM_THEME_KEY)?.value as ThemeKeys;
const bgPattern = getBgPattern(colorTheme?.selected);
const objectSettings = typeof settings === "string" ? undefined : settings;
@ -68,8 +61,7 @@ export default async function Layout({
return (
<div style={{ width: "100%", height: "100%" }}>
<SimpleNav systemTheme={systemTheme} />
<SimpleNav />
<LoginFormWrapper id="login-page" bgPattern={bgPattern}>
<div className="bg-cover" />
<Scrollbar id="customScrollBar">

View File

@ -24,7 +24,14 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { getSettings } from "@/utils/actions";
import { PROVIDERS_DATA } from "@docspace/shared/constants";
import {
getCapabilities,
getSettings,
getSSO,
getThirdPartyProviders,
} from "@/utils/actions";
import Login from "@/components/Login";
import LoginForm from "@/components/LoginForm";
import ThirdParty from "@/components/ThirdParty";
@ -32,7 +39,26 @@ import RecoverAccess from "@/components/RecoverAccess";
import Register from "@/components/Register";
async function Page() {
const settings = await getSettings();
const [settings, thirdParty, capabilities, ssoSettings] = await Promise.all([
getSettings(),
getThirdPartyProviders(),
getCapabilities(),
getSSO(),
]);
const ssoUrl = capabilities ? capabilities.ssoUrl : "";
const hideAuthPage = ssoSettings ? ssoSettings.hideAuthPage : false;
const ssoExists = !!ssoUrl;
const oauthDataExists =
!capabilities?.oauthEnabled || !thirdParty || thirdParty.length === 0
? false
: thirdParty
.map((item) => {
if (!(item.provider in PROVIDERS_DATA)) return undefined;
return item;
})
.some((item) => !!item);
return (
<Login>
@ -43,8 +69,16 @@ async function Page() {
cookieSettingsEnabled={settings?.cookieSettingsEnabled}
reCaptchaPublicKey={settings?.recaptchaPublicKey}
reCaptchaType={settings?.recaptchaType}
ldapDomain={capabilities?.ldapDomain}
/>
<ThirdParty
thirdParty={thirdParty}
capabilities={capabilities}
ssoExists={ssoExists}
ssoUrl={ssoUrl}
hideAuthPage={hideAuthPage}
oauthDataExists={oauthDataExists}
/>
<ThirdParty />
{settings.enableAdmMess && <RecoverAccess />}
{settings.enabledJoin && (
<Register

View File

@ -36,11 +36,11 @@ import type {
TFirebaseSettings,
TSettings,
} from "@docspace/shared/api/settings/types";
import FirebaseHelper from "@docspace/shared/utils/firebase";
import useTheme from "@/hooks/useTheme";
import useDeviceType from "@/hooks/useDeviceType";
import useI18N from "@/hooks/useI18N";
import FirebaseHelper from "@docspace/shared/utils/firebase";
import pkg from "../../package.json";

View File

@ -26,6 +26,7 @@
import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
import { Toast } from "@docspace/shared/components/toast";
import { getBaseUrl } from "@docspace/shared/utils/next-ssr-helper";
import { TenantStatus, ThemeKeys } from "@docspace/shared/enums";
@ -58,18 +59,11 @@ export default async function RootLayout({
let redirectUrl = "";
const timers = { otherOperations: 0 };
const startOtherOperationsDate = new Date();
const [settings, colorTheme] = await Promise.all([
getSettings(),
getColorTheme(),
]);
timers.otherOperations =
new Date().getTime() - startOtherOperationsDate.getTime();
if (settings === "access-restricted") redirectUrl = `/${settings}`;
if (settings === "portal-not-found") {
@ -132,7 +126,6 @@ export default async function RootLayout({
systemTheme: systemTheme?.value as ThemeKeys,
}}
redirectURL={redirectUrl}
timers={timers}
>
<Toast isSSR />
{children}

View File

@ -33,7 +33,6 @@ import { useSearchParams } from "next/navigation";
import { useTheme } from "styled-components";
import { Text } from "@docspace/shared/components/text";
import { WhiteLabelLogoType } from "@docspace/shared/enums";
import { getLogoUrl } from "@docspace/shared/utils/common";

View File

@ -38,6 +38,7 @@ import { useTranslation } from "react-i18next";
import ReCAPTCHA from "react-google-recaptcha";
import HCaptcha from "@hcaptcha/react-hcaptcha";
import { useTheme } from "styled-components";
import { Id } from "react-toastify";
import { useSearchParams } from "next/navigation";
import { Text } from "@docspace/shared/components/text";
@ -52,6 +53,7 @@ import { toastr } from "@docspace/shared/components/toast";
import { thirdPartyLogin, checkConfirmLink } from "@docspace/shared/api/user";
import { setWithCredentialsStatus } from "@docspace/shared/api/client";
import { TValidate } from "@docspace/shared/components/email-input/EmailInput.types";
import { RecaptchaType } from "@docspace/shared/enums";
import { LoginFormProps } from "@/types";
import { getEmailFromInvitation, getConfirmDataFromInvitation } from "@/utils";
@ -59,11 +61,11 @@ import { getEmailFromInvitation, getConfirmDataFromInvitation } from "@/utils";
import EmailContainer from "./sub-components/EmailContainer";
import PasswordContainer from "./sub-components/PasswordContainer";
import ForgotContainer from "./sub-components/ForgotContainer";
import LDAPContainer from "./sub-components/LDAPContainer";
import { LoginDispatchContext, LoginValueContext } from "../Login";
import { StyledCaptcha } from "./LoginForm.styled";
import { LoginDispatchContext, LoginValueContext } from "../Login";
import LDAPContainer from "./sub-components/LDAPContainer";
import { RecaptchaType } from "@docspace/shared/enums";
let showToastr = true;
@ -72,10 +74,11 @@ const LoginForm = ({
cookieSettingsEnabled,
reCaptchaPublicKey,
reCaptchaType,
ldapDomain,
}: LoginFormProps) => {
const { isLoading, isModalOpen, ldapDomain } = useContext(LoginValueContext);
const { isLoading, isModalOpen } = useContext(LoginValueContext);
const { setIsLoading } = useContext(LoginDispatchContext);
const toastId = useRef(null);
const toastId = useRef<Id>();
const searchParams = useSearchParams();

View File

@ -54,7 +54,7 @@ interface IEmailContainer {
onBlurEmail: () => void;
onValidateEmail: (res: TValidate) => undefined;
isLdapLogin: boolean;
ldapDomain: string;
ldapDomain?: string;
}
const EmailContainer = ({

View File

@ -49,7 +49,7 @@ const ForgotPasswordModalDialog = ({
userEmail,
onDialogClose,
}: ForgotPasswordModalDialogProps) => {
const [email, setEmail] = useState(userEmail);
const [email, setEmail] = useState(userEmail ?? "");
const [emailError, setEmailError] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [errorText, setErrorText] = useState("");

View File

@ -34,6 +34,7 @@ import { mobile } from "@docspace/shared/utils/device";
import { getLogoUrl } from "@docspace/shared/utils/common";
import { Base, Dark } from "@docspace/shared/themes";
import { ThemeKeys, WhiteLabelLogoType } from "@docspace/shared/enums";
import LanguageComboboxWrapper from "./LanguageCombobox";
const StyledSimpleNav = styled.div`
@ -60,19 +61,19 @@ const StyledSimpleNav = styled.div`
StyledSimpleNav.defaultProps = { theme: Base };
interface SimpleNavProps {
systemTheme: ThemeKeys;
}
interface SimpleNavProps {}
const SimpleNav = ({ systemTheme }: SimpleNavProps) => {
const SimpleNav = ({}: SimpleNavProps) => {
const theme = useTheme();
const isDark = !theme.isBase;
const logoUrl = getLogoUrl(WhiteLabelLogoType.LightSmall, isDark);
return (
<StyledSimpleNav id="login-header">
<img className="logo" src={logoUrl} alt="logo-url" />
<LanguageComboboxWrapper />
{/* <LanguageComboboxWrapper /> */}
</StyledSimpleNav>
);
};

View File

@ -33,22 +33,13 @@ import styled from "styled-components";
import { SocialButtonsGroup } from "@docspace/shared/components/social-buttons-group";
import { Text } from "@docspace/shared/components/text";
import { PROVIDERS_DATA } from "@docspace/shared/constants";
import { getOAuthToken, getLoginLink } from "@docspace/shared/utils/common";
import {
TCapabilities,
TGetSsoSettings,
TThirdPartyProvider,
} from "@docspace/shared/api/settings/types";
import { Nullable } from "@docspace/shared/types";
import SSOIcon from "PUBLIC_DIR/images/sso.react.svg?url";
import {
getCapabilities,
getSSO,
getThirdPartyProviders,
} from "@/utils/actions";
import SSOIcon from "PUBLIC_DIR/images/sso.react.svg";
import { LoginDispatchContext, LoginValueContext } from "./Login";
@ -57,7 +48,23 @@ const StyledThirdParty = styled.div<{ isVisible: boolean }>`
height: auto;
`;
const ThirdParty = () => {
type ThirdPartyProps = {
thirdParty?: TThirdPartyProvider[];
capabilities?: TCapabilities;
ssoUrl?: string;
ssoExists?: boolean;
oauthDataExists?: boolean;
hideAuthPage?: boolean;
};
const ThirdParty = ({
thirdParty,
capabilities,
ssoUrl,
ssoExists,
oauthDataExists,
hideAuthPage,
}: ThirdPartyProps) => {
const { isLoading } = useContext(LoginValueContext);
const { setIsModalOpen, setLdapDomain } = useContext(LoginDispatchContext);
@ -65,33 +72,7 @@ const ThirdParty = () => {
const { t } = useTranslation(["Login", "Common"]);
const [capabilities, setCapabilities] =
React.useState<Nullable<TCapabilities>>(null);
const [thirdPartyProvider, setThirdPartyProvider] =
React.useState<Nullable<TThirdPartyProvider[]>>(null);
const [ssoSettings, setSsoSettings] =
React.useState<Nullable<TGetSsoSettings>>(null);
const getData = useCallback(async () => {
const [thirdParty, capabilities, ssoSettings] = await Promise.all([
getThirdPartyProviders(),
getCapabilities(),
getSSO(),
]);
if (thirdParty) setThirdPartyProvider(thirdParty);
if (capabilities) setCapabilities(capabilities);
if (ssoSettings) setSsoSettings(ssoSettings);
}, []);
useEffect(() => {
getData();
}, [getData]);
useEffect(() => {
const ssoUrl = capabilities ? capabilities.ssoUrl : "";
const hideAuthPage = ssoSettings ? ssoSettings.hideAuthPage : false;
if (capabilities?.ldapEnabled && capabilities.ldapDomain)
setLdapDomain(capabilities.ldapDomain);
@ -102,25 +83,7 @@ const ThirdParty = () => {
) {
window.location.replace(ssoUrl);
}
}, [capabilities, searchParams, ssoSettings, setLdapDomain]);
const ssoExists = () => {
if (capabilities?.ssoUrl) return true;
else return false;
};
const oauthDataExists = () => {
if (!capabilities?.oauthEnabled) return false;
let existProviders = 0;
if (thirdPartyProvider && thirdPartyProvider.length > 0)
thirdPartyProvider?.map((item) => {
if (!(item.provider in PROVIDERS_DATA)) return;
existProviders++;
});
return !!existProviders;
};
}, [capabilities, searchParams, setLdapDomain, ssoUrl, hideAuthPage]);
const onSocialButtonClick = useCallback(
(e: React.MouseEvent<Element, MouseEvent>) => {
@ -171,7 +134,7 @@ const ThirdParty = () => {
[],
);
const ssoProps = ssoExists()
const ssoProps = ssoExists
? {
ssoUrl: capabilities?.ssoUrl,
ssoLabel: capabilities?.ssoLabel,
@ -179,7 +142,7 @@ const ThirdParty = () => {
}
: {};
const isVisible = oauthDataExists() || ssoExists();
const isVisible = oauthDataExists || ssoExists;
return (
isVisible && (
@ -188,7 +151,7 @@ const ThirdParty = () => {
<Text className="or-label">{t("Common:orContinueWith")}</Text>
</div>
<SocialButtonsGroup
providers={thirdPartyProvider ?? undefined}
providers={thirdParty ?? undefined}
onClick={onSocialButtonClick}
onMoreAuthToggle={setIsModalOpen}
t={t}

View File

@ -25,14 +25,12 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React from "react";
import { i18n } from "i18next";
import { getCookie, getLanguage } from "@docspace/shared/utils";
import { getCookie } from "@docspace/shared/utils";
import { LANGUAGE } from "@docspace/shared/constants";
import { TSettings } from "@docspace/shared/api/settings/types";
import { getI18NInstance } from "@/utils/i18n";
import { setCookie } from "@docspace/shared/utils/cookie";
interface UseI18NProps {
settings?: TSettings;

View File

@ -24,8 +24,10 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import useDeviceType from "@/hooks/useDeviceType";
import ErrorBoundary from "@docspace/shared/components/error-boundary/ErrorBoundary";
import useDeviceType from "@/hooks/useDeviceType";
import { ErrorBoundaryProps } from "./ErrorBoundary.types";
const ErrorBoundaryWrapper = (props: ErrorBoundaryProps) => {

View File

@ -33,20 +33,18 @@ import { ThemeProvider } from "@docspace/shared/components/theme-provider";
import { TFirebaseSettings } from "@docspace/shared/api/settings/types";
import FirebaseHelper from "@docspace/shared/utils/firebase";
import { TUser } from "@docspace/shared/api/people/types";
import { ThemeKeys } from "@docspace/shared/enums";
import { Base, Dark } from "@docspace/shared/themes";
import { TDataContext } from "@/types";
import useI18N from "@/hooks/useI18N";
import useTheme from "@/hooks/useTheme";
import pkgFile from "../../package.json";
import ErrorBoundaryWrapper from "./ErrorBoundary";
export const Providers = ({
children,
value,
timers,
redirectURL,
}: {
children: React.ReactNode;
@ -61,10 +59,6 @@ export const Providers = ({
if (redirectURL) window.location.replace(redirectURL);
}, [redirectURL]);
React.useEffect(() => {
console.log("Timers:", { ...timers });
}, [timers]);
const { i18n } = useI18N({
settings: value.settings,
});

View File

@ -86,6 +86,7 @@ export type LoginFormProps = {
reCaptchaPublicKey?: string;
reCaptchaType?: RecaptchaType;
cookieSettingsEnabled: boolean;
ldapDomain?: string;
};
export type ForgotPasswordModalDialogProps = {