diff --git a/client/packages/lowcoder-cli/client.d.ts b/client/packages/lowcoder-cli/client.d.ts index 2621660f5..98926bd29 100644 --- a/client/packages/lowcoder-cli/client.d.ts +++ b/client/packages/lowcoder-cli/client.d.ts @@ -34,6 +34,7 @@ declare var LOWCODER_NODE_SERVICE_URL: string; declare var LOWCODER_SHOW_BRAND: string; declare var LOWCODER_CUSTOM_LOGO: string; declare var LOWCODER_CUSTOM_LOGO_SQUARE: string; +declare var LOWCODER_CUSTOM_AUTH_WELCOME_TEXT: string; declare var REACT_APP_ENV: string; declare var REACT_APP_BUILD_ID: string; declare var REACT_APP_LOG_LEVEL: string; diff --git a/client/packages/lowcoder-dev-utils/buildVars.js b/client/packages/lowcoder-dev-utils/buildVars.js index 0ad460323..1f25eb122 100644 --- a/client/packages/lowcoder-dev-utils/buildVars.js +++ b/client/packages/lowcoder-dev-utils/buildVars.js @@ -35,6 +35,10 @@ export const buildVars = [ name: "LOWCODER_NODE_SERVICE_URL", defaultValue: "", }, + { + name: "LOWCODER_CUSTOM_AUTH_WELCOME_TEXT", + defaultValue: "", + }, { name: "REACT_APP_ENV", defaultValue: "production", diff --git a/client/packages/lowcoder/src/api/configApi.ts b/client/packages/lowcoder/src/api/configApi.ts index 8c4e31e95..42d315279 100644 --- a/client/packages/lowcoder/src/api/configApi.ts +++ b/client/packages/lowcoder/src/api/configApi.ts @@ -8,10 +8,14 @@ export interface ConfigResponse extends ApiResponse { } class ConfigApi extends Api { - static configURL = "/v1/configs"; + static configURL = "/configs"; - static fetchConfig(): AxiosPromise { - return Api.get(ConfigApi.configURL); + static fetchConfig(orgId?: string): AxiosPromise { + let authConfigURL = ConfigApi.configURL; + if(orgId?.length) { + authConfigURL += `?orgId?=${orgId}`; + } + return Api.get(authConfigURL); } } diff --git a/client/packages/lowcoder/src/api/inviteApi.ts b/client/packages/lowcoder/src/api/inviteApi.ts index 27b9944c1..f2a85caa1 100644 --- a/client/packages/lowcoder/src/api/inviteApi.ts +++ b/client/packages/lowcoder/src/api/inviteApi.ts @@ -14,10 +14,11 @@ export type InviteInfo = { inviteCode: string; createUserName: string; invitedOrganizationName: string; + invitedOrganizationId: string; }; class InviteApi extends Api { - static getInviteURL = "/v1/invitation"; + static getInviteURL = "/invitation"; static acceptInviteURL = (invitationId: string) => `/v1/invitation/${invitationId}/invite`; // generate invitation diff --git a/client/packages/lowcoder/src/api/userApi.ts b/client/packages/lowcoder/src/api/userApi.ts index 8cd666444..36df3685b 100644 --- a/client/packages/lowcoder/src/api/userApi.ts +++ b/client/packages/lowcoder/src/api/userApi.ts @@ -9,6 +9,7 @@ export interface CommonLoginParam { invitationId?: string; authId?: string; source?: string; + orgId?: string; } export interface CommonBindParam { @@ -17,8 +18,8 @@ export interface CommonBindParam { source?: string; } -interface ThirdPartyAuthRequest { - state: string; +export interface ThirdPartyAuthRequest { + state?: string; code: string; redirectUrl: string; } diff --git a/client/packages/lowcoder/src/app-env.d.ts b/client/packages/lowcoder/src/app-env.d.ts index 95d829c6f..f11d51d0e 100644 --- a/client/packages/lowcoder/src/app-env.d.ts +++ b/client/packages/lowcoder/src/app-env.d.ts @@ -37,6 +37,7 @@ declare var LOWCODER_NODE_SERVICE_URL: string; declare var LOWCODER_SHOW_BRAND: string; declare var LOWCODER_CUSTOM_LOGO: string; declare var LOWCODER_CUSTOM_LOGO_SQUARE: string; +declare var LOWCODER_CUSTOM_AUTH_WELCOME_TEXT: string; declare var REACT_APP_ENV: string; declare var REACT_APP_BUILD_ID: string; declare var REACT_APP_LOG_LEVEL: string; diff --git a/client/packages/lowcoder/src/app.tsx b/client/packages/lowcoder/src/app.tsx index 04c6c1bf8..12d30f54b 100644 --- a/client/packages/lowcoder/src/app.tsx +++ b/client/packages/lowcoder/src/app.tsx @@ -13,6 +13,8 @@ import { IMPORT_APP_FROM_TEMPLATE_URL, INVITE_LANDING_URL, isAuthUnRequired, + ORG_AUTH_LOGIN_URL, + ORG_AUTH_REGISTER_URL, QUERY_LIBRARY_URL, SETTING, TRASH_URL, @@ -70,10 +72,11 @@ const Wrapper = (props: { children: React.ReactNode }) => ( type AppIndexProps = { isFetchUserFinished: boolean; isFetchHomeFinished: boolean; - isFetchingConfig: boolean; + // isFetchingConfig: boolean; + currentOrgId?: string; orgDev: boolean; defaultHomePage: string | null | undefined; - fetchConfig: () => void; + fetchConfig: (orgId?: string) => void; getCurrentUser: () => void; fetchHome: () => void; favicon: string; @@ -83,16 +86,22 @@ type AppIndexProps = { class AppIndex extends React.Component { componentDidMount() { this.props.getCurrentUser(); - this.props.fetchConfig(); - if (history.location.pathname === BASE_URL) { + const { pathname } = history.location; + + this.props.fetchConfig(this.props.currentOrgId); + + if (pathname === BASE_URL) { this.props.fetchHome(); } } - componentDidUpdate() { + componentDidUpdate(prevProps: AppIndexProps) { if (history.location.pathname === BASE_URL) { this.props.fetchHome(); } + if(prevProps.currentOrgId !== this.props.currentOrgId) { + this.props.fetchConfig(this.props.currentOrgId); + } } render() { @@ -101,7 +110,7 @@ class AppIndex extends React.Component { // make sure all users in this app have checked login info if ( !this.props.isFetchUserFinished || - this.props.isFetchingConfig || + // this.props.isFetchingConfig || (pathname === BASE_URL && !this.props.isFetchHomeFinished) ) { const hideLoadingHeader = isTemplate || isAuthUnRequired(pathname); @@ -151,6 +160,8 @@ class AppIndex extends React.Component { component={ApplicationHome} /> + + @@ -174,8 +185,9 @@ class AppIndex extends React.Component { const mapStateToProps = (state: AppState) => ({ isFetchUserFinished: isFetchUserFinished(state), - isFetchingConfig: getSystemConfigFetching(state), + // isFetchingConfig: getSystemConfigFetching(state), orgDev: state.ui.users.user.orgDev, + currentOrgId: state.ui.users.user.currentOrgId, defaultHomePage: state.ui.application.homeOrg?.commonSettings.defaultHomePage, isFetchHomeFinished: state.ui.application.loadingStatus.fetchHomeDataFinished, favicon: getBrandingConfig(state)?.favicon @@ -188,7 +200,7 @@ const mapDispatchToProps = (dispatch: any) => ({ getCurrentUser: () => { dispatch(fetchUserAction()); }, - fetchConfig: () => dispatch(fetchConfigAction()), + fetchConfig: (orgId?: string) => dispatch(fetchConfigAction(orgId)), fetchHome: () => dispatch(fetchHomeData({})), }); diff --git a/client/packages/lowcoder/src/constants/authConstants.ts b/client/packages/lowcoder/src/constants/authConstants.ts index d7b35abe4..323615f16 100644 --- a/client/packages/lowcoder/src/constants/authConstants.ts +++ b/client/packages/lowcoder/src/constants/authConstants.ts @@ -3,6 +3,8 @@ import { AUTH_LOGIN_URL, AUTH_REGISTER_URL, OAUTH_REDIRECT, + ORG_AUTH_LOGIN_URL, + ORG_AUTH_REGISTER_URL, } from "constants/routesURL"; import { InviteInfo } from "api/inviteApi"; import Login, { ThirdPartyBindCard } from "pages/userAuth/login"; @@ -56,6 +58,7 @@ export type AuthSessionStoreParams = { afterLoginRedirect: string | null; sourceType: string; invitationId?: string; + invitedOrganizationId?: string; routeLink?: boolean; name: string; authId?: string; @@ -65,13 +68,15 @@ export type AuthSessionStoreParams = { * action after third party auth * bind & innerBind has different redirect action */ -export type ThirdPartyAuthGoal = "login" | "bind" | "innerBind"; +export type ThirdPartyAuthGoal = "register" | "login" | "bind" | "innerBind"; export const AuthRoutes: Array<{ path: string; component: React.ComponentType }> = [ { path: AUTH_LOGIN_URL, component: Login }, { path: AUTH_BIND_URL, component: ThirdPartyBindCard }, { path: AUTH_REGISTER_URL, component: UserRegister }, { path: OAUTH_REDIRECT, component: AuthRedirect }, + { path: ORG_AUTH_LOGIN_URL, component: Login }, + { path: ORG_AUTH_REGISTER_URL, component: UserRegister }, ]; export type ServerAuthType = "GOOGLE" | "GITHUB" | "FORM"; diff --git a/client/packages/lowcoder/src/constants/routesURL.ts b/client/packages/lowcoder/src/constants/routesURL.ts index b9e164bbf..36772a197 100644 --- a/client/packages/lowcoder/src/constants/routesURL.ts +++ b/client/packages/lowcoder/src/constants/routesURL.ts @@ -41,6 +41,8 @@ export const CAS_AUTH_REDIRECT = `${USER_AUTH_URL}/cas/redirect`; export const LDAP_AUTH_LOGIN_URL = `${USER_AUTH_URL}/ldap/login`; export const USER_INFO_COMPLETION = `${USER_AUTH_URL}/completion`; export const INVITE_LANDING_URL = "/invite/:invitationId"; +export const ORG_AUTH_LOGIN_URL = `/org/:orgId/auth/login`; +export const ORG_AUTH_REGISTER_URL = `/org/:orgId/auth/register`; export const APPLICATION_VIEW_URL = (appId: string, viewMode: AppViewMode) => `${ALL_APPLICATIONS_URL}/${appId}/${viewMode}`; @@ -49,6 +51,8 @@ export const isAuthUnRequired = (pathname: string): boolean => { return ( pathname.startsWith("/invite/") || pathname.startsWith(USER_AUTH_URL) || + pathname.endsWith('/auth/login') || + pathname.endsWith('/auth/register') || pathname.startsWith(COMPONENT_DOC_URL) ); }; diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index ddebe2166..5f89000a3 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -1960,6 +1960,7 @@ export const en = { resetSuccess: "Reset succeeded", resetSuccessDesc: "Password reset succeeded. The new password is: {password}", copyPassword: "Copy password", + poweredByLowcoder: "Powered by Lowcoder.cloud" }, preLoad: { jsLibraryHelpText: diff --git a/client/packages/lowcoder/src/i18n/locales/zh.ts b/client/packages/lowcoder/src/i18n/locales/zh.ts index 753c456c6..8cd96a946 100644 --- a/client/packages/lowcoder/src/i18n/locales/zh.ts +++ b/client/packages/lowcoder/src/i18n/locales/zh.ts @@ -1934,6 +1934,7 @@ userAuth: { resetSuccess: "重置成功", resetSuccessDesc: "密码重置成功.新密码为:{password}", copyPassword: "复制密码", + poweredByLowcoder: "供电 Lowcoder.cloud" }, preLoad: { jsLibraryHelpText: "通过URL链接向当前应用程序添加JavaScript库.lodash、day.js、uuid、numbro内置于系统中,可立即使用.JavaScript库在应用程序初始化之前加载,这可能会影响应用程序的性能.", diff --git a/client/packages/lowcoder/src/pages/common/inviteLanding.tsx b/client/packages/lowcoder/src/pages/common/inviteLanding.tsx index abd84231b..cce825f18 100644 --- a/client/packages/lowcoder/src/pages/common/inviteLanding.tsx +++ b/client/packages/lowcoder/src/pages/common/inviteLanding.tsx @@ -8,15 +8,17 @@ import { RouteComponentProps } from "react-router-dom"; import { AppState } from "redux/reducers"; import history from "util/history"; import { isFetchUserFinished } from "redux/selectors/usersSelectors"; +import { fetchConfigAction } from "redux/reduxActions/configActions"; import { trans } from "i18n"; import { messageInstance } from "lowcoder-design"; type InviteLandingProp = RouteComponentProps<{ invitationId: string }, StaticContext, any> & { invitationId: string; + fetchConfig: (orgId?: string) => void; }; function InviteLanding(props: InviteLandingProp) { - const { invitationId } = props; + const { invitationId, fetchConfig } = props; const fetchUserFinished = useSelector(isFetchUserFinished); useEffect(() => { if (!fetchUserFinished) { @@ -27,6 +29,7 @@ function InviteLanding(props: InviteLandingProp) { history.push(BASE_URL); return; } + let orgId:string | undefined = undefined; // accept the invitation InviteApi.acceptInvite({ invitationId }) .then((resp) => { @@ -39,6 +42,7 @@ function InviteLanding(props: InviteLandingProp) { resp?.status === API_STATUS_CODES.REQUEST_NOT_AUTHORISED ) { const inviteInfo = resp.data.data; + orgId = inviteInfo.invitedOrganizationId; const inviteState = inviteInfo ? { ...inviteInfo, invitationId } : { invitationId }; history.push({ pathname: AUTH_LOGIN_URL, @@ -53,8 +57,10 @@ function InviteLanding(props: InviteLandingProp) { .catch((errorResp) => { messageInstance.error(errorResp.message); history.push(BASE_URL); + }).finally(() => { + // fetchConfig(orgId); }); - }, [fetchUserFinished, invitationId]); + }, [fetchUserFinished, invitationId, fetchConfig]); return null; } @@ -64,4 +70,9 @@ const mapStateToProps = (state: AppState, props: InviteLandingProp) => { }; }; -export default connect(mapStateToProps)(InviteLanding); +const mapDispatchToProps = (dispatch: any) => ({ + fetchConfig: (orgId?: string) => dispatch(fetchConfigAction(orgId)), +}); + + +export default connect(mapStateToProps, mapDispatchToProps)(InviteLanding); diff --git a/client/packages/lowcoder/src/pages/common/profileDropdown.tsx b/client/packages/lowcoder/src/pages/common/profileDropdown.tsx index ab9711a5b..adc001b76 100644 --- a/client/packages/lowcoder/src/pages/common/profileDropdown.tsx +++ b/client/packages/lowcoder/src/pages/common/profileDropdown.tsx @@ -26,6 +26,7 @@ import { trans } from "i18n"; import { showSwitchOrg } from "@lowcoder-ee/pages/common/customerService"; import { checkIsMobile } from "util/commonUtils"; import { selectSystemConfig } from "redux/selectors/configSelectors"; +import { ItemType } from "antd/es/menu/hooks/useItems"; const ProfileWrapper = styled.div` display: flex; @@ -159,13 +160,10 @@ export default function ProfileDropdown(props: DropDownProps) { } }; - const menu = ( - } - > - + let profileDropdownMenuItems:ItemType[] = [ + { + key: 'profile', + label: ( @@ -187,36 +185,61 @@ export default function ProfileDropdown(props: DropDownProps) { {OrgRoleInfo[currentOrgRoleId].name} )} - - {orgs && orgs.length > 0 && showSwitchOrg(props.user, sysConfig) && ( - - - {trans("profile.joinedOrg")} - - {orgs.map((org: Org) => { - const MenuItem = (currentOrgId === org.id ? SelectDropMenuItem : Menu.Item) as React.ElementType; - return ( - }> - {org.name} - - ); - })} - {!checkIsMobile(window.innerWidth) && ( - <> - - }> - {trans("profile.createOrg")} - - - )} - - )} - {trans("profile.logout")} - + ), + }, + { + key: 'logout', + label: trans("profile.logout"), + } + ] + + if(orgs && orgs.length > 0 && showSwitchOrg(props.user, sysConfig)) { + const switchOrgSubMenu = orgs.map((org: Org) => ({ + key: org.id, + icon: currentOrgId === org.id && , + label: org.name + })) + + let addWorkSpace:ItemType[] = []; + if(!checkIsMobile(window.innerWidth)) { + addWorkSpace = [ + { type: 'divider'}, + { + key: 'newOrganization', + icon: , + label: trans("profile.createOrg") + } + ] + } + + const switchOrgMenu = { + key: 'switchOrg', + label: trans("profile.switchOrg"), + popupOffset: [4, -12], + children: [ + { + key: 'joinedOrg', + label: ( + + {trans("profile.joinedOrg")} + + ), + disabled: true, + }, + ...switchOrgSubMenu, + ...addWorkSpace, + ] + } + profileDropdownMenuItems.splice(1, 0, switchOrgMenu); + } + + const menu = ( + } + items={profileDropdownMenuItems} + /> ); return ( <> diff --git a/client/packages/lowcoder/src/pages/userAuth/authComponents.tsx b/client/packages/lowcoder/src/pages/userAuth/authComponents.tsx index 5dc599391..81847b6b3 100644 --- a/client/packages/lowcoder/src/pages/userAuth/authComponents.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/authComponents.tsx @@ -32,7 +32,7 @@ const AuthCard = styled.div` } `; -const AuthCardTitle = styled.div<{ type?: string }>` +const AuthCardHeading = styled.div<{ type?: string }>` font-weight: 600; font-size: 28px; color: #222222; @@ -51,6 +51,12 @@ const AuthCardTitle = styled.div<{ type?: string }>` } `; +const AuthCardSubHeading = styled.div` + font-size: 14px; + color: #222222; + line-height: 14px; +` + const AuthBottom = styled.div` display: flex; align-items: center; @@ -116,10 +122,24 @@ const StyledConfirmButton = styled(TacoButton)` transition: unset; `; -export const AuthContainer = (props: { children: any; title?: string; type?: string }) => { +export const AuthContainer = (props: { + children: any; + heading?: string; + subHeading?: string; + type?: string +}) => { return ( - {props.title || ""} + + {props.heading || ""} + + { props.subHeading && ( + + {props.subHeading} + + )} {props.children} ); @@ -158,6 +178,7 @@ export const ConfirmButton = (props: { const TermsAndPrivacyContent = styled.div` display: flex; align-items: center; + margin-top: 16px; font-size: 13px; color: #333333; @@ -199,10 +220,23 @@ export const LoginLogoStyle = styled.img` margin-right: 8px; width: 32px; height: 32px; + position: absolute; + left: 6px; +`; + +export const LoginLabelStyle = styled.p` + font-size: 16px; + color: #333333; + line-height: 16px; + margin: 0px; `; -export const StyledLoginButton = styled.button` - padding: 0; +export const StyledLoginButton = styled(TacoButton)` + position: relative; + height: 48px; + border: 1px solid lightgray !important; + border-radius: 8px; + padding: 8px; white-space: nowrap; word-break: keep-all; outline: 0; diff --git a/client/packages/lowcoder/src/pages/userAuth/authUtils.ts b/client/packages/lowcoder/src/pages/userAuth/authUtils.ts index 6cec13bd2..cff083fdf 100644 --- a/client/packages/lowcoder/src/pages/userAuth/authUtils.ts +++ b/client/packages/lowcoder/src/pages/userAuth/authUtils.ts @@ -23,7 +23,7 @@ import { } from "constants/authConstants"; export const AuthContext = createContext<{ - systemConfig: SystemConfig; + systemConfig?: SystemConfig; inviteInfo?: AuthInviteInfo; thirdPartyAuthError?: boolean; }>(undefined as any); @@ -125,7 +125,8 @@ export const geneAuthStateAndSaveParam = ( authGoal: ThirdPartyAuthGoal, config: ThirdPartyConfigType, afterLoginRedirect: string | null, - invitationId?: string + invitationId?: string, + invitedOrganizationId?: string, ) => { const state = Math.floor(Math.random() * 0xffffffff).toString(16); const params: AuthSessionStoreParams = { @@ -135,6 +136,7 @@ export const geneAuthStateAndSaveParam = ( afterLoginRedirect: afterLoginRedirect || null, sourceType: config.sourceType, invitationId: invitationId, + invitedOrganizationId: invitedOrganizationId, routeLink: config.routeLink, name: config.name, authId: config.id, diff --git a/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx b/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx index ffd9dd1b9..186c62c7c 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx @@ -15,8 +15,8 @@ import { UserConnectionSource } from "@lowcoder-ee/constants/userConstants"; import { trans } from "i18n"; import { AuthContext, useAuthSubmit } from "pages/userAuth/authUtils"; import { ThirdPartyAuth } from "pages/userAuth/thirdParty/thirdPartyAuth"; -import { AUTH_REGISTER_URL } from "constants/routesURL"; -import { useLocation } from "react-router-dom"; +import { AUTH_REGISTER_URL, ORG_AUTH_REGISTER_URL } from "constants/routesURL"; +import { useLocation, useParams } from "react-router-dom"; const AccountLoginWrapper = styled(FormWrapperMobile)` display: flex; @@ -24,7 +24,11 @@ const AccountLoginWrapper = styled(FormWrapperMobile)` margin-bottom: 106px; `; -export default function FormLogin() { +type FormLoginProps = { + organizationId?: string; +} + +export default function FormLogin(props: FormLoginProps) { const [account, setAccount] = useState(""); const [password, setPassword] = useState(""); const redirectUrl = useRedirectUrl(); @@ -32,6 +36,7 @@ export default function FormLogin() { const invitationId = inviteInfo?.invitationId; const authId = systemConfig?.form.id; const location = useLocation(); + const orgId = useParams().orgId; const { onSubmit, loading } = useAuthSubmit( () => @@ -69,14 +74,24 @@ export default function FormLogin() { {trans("userAuth.login")} + + {props.organizationId && ( + + )} - - {systemConfig.form.enableRegister && ( - - {trans("userAuth.register")} - - )} + + {trans("userAuth.register")} + ); diff --git a/client/packages/lowcoder/src/pages/userAuth/index.tsx b/client/packages/lowcoder/src/pages/userAuth/index.tsx index 1e0fb898f..6d5e322ae 100644 --- a/client/packages/lowcoder/src/pages/userAuth/index.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/index.tsx @@ -1,19 +1,39 @@ import { AUTH_LOGIN_URL, USER_AUTH_URL } from "constants/routesURL"; -import { Redirect, Route, Switch, useLocation } from "react-router-dom"; -import React from "react"; -import { useSelector } from "react-redux"; +import { Redirect, Route, Switch, useLocation, useParams } from "react-router-dom"; +import React, { useEffect, useMemo } from "react"; +import { useSelector, useDispatch } from "react-redux"; import { selectSystemConfig } from "redux/selectors/configSelectors"; import { AuthContext } from "pages/userAuth/authUtils"; import { AuthRoutes } from "@lowcoder-ee/constants/authConstants"; import { AuthLocationState } from "constants/authConstants"; import { ProductLoading } from "components/ProductLoading"; +import { fetchConfigAction } from "redux/reduxActions/configActions"; +import _ from "lodash"; export default function UserAuth() { + const dispatch = useDispatch(); const location = useLocation(); - const systemConfig = useSelector(selectSystemConfig); - if (!systemConfig) { + const systemConfig = useSelector(selectSystemConfig, _.isEqual); + const orgId = useParams().orgId; + const inviteInfo = location.state?.inviteInfo; + + const organizationId = useMemo(() => { + if(inviteInfo?.invitedOrganizationId) { + return inviteInfo?.invitedOrganizationId; + } + return orgId; + }, [ orgId, inviteInfo ]) + + useEffect(() => { + if(organizationId) { + dispatch(fetchConfigAction(organizationId)); + } + }, [organizationId, dispatch]) + + if (organizationId && !systemConfig) { return ; } + return ( {AuthRoutes.map((route) => ( - + ))} diff --git a/client/packages/lowcoder/src/pages/userAuth/login.tsx b/client/packages/lowcoder/src/pages/userAuth/login.tsx index a847e93cf..6478c1c31 100644 --- a/client/packages/lowcoder/src/pages/userAuth/login.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/login.tsx @@ -1,11 +1,11 @@ -import { useLocation } from "react-router-dom"; +import { useLocation, useParams } from "react-router-dom"; import { AuthSearchParams } from "constants/authConstants"; import { CommonTextLabel } from "components/Label"; import { trans } from "i18n"; import { ThirdPartyAuth } from "pages/userAuth/thirdParty/thirdPartyAuth"; import FormLogin from "@lowcoder-ee/pages/userAuth/formLogin"; import { AuthContainer } from "pages/userAuth/authComponents"; -import React, { useContext } from "react"; +import React, { useContext, useMemo } from "react"; import { AuthContext, getLoginTitle } from "pages/userAuth/authUtils"; import styled from "styled-components"; import { requiresUnAuth } from "pages/userAuth/authHOC"; @@ -64,13 +64,13 @@ const thirdPartyLoginLabel = (name: string) => trans("userAuth.signInLabel", { n export const ThirdPartyBindCard = () => { const { systemConfig } = useContext(AuthContext); return ( - + ().orgId; + + const loginType = systemConfig?.authConfigs.find( (config) => config.sourceType === queryParams.get(AuthSearchParams.loginType) )?.sourceType; let autoJumpSource: string | undefined; @@ -96,6 +98,13 @@ function Login() { autoJumpSource = systemConfig.authConfigs[0].sourceType; } + const organizationId = useMemo(() => { + if(inviteInfo?.invitedOrganizationId) { + return inviteInfo?.invitedOrganizationId; + } + return orgId; + }, [ inviteInfo, orgId ]) + const thirdPartyLoginView = ( {!autoJumpSource && ( @@ -116,15 +125,24 @@ function Login() { if (loginType) { loginCardView = thirdPartyLoginView; // Specify the login type with query param - } else if (systemConfig.form.enableLogin) { - loginCardView = ; + } else if (systemConfig?.form.enableLogin) { + loginCardView = ; } else { loginCardView = thirdPartyLoginView; } + const loginHeading = organizationId && LOWCODER_CUSTOM_AUTH_WELCOME_TEXT !== "" + ? LOWCODER_CUSTOM_AUTH_WELCOME_TEXT + : getLoginTitle(inviteInfo?.createUserName, systemConfig?.branding?.brandName) + + const loginSubHeading = organizationId && LOWCODER_CUSTOM_AUTH_WELCOME_TEXT !== "" + ? trans("userAuth.poweredByLowcoder") + : '' + return ( {loginCardView} diff --git a/client/packages/lowcoder/src/pages/userAuth/register.tsx b/client/packages/lowcoder/src/pages/userAuth/register.tsx index b1b82d0f9..151d00a20 100644 --- a/client/packages/lowcoder/src/pages/userAuth/register.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/register.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from "react"; +import React, { useContext, useState, useMemo } from "react"; import { AuthContainer, ConfirmButton, @@ -8,7 +8,7 @@ import { TermsAndPrivacyInfo, } from "pages/userAuth/authComponents"; import { FormInput, PasswordInput } from "lowcoder-design"; -import { AUTH_LOGIN_URL } from "constants/routesURL"; +import { AUTH_LOGIN_URL, ORG_AUTH_LOGIN_URL } from "constants/routesURL"; import UserApi from "api/userApi"; import { useRedirectUrl } from "util/hooks"; import { checkEmailValid } from "util/stringUtils"; @@ -18,6 +18,8 @@ import { useLocation } from "react-router-dom"; import { UserConnectionSource } from "@lowcoder-ee/constants/userConstants"; import { trans } from "i18n"; import { AuthContext, checkPassWithMsg, useAuthSubmit } from "pages/userAuth/authUtils"; +import { ThirdPartyAuth } from "pages/userAuth/thirdParty/thirdPartyAuth"; +import { useParams } from "react-router-dom"; const StyledFormInput = styled(FormInput)` margin-bottom: 16px; @@ -30,17 +32,7 @@ const StyledPasswordInput = styled(PasswordInput)` const RegisterContent = styled(FormWrapperMobile)` display: flex; flex-direction: column; - - button { - margin: 20px 0 16px 0; - } -`; - -const TermsAndPrivacyInfoWrapper = styled.div` - margin-bottom: 80px; - @media screen and (max-width: 640px) { - margin: 10px 0 64px 0; - } + margin-bottom: 106px; `; function UserRegister() { @@ -50,26 +42,49 @@ function UserRegister() { const redirectUrl = useRedirectUrl(); const location = useLocation(); const { systemConfig, inviteInfo } = useContext(AuthContext); - const authId = systemConfig.form.id; + const invitationId = inviteInfo?.invitationId; + // const invitedOrganizationId = inviteInfo?.invitedOrganizationId; + const orgId = useParams().orgId; + const organizationId = useMemo(() => { + if(inviteInfo?.invitedOrganizationId) { + return inviteInfo?.invitedOrganizationId; + } + return orgId; + }, [ inviteInfo, orgId ]) + + const authId = systemConfig?.form.id; const { loading, onSubmit } = useAuthSubmit( () => UserApi.formLogin({ register: true, loginId: account, password: password, - invitationId: inviteInfo?.invitationId, + invitationId, source: UserConnectionSource.email, authId, }), false, redirectUrl ); - if (!systemConfig || !systemConfig.form.enableRegister) { + + if (!systemConfig || !systemConfig?.form.enableRegister) { return null; } + const registerHeading = organizationId && LOWCODER_CUSTOM_AUTH_WELCOME_TEXT !== "" + ? LOWCODER_CUSTOM_AUTH_WELCOME_TEXT + : trans("userAuth.register") + + const registerSubHeading = organizationId && LOWCODER_CUSTOM_AUTH_WELCOME_TEXT !== "" + ? trans("userAuth.poweredByLowcoder") + : '' + return ( - + {trans("userAuth.registerByEmail")} {trans("userAuth.register")} - - setSubmitBtnDisable(!e.target.checked)} /> - - - {trans("userAuth.userLogin")} - + setSubmitBtnDisable(!e.target.checked)} /> + {organizationId && ( + + )} + + {trans("userAuth.userLogin")} + ); } diff --git a/client/packages/lowcoder/src/pages/userAuth/thirdParty/authRedirect.tsx b/client/packages/lowcoder/src/pages/userAuth/thirdParty/authRedirect.tsx index 87d1a8fbd..686532fe2 100644 --- a/client/packages/lowcoder/src/pages/userAuth/thirdParty/authRedirect.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/thirdParty/authRedirect.tsx @@ -2,7 +2,7 @@ import { useLocation } from "react-router-dom"; import { AuthSessionStoreParams } from "constants/authConstants"; import { messageInstance } from "lowcoder-design"; -import { AUTH_LOGIN_URL, BASE_URL } from "constants/routesURL"; +import { AUTH_LOGIN_URL, AUTH_REGISTER_URL, BASE_URL } from "constants/routesURL"; import history from "util/history"; import PageSkeleton from "components/PageSkeleton"; import { trans } from "i18n"; @@ -35,7 +35,13 @@ function validateParam(authParams: AuthSessionStoreParams, urlParam: AuthRedirec return true; } else { messageInstance.error(trans("userAuth.invalidThirdPartyParam")); - history.push(authParams.authGoal === "login" ? AUTH_LOGIN_URL : BASE_URL, { + let redirectUrl = BASE_URL; + if(authParams.authGoal === "login") { + redirectUrl = AUTH_LOGIN_URL; + } else if(authParams.authGoal === "register") { + redirectUrl = AUTH_REGISTER_URL; + } + history.push(redirectUrl, { thirdPartyAuthError: true, }); return false; diff --git a/client/packages/lowcoder/src/pages/userAuth/thirdParty/authenticator/abstractAuthenticator.ts b/client/packages/lowcoder/src/pages/userAuth/thirdParty/authenticator/abstractAuthenticator.ts index 784bbee0b..6d1ecea9c 100644 --- a/client/packages/lowcoder/src/pages/userAuth/thirdParty/authenticator/abstractAuthenticator.ts +++ b/client/packages/lowcoder/src/pages/userAuth/thirdParty/authenticator/abstractAuthenticator.ts @@ -28,7 +28,9 @@ export abstract class AbstractAuthenticator { doAuth() { const { authParams } = this; - authParams.authGoal === "login" ? this.doLogin() : this.doBind(); + (authParams.authGoal === "login" || authParams.authGoal === "register") + ? this.doLogin() + : this.doBind(); } protected doLogin() { diff --git a/client/packages/lowcoder/src/pages/userAuth/thirdParty/authenticator/oAuthAuthenticator.ts b/client/packages/lowcoder/src/pages/userAuth/thirdParty/authenticator/oAuthAuthenticator.ts index 7f0246299..9557a27c6 100644 --- a/client/packages/lowcoder/src/pages/userAuth/thirdParty/authenticator/oAuthAuthenticator.ts +++ b/client/packages/lowcoder/src/pages/userAuth/thirdParty/authenticator/oAuthAuthenticator.ts @@ -1,6 +1,6 @@ import { AbstractAuthenticator } from "./abstractAuthenticator"; import { AxiosPromise } from "axios"; -import UserApi from "api/userApi"; +import UserApi, { CommonLoginParam, ThirdPartyAuthRequest } from "api/userApi"; import { ApiResponse } from "api/apiResponses"; export class OAuthAuthenticator extends AbstractAuthenticator { @@ -19,13 +19,16 @@ export class OAuthAuthenticator extends AbstractAuthenticator { login(): AxiosPromise { const { urlParam, authParams, redirectUrl } = this; - return UserApi.thirdPartyLogin({ + const params: ThirdPartyAuthRequest & CommonLoginParam = { state: urlParam.state!, code: urlParam.code!, source: authParams.sourceType, authId: authParams.authId, redirectUrl: redirectUrl, - ...(authParams.invitationId && { invitationId: authParams.invitationId }), - }); + } + if(authParams.invitedOrganizationId) { + params.orgId = authParams.invitedOrganizationId; + } + return UserApi.thirdPartyLogin(params); } } diff --git a/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx b/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx index 6a3eedb75..14d7fc189 100644 --- a/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx @@ -7,18 +7,30 @@ import { import { CommonGrayLabel, WhiteLoading } from "lowcoder-design"; import { useLocation } from "react-router-dom"; import history from "util/history"; -import { LoginLogoStyle, StyledLoginButton } from "pages/userAuth/authComponents"; +import { LoginLogoStyle, LoginLabelStyle, StyledLoginButton } from "pages/userAuth/authComponents"; import { useSelector } from "react-redux"; import { selectSystemConfig } from "redux/selectors/configSelectors"; import React from "react"; import { messageInstance } from "lowcoder-design"; - +import styled from "styled-components"; import { trans } from "i18n"; import { geneAuthStateAndSaveParam, getAuthUrl, getRedirectUrl } from "pages/userAuth/authUtils"; +import { Divider } from "antd"; + +const ThirdPartyLoginButtonWrapper = styled.div` + button{ + width: 100%; + + &:not(:last-child) { + margin-bottom: 16px; + } + } +`; function ThirdPartyLoginButton(props: { config: ThirdPartyConfigType; invitationId?: string; + invitedOrganizationId?: string; autoJump?: boolean; authGoal: ThirdPartyAuthGoal; label: string; @@ -33,7 +45,8 @@ function ThirdPartyLoginButton(props: { props.authGoal, config, loginRedirectUrl, - props.invitationId + props.invitationId, + props.invitedOrganizationId, ); if (config.authType === "LDAP") { history.push({ @@ -70,16 +83,24 @@ function ThirdPartyLoginButton(props: { onLoginClick(); return ; } + + const buttonLabel = props.authGoal === 'register' + ? `Sign up with ${label}` + : `Sign in with ${label}`; + return ( - + - {label} + + { buttonLabel } + ); } export function ThirdPartyAuth(props: { invitationId?: string; + invitedOrganizationId?: string; autoJumpSource?: string; authGoal: ThirdPartyAuthGoal; labelFormatter?: (name: string) => string; @@ -101,9 +122,15 @@ export function ThirdPartyAuth(props: { key={config.name} config={config} invitationId={props.invitationId} + invitedOrganizationId={props.invitedOrganizationId} label={props.labelFormatter ? props.labelFormatter(config.name) : config.name} /> ); }); - return <>{socialLoginButtons}; + return ( + + { Boolean(socialLoginButtons.length) && } + {socialLoginButtons} + + ); } diff --git a/client/packages/lowcoder/src/redux/reduxActions/configActions.ts b/client/packages/lowcoder/src/redux/reduxActions/configActions.ts index 2b8e7f3cc..78ae83fbf 100644 --- a/client/packages/lowcoder/src/redux/reduxActions/configActions.ts +++ b/client/packages/lowcoder/src/redux/reduxActions/configActions.ts @@ -1,9 +1,16 @@ import { ReduxActionTypes } from "constants/reduxActionConstants"; import { ExternalEditorContextState } from "util/context/ExternalEditorContext"; -export const fetchConfigAction = () => { +export type FetchConfigActionPayload = { + orgId?: string; +}; + +export const fetchConfigAction = (orgId?: string) => { return { type: ReduxActionTypes.FETCH_SYS_CONFIG_INIT, + payload: { + orgId, + } }; }; diff --git a/client/packages/lowcoder/src/redux/sagas/configSagas.ts b/client/packages/lowcoder/src/redux/sagas/configSagas.ts index 8faf9c9e6..8570c04fb 100644 --- a/client/packages/lowcoder/src/redux/sagas/configSagas.ts +++ b/client/packages/lowcoder/src/redux/sagas/configSagas.ts @@ -1,14 +1,22 @@ import { all, call, put, takeLatest } from "redux-saga/effects"; -import { ReduxActionErrorTypes, ReduxActionTypes } from "constants/reduxActionConstants"; +import { + ReduxActionErrorTypes, + ReduxActionTypes, + ReduxAction, +} from "constants/reduxActionConstants"; import { AxiosResponse } from "axios"; import { validateResponse } from "api/apiUtils"; import log from "loglevel"; import ConfigApi, { ConfigResponse } from "api/configApi"; import { transToSystemConfig } from "@lowcoder-ee/constants/configConstants"; +import { FetchConfigActionPayload } from "redux/reduxActions/configActions"; -export function* fetchConfigSaga() { +export function* fetchConfigSaga(action: ReduxAction) { try { - const response: AxiosResponse = yield call(ConfigApi.fetchConfig); + const response: AxiosResponse = yield call( + ConfigApi.fetchConfig, + action.payload.orgId, + ); const isValidResponse: boolean = validateResponse(response); if (isValidResponse) { yield put({