From b64013a6c345c22ec8575218cabd33258d622c0d Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 23 Oct 2024 21:02:59 +0500 Subject: [PATCH 01/12] implemented new auth flow --- .../src/components/tacoInput.tsx | 4 +- client/packages/lowcoder/src/api/orgApi.ts | 4 + .../lowcoder/src/pages/userAuth/formLogin.tsx | 4 +- .../src/pages/userAuth/formLoginSteps.tsx | 241 ++++++++++++++++++ .../lowcoder/src/pages/userAuth/login.tsx | 8 +- 5 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx diff --git a/client/packages/lowcoder-design/src/components/tacoInput.tsx b/client/packages/lowcoder-design/src/components/tacoInput.tsx index 1258400112..f0208e52fc 100644 --- a/client/packages/lowcoder-design/src/components/tacoInput.tsx +++ b/client/packages/lowcoder-design/src/components/tacoInput.tsx @@ -335,8 +335,9 @@ const FormInput = (props: { className?: string; inputRef?: Ref; msg?: string; + defaultValue?: string; }) => { - const { mustFill, checkRule, label, placeholder, onChange, formName, className, inputRef } = + const { mustFill, checkRule, label, placeholder, onChange, formName, className, inputRef, defaultValue } = props; const [valueValid, setValueValid] = useState(true); return ( @@ -350,6 +351,7 @@ const FormInput = (props: { ref={inputRef} name={formName} placeholder={placeholder} + defaultValue={defaultValue} onChange={(e) => { let valid = true; if (checkRule) { diff --git a/client/packages/lowcoder/src/api/orgApi.ts b/client/packages/lowcoder/src/api/orgApi.ts index c3d66e5571..6e7c532e4d 100644 --- a/client/packages/lowcoder/src/api/orgApi.ts +++ b/client/packages/lowcoder/src/api/orgApi.ts @@ -52,6 +52,7 @@ export class OrgApi extends Api { static deleteOrgURL = (orgId: string) => `/organizations/${orgId}`; static updateOrgURL = (orgId: string) => `/organizations/${orgId}/update`; static fetchUsage = (orgId: string) => `/organizations/${orgId}/api-usage`; + static fetchOrgsByEmailURL = (email: string) => `organizations/byuser/${email}`; static createGroup(request: { name: string }): AxiosPromise> { return Api.post(OrgApi.createGroupURL, request); @@ -141,6 +142,9 @@ export class OrgApi extends Api { return Api.get(OrgApi.fetchUsage(orgId), { lastMonthOnly: true }); } + static fetchOrgsByEmail(email: string): AxiosPromise { + return Api.get(OrgApi.fetchOrgsByEmailURL(email)); + } } export default OrgApi; diff --git a/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx b/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx index e43e3b94f1..2d0ab4f420 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx @@ -20,7 +20,8 @@ import { Link, useLocation, useParams } from "react-router-dom"; import { Divider } from "antd"; import Flex from "antd/es/flex"; -const AccountLoginWrapper = styled(FormWrapperMobile)` +export const AccountLoginWrapper = styled(FormWrapperMobile)` + position: relative; display: flex; flex-direction: column; margin-bottom: 0px; @@ -62,7 +63,6 @@ export default function FormLogin(props: FormLoginProps) { return ( <> - {/* {trans("userAuth.login")} */} ` + display: flex; + justify-content: center; + flex-direction: column; + min-height: 56px; + margin-bottom: -1px; + padding: 0 24px; + color: rgba(0, 0, 0, 0.88); + font-size: 16px; + background: transparent; + border: 1px solid #f0f0f0; + border-radius: 8px; + cursor: pointer; + margin-bottom: 16px; + // box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02); + ${props => props.$selected && `background: #e6f4ff;`} + + &:hover { + box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.16), 0 3px 6px 0 rgba(0, 0, 0, 0.12), 0 5px 12px 4px rgba(0, 0, 0, 0.09); + } +`; + +type OrgItem = { + orgId: string; + orgName: string; +} + +enum CurrentStepEnum { + EMAIL = "EMAIL", + WORKSPACES = "WORKSPACES", + AUTH_PROVIDERS = "AUTH_PROVIDERS", +} + +const StepHeader = (props : { + title: string, +}) => ( + +

{props.title}

+
+) + +const StepBackButton = (props : { + onClick: () => void, +}) => ( + +) +export default function FormLoginSteps() { + const [account, setAccount] = useState(""); + const [password, setPassword] = useState(""); + const redirectUrl = useRedirectUrl(); + const { systemConfig, inviteInfo, fetchUserAfterAuthSuccess } = useContext(AuthContext); + const invitationId = inviteInfo?.invitationId; + const authId = systemConfig?.form.id; + const location = useLocation(); + const [orgLoading, setOrgLoading] = useState(false); + const [orgList, setOrgList] = useState([]); + const [currentStep, setCurrentStep] = useState(CurrentStepEnum.EMAIL); + const [organizationId, setOrganizationId] = useState(); + + const { onSubmit, loading } = useAuthSubmit( + () => + UserApi.formLogin({ + register: false, + loginId: account, + password: password, + invitationId: invitationId, + source: UserConnectionSource.email, + orgId: organizationId, + authId, + }), + false, + redirectUrl, + fetchUserAfterAuthSuccess, + ); + + const fetchOrgsByEmail = () => { + setOrgLoading(true); + OrgApi.fetchOrgsByEmail(account) + .then((resp) => { + if (validateResponse(resp)) { + console.log(resp.data.data); + setOrgList(resp.data.data); + if (resp.data.data.length === 1) { + setOrganizationId(resp.data.data[0].orgId); + setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS); + return; + } + setCurrentStep(CurrentStepEnum.WORKSPACES); + } else { + throw new Error('Error while fetching organizations'); + } + }) + .catch((e) => { + messageInstance.error(e.message); + }) + .finally(() => { + setOrgLoading(false); + }); + } + + if(currentStep === CurrentStepEnum.EMAIL) { + return ( + <> + + + setAccount(valid ? value : "")} + placeholder={trans("userAuth.inputEmail")} + checkRule={{ + check: (value) => checkPhoneValid(value) || checkEmailValid(value), + errorMsg: trans("userAuth.inputValidEmail"), + }} + /> + + {/* {trans("userAuth.login")} */} + Continue + + + + + + {trans("userAuth.register")} + + + + ) + } + + if (currentStep === CurrentStepEnum.WORKSPACES) { + return ( + <> + + setCurrentStep(CurrentStepEnum.EMAIL)} /> + + {orgList.map(org => ( + { + setOrganizationId(org.orgId); + setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS); + }} + > + {org.orgName} + + ))} + + + ) + } + + return ( + <> + + setCurrentStep(CurrentStepEnum.WORKSPACES)} /> + + setPassword(value)} + valueCheck={() => [true, ""]} + /> + + + {`${trans("userAuth.forgotPassword")}?`} + + + + {trans("userAuth.login")} + + {organizationId && ( + + )} + + + + + {trans("userAuth.register")} + + + + ); +} diff --git a/client/packages/lowcoder/src/pages/userAuth/login.tsx b/client/packages/lowcoder/src/pages/userAuth/login.tsx index 4b2d895247..1f9b0f3746 100644 --- a/client/packages/lowcoder/src/pages/userAuth/login.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/login.tsx @@ -9,6 +9,7 @@ import React, { useContext, useMemo } from "react"; import { AuthContext, getLoginTitle } from "pages/userAuth/authUtils"; import styled from "styled-components"; import { requiresUnAuth } from "pages/userAuth/authHOC"; +import FormLoginSteps from "./formLoginSteps"; const ThirdAuthWrapper = styled.div` display: flex; @@ -87,7 +88,7 @@ function Login() { const invitationId = inviteInfo?.invitationId; const location = useLocation(); const queryParams = new URLSearchParams(location.search); - const orgId = useParams().orgId; + const { orgId } = useParams<{orgId?: string}>(); const loginType = systemConfig?.authConfigs.find( (config) => config.sourceType === queryParams.get(AuthSearchParams.loginType) @@ -143,7 +144,10 @@ function Login() { heading={loginHeading} subHeading={loginSubHeading} > - + { Boolean(organizationId) + ? + : + } ); From 948dd93ae808a04f70da1550c41aba24f55323bc Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 23 Oct 2024 21:06:51 +0500 Subject: [PATCH 02/12] show error where user doesn't belong to workspace --- .../lowcoder/src/pages/userAuth/formLoginSteps.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx index 8e76f37a03..cdaa46f827 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx @@ -116,9 +116,11 @@ export default function FormLoginSteps() { OrgApi.fetchOrgsByEmail(account) .then((resp) => { if (validateResponse(resp)) { - console.log(resp.data.data); setOrgList(resp.data.data); - if (resp.data.data.length === 1) { + if (!resp.data.data.lenght) { + throw new Error('Error: no workspaces found'); + } + else if (resp.data.data.length === 1) { setOrganizationId(resp.data.data[0].orgId); setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS); return; @@ -154,7 +156,6 @@ export default function FormLoginSteps() { }} /> - {/* {trans("userAuth.login")} */} Continue
From 2b2116145814efa84148b944d0db01e990eb292f Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 24 Oct 2024 12:05:10 +0500 Subject: [PATCH 03/12] show auth providers for selected workspace --- client/packages/lowcoder/src/i18n/locales/en.ts | 5 ++++- .../lowcoder/src/pages/userAuth/formLoginSteps.tsx | 13 ++++++++----- .../pages/userAuth/thirdParty/thirdPartyAuth.tsx | 9 ++++++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 61450a61f0..6b9becf132 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -3008,7 +3008,10 @@ export const en = { "resetSuccessDesc": "Password Reset Succeeded. The New Password is: {password}", "resetLostPasswordSuccess": "Password Reset Succeeded. Please login again.", "copyPassword": "Copy Password", - "poweredByLowcoder": "Powered by: Lowcoder.cloud" + "poweredByLowcoder": "Powered by: Lowcoder.cloud", + "continue": "Continue", + "enterPassword": "Enter your password", + "selectWorkspace": "Select your workspace", }, "preLoad": { "jsLibraryHelpText": "Add JavaScript Libraries to Your Current Application via URL Addresses. lodash, day.js, uuid, numbro are Built into the System for Immediate Use. JavaScript Libraries are Loaded Before the Application is Initialized, Which Can Have an Impact on Application Performance.", diff --git a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx index cdaa46f827..7ece4e31fb 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx @@ -25,6 +25,8 @@ import Card from "antd/es/card/Card"; import { AccountLoginWrapper } from "./formLogin"; import { default as Button } from "antd/es/button"; import LeftOutlined from "@ant-design/icons/LeftOutlined"; +import { fetchConfigAction } from "@lowcoder-ee/redux/reduxActions/configActions"; +import { useDispatch } from "react-redux"; const StyledCard = styled.div<{$selected: boolean}>` display: flex; @@ -83,6 +85,7 @@ const StepBackButton = (props : { ) export default function FormLoginSteps() { + const dispatch = useDispatch(); const [account, setAccount] = useState(""); const [password, setPassword] = useState(""); const redirectUrl = useRedirectUrl(); @@ -117,7 +120,7 @@ export default function FormLoginSteps() { .then((resp) => { if (validateResponse(resp)) { setOrgList(resp.data.data); - if (!resp.data.data.lenght) { + if (!resp.data.data.length) { throw new Error('Error: no workspaces found'); } else if (resp.data.data.length === 1) { @@ -145,7 +148,6 @@ export default function FormLoginSteps() { setAccount(valid ? value : "")} @@ -156,7 +158,7 @@ export default function FormLoginSteps() { }} /> - Continue + {trans("userAuth.continue")} @@ -177,13 +179,14 @@ export default function FormLoginSteps() { <> setCurrentStep(CurrentStepEnum.EMAIL)} /> - + {orgList.map(org => ( { setOrganizationId(org.orgId); + dispatch(fetchConfigAction(org.orgId)); setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS); }} > @@ -199,7 +202,7 @@ export default function FormLoginSteps() { <> setCurrentStep(CurrentStepEnum.WORKSPACES)} /> - + - { Boolean(socialLoginButtons.length) && } + { Boolean(socialLoginButtons.length) && ( + + or + + )} {socialLoginButtons} ); From bf4fb3c6cd714e9427df874b317708cc0a716701 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 24 Oct 2024 12:51:46 +0500 Subject: [PATCH 04/12] redirect to signup page if no workspace found on login --- .../src/pages/userAuth/formLoginSteps.tsx | 18 +++++++++++++----- .../lowcoder/src/pages/userAuth/register.tsx | 11 +++++++---- .../userAuth/thirdParty/thirdPartyAuth.tsx | 10 +++++++++- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx index 7ece4e31fb..4ae2878ba9 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx @@ -6,7 +6,7 @@ import { LoginCardTitle, StyledRouteLink, } from "pages/userAuth/authComponents"; -import React, { useContext, useMemo, useState } from "react"; +import React, { useContext, useState } from "react"; import styled from "styled-components"; import UserApi from "api/userApi"; import { useRedirectUrl } from "util/hooks"; @@ -27,6 +27,7 @@ import { default as Button } from "antd/es/button"; import LeftOutlined from "@ant-design/icons/LeftOutlined"; import { fetchConfigAction } from "@lowcoder-ee/redux/reduxActions/configActions"; import { useDispatch } from "react-redux"; +import history from "util/history"; const StyledCard = styled.div<{$selected: boolean}>` display: flex; @@ -86,13 +87,16 @@ const StepBackButton = (props : { ) export default function FormLoginSteps() { const dispatch = useDispatch(); - const [account, setAccount] = useState(""); + const location = useLocation(); + const [account, setAccount] = useState(() => { + const { email } = (location.state || {}) as any; + return email ?? ''; + }); const [password, setPassword] = useState(""); const redirectUrl = useRedirectUrl(); const { systemConfig, inviteInfo, fetchUserAfterAuthSuccess } = useContext(AuthContext); const invitationId = inviteInfo?.invitationId; const authId = systemConfig?.form.id; - const location = useLocation(); const [orgLoading, setOrgLoading] = useState(false); const [orgList, setOrgList] = useState([]); const [currentStep, setCurrentStep] = useState(CurrentStepEnum.EMAIL); @@ -121,9 +125,13 @@ export default function FormLoginSteps() { if (validateResponse(resp)) { setOrgList(resp.data.data); if (!resp.data.data.length) { - throw new Error('Error: no workspaces found'); + history.push( + AUTH_REGISTER_URL, + {...location.state || {}, email: account}, + ) + return; } - else if (resp.data.data.length === 1) { + if (resp.data.data.length === 1) { setOrganizationId(resp.data.data[0].orgId); setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS); return; diff --git a/client/packages/lowcoder/src/pages/userAuth/register.tsx b/client/packages/lowcoder/src/pages/userAuth/register.tsx index 88e6cadd7f..16c070a2e8 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, useMemo } from "react"; +import React, { useContext, useState, useMemo, useEffect } from "react"; import { AuthContainer, ConfirmButton, @@ -37,11 +37,14 @@ const RegisterContent = styled(FormWrapperMobile)` `; function UserRegister() { + const location = useLocation(); const [submitBtnDisable, setSubmitBtnDisable] = useState(false); - const [account, setAccount] = useState(""); + const [account, setAccount] = useState(() => { + const { email } = (location.state || {}) as any; + return email ?? ''; + }); const [password, setPassword] = useState(""); const redirectUrl = useRedirectUrl(); - const location = useLocation(); const { systemConfig, inviteInfo, fetchUserAfterAuthSuccess } = useContext(AuthContext); const invitationId = inviteInfo?.invitationId; @@ -81,10 +84,10 @@ function UserRegister() { type="large" > - {/* {trans("userAuth.registerByEmail")} */} setAccount(valid ? value : "")} placeholder={trans("userAuth.inputEmail")} checkRule={{ diff --git a/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx b/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx index 564aa4f608..daf1cbf311 100644 --- a/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx @@ -7,7 +7,7 @@ import { WhiteLoading } from "lowcoder-design"; import history from "util/history"; import { LoginLogoStyle, LoginLabelStyle, StyledLoginButton } from "pages/userAuth/authComponents"; import { useSelector } from "react-redux"; -import { selectSystemConfig } from "redux/selectors/configSelectors"; +import { getSystemConfigFetching, selectSystemConfig } from "redux/selectors/configSelectors"; import React from "react"; import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"; import styled from "styled-components"; @@ -17,6 +17,8 @@ import { default as Divider } from "antd/es/divider"; import { default as Typography } from "antd/es/typography"; import { useRedirectUrl } from "util/hooks"; import { MultiIconDisplay } from "../../../comps/comps/multiIconDisplay"; +import Spin from "antd/es/spin"; +import { LoadingOutlined } from "@ant-design/icons"; const { Text } = Typography; @@ -107,7 +109,13 @@ export function ThirdPartyAuth(props: { authGoal: ThirdPartyAuthGoal; labelFormatter?: (name: string) => string; }) { + const systemConfigFetching = useSelector(getSystemConfigFetching); const systemConfig = useSelector(selectSystemConfig); + + if (systemConfigFetching) { + return } />; + } + if (!systemConfig) { return null; } From b8284c5a421baaca59db85a4d8e594804d2035a4 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 24 Oct 2024 21:42:27 +0500 Subject: [PATCH 05/12] added delete auth provider functionality --- .../packages/lowcoder/src/api/idSourceApi.ts | 4 +- .../packages/lowcoder/src/i18n/locales/en.ts | 4 +- .../setting/idSource/detail/deleteConfig.tsx | 56 +++++++++++++------ .../pages/setting/idSource/detail/index.tsx | 17 ++++-- .../src/pages/setting/idSource/list.tsx | 6 +- .../setting/idSource/styledComponents.tsx | 24 +------- 6 files changed, 60 insertions(+), 51 deletions(-) diff --git a/client/packages/lowcoder/src/api/idSourceApi.ts b/client/packages/lowcoder/src/api/idSourceApi.ts index 98e6141d73..00f2b7fcfa 100644 --- a/client/packages/lowcoder/src/api/idSourceApi.ts +++ b/client/packages/lowcoder/src/api/idSourceApi.ts @@ -44,8 +44,8 @@ class IdSourceApi extends Api { return Api.post(IdSourceApi.saveConfigURL, request); } - static deleteConfig(id: string): AxiosPromise { - return Api.delete(IdSourceApi.deleteConfigURL(id)); + static deleteConfig(id: string, deleteConfig?: boolean): AxiosPromise { + return Api.delete(IdSourceApi.deleteConfigURL(id), {delete: deleteConfig}); } static syncManual(authType: string): AxiosPromise { diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 6b9becf132..0bb42fb5f0 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -3545,12 +3545,12 @@ export const en = { "formSelectPlaceholder": "Please Select the {label}", "saveSuccess": "Saved Successfully", "dangerLabel": "Danger Zone", - "dangerTip": "Disabling This ID Provider May Result in Some Users Being Unable to Log In. Proceed With Caution.", + "dangerTip": "Disabling or Deleting This ID Provider May Result in Some Users Being Unable to Log In. Proceed With Caution.", "disable": "Disable", "disableSuccess": "Disabled Successfully", "encryptedServer": "-------- Encrypted on the Server Side --------", "disableTip": "Tips", - "disableContent": "Disabling This ID Provider May Result in Some Users Being Unable to Log In. Are You Sure to Proceed?", + "disableContent": "{action} This ID Provider May Result in Some Users Being Unable to Log In. Are You Sure to Proceed?", "manualTip": "", "lockTip": "The Content is Locked. To Make Changes, Please Click the {icon} to Unlock.", "lockModalContent": "Changing the 'ID Attribute' Field Can Have Significant Impacts on User Identification. Please Confirm That You Understand the Implications of This Change Before Proceeding.", diff --git a/client/packages/lowcoder/src/pages/setting/idSource/detail/deleteConfig.tsx b/client/packages/lowcoder/src/pages/setting/idSource/detail/deleteConfig.tsx index ec0a332572..c0af7c3f75 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/detail/deleteConfig.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/detail/deleteConfig.tsx @@ -4,42 +4,62 @@ import { trans } from "i18n"; import { useState } from "react"; import { validateResponse } from "api/apiUtils"; import IdSourceApi from "api/idSourceApi"; -import { DangerIcon, CustomModal } from "lowcoder-design"; +import { CustomModal } from "lowcoder-design"; import history from "util/history"; import { OAUTH_PROVIDER_SETTING } from "constants/routesURL"; import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"; +import Flex from "antd/es/flex"; +import Alert from "antd/es/alert"; -export const DeleteConfig = (props: { id: string }) => { +export const DeleteConfig = (props: { + id: string, + allowDelete?: boolean, + allowDisable?: boolean, +}) => { + const [disableLoading, setDisableLoading] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false); - const handleDelete = () => { + + const handleDelete = (deleteConfig?: boolean) => { + const setLoading = deleteConfig ? setDeleteLoading : setDisableLoading; + const action = deleteConfig ? trans("delete") : trans("idSource.disable"); CustomModal.confirm({ title: trans("idSource.disableTip"), - content: trans("idSource.disableContent"), + content: trans("idSource.disableContent", {action}), onConfirm: () => { - setDeleteLoading(true); - IdSourceApi.deleteConfig(props.id) - .then((resp) => { - if (validateResponse(resp)) { - messageInstance.success(trans("idSource.disableSuccess"), 0.8, () => + setLoading(true); + IdSourceApi.deleteConfig(props.id, deleteConfig) + .then((resp) => { + if (validateResponse(resp)) { + const successMsg = deleteConfig ? trans("home.deleteSuccessMsg") : trans("idSource.disableSuccess"); + messageInstance.success(successMsg, 0.8, () => history.push(OAUTH_PROVIDER_SETTING) ); } }) .catch((e) => messageInstance.error(e.message)) - .finally(() => setDeleteLoading(false)); + .finally(() => setLoading(false)); }, }); }; return ( -
{trans("idSource.dangerLabel")}
-
- - {trans("idSource.dangerTip")} -
- +

{trans("idSource.dangerLabel")}

+ + + + {props.allowDelete && ( + + )} +
); }; diff --git a/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx b/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx index b5e1fdb9ed..0f3f324448 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx @@ -44,19 +44,22 @@ import { sourceMappingKeys } from "../OAuthForms/GenericOAuthForm"; import Flex from "antd/es/flex"; type IdSourceDetailProps = { - location: Location & { state: ConfigItem }; + location: Location & { state: { config: ConfigItem, totalEnabledConfigs: number }}; }; export const IdSourceDetail = (props: IdSourceDetailProps) => { - const configDetail = props.location.state; + const { + config: configDetail, + totalEnabledConfigs, + } = props.location.state; const [form] = useForm(); const [lock, setLock] = useState(() => { - const config = props.location.state; + const { config } = props.location.state; return !config.ifLocal; }); const [saveLoading, setSaveLoading] = useState(false); const [saveDisable, setSaveDisable] = useState(() => { - const config = props.location.state; + const { config } = props.location.state; if ( (config.authType === AuthType.Form && !config.enable) || (!config.ifLocal && !config.enable) @@ -324,7 +327,11 @@ export const IdSourceDetail = (props: IdSourceDetailProps) => { {configDetail.enable && ( <> - + )} diff --git a/client/packages/lowcoder/src/pages/setting/idSource/list.tsx b/client/packages/lowcoder/src/pages/setting/idSource/list.tsx index 4843d0492d..fa235a9e65 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/list.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/list.tsx @@ -33,7 +33,6 @@ import { FreeTypes } from "pages/setting/idSource/idSourceConstants"; import { messageInstance, AddIcon } from "lowcoder-design"; import { currentOrgAdmin } from "../../../util/permissionUtils"; import CreateModal from "./createModal"; -import _ from "lodash"; import { HelpText } from "components/HelpText"; import { IconControlView } from "@lowcoder-ee/comps/controls/iconControl"; @@ -42,6 +41,7 @@ export const IdSourceList = (props: any) => { const config = useSelector(selectSystemConfig); const { currentOrgId} = user; const [configs, setConfigs] = useState([]); + const [enabledConfigs, setEnabledConfigs] = useState([]); const [fetching, setFetching] = useState(false); const [modalVisible, setModalVisible] = useState(false); const enableEnterpriseLogin = useSelector(selectSystemConfig)?.featureFlag?.enableEnterpriseLogin; @@ -76,8 +76,8 @@ export const IdSourceList = (props: any) => { let res: ConfigItem[] = resp.data.data.filter((item: ConfigItem) => IdSource.includes(item.authType) ); - // res = _.uniqBy(res, 'authType'); setConfigs(res); + setEnabledConfigs(res.filter(item => item.enable)); } }) .catch((e) => { @@ -126,7 +126,7 @@ export const IdSourceList = (props: any) => { } history.push({ pathname: OAUTH_PROVIDER_DETAIL, - state: record, + state: { config: record, totalEnabledConfigs: enabledConfigs.length }, }); }, })} diff --git a/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx b/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx index e05a87a279..7cdc3e4fa3 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx @@ -258,10 +258,9 @@ export const DeleteWrapper = styled.div` line-height: 19px; .danger-tip { - height: 32px; - padding: 0 16px 0 8px; - margin: 5px 0 8px 0; - background: #fff3f1; + max-width: 440px; + padding: 8px 16px; + margin: 5px 0 12px 0; border-radius: 4px; display: inline-flex; align-items: center; @@ -270,23 +269,6 @@ export const DeleteWrapper = styled.div` margin-right: 8px; } } - - .ant-btn { - min-width: 84px; - display: block; - padding: 4px 8px; - background: #fef4f4; - border: 1px solid #fccdcd; - font-size: 13px; - color: #f73131; - - &:hover, - &.ant-btn-loading { - background: #feecec; - } - - ${btnLoadingCss} - } `; export const StatusSpan = styled.span` From cb594eed2d5fe48181bb130339425314c807f923 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 25 Oct 2024 21:02:09 +0500 Subject: [PATCH 06/12] show new auth flow when user is invited or joins by org url --- .../src/pages/userAuth/formLoginSteps.tsx | 24 ++++++++++++++++--- .../lowcoder/src/pages/userAuth/login.tsx | 5 ++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx index 4ae2878ba9..46becb1419 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx @@ -85,7 +85,12 @@ const StepBackButton = (props : { Back ) -export default function FormLoginSteps() { + +type FormLoginProps = { + organizationId?: string; +} + +export default function FormLoginSteps(props: FormLoginProps) { const dispatch = useDispatch(); const location = useLocation(); const [account, setAccount] = useState(() => { @@ -100,7 +105,8 @@ export default function FormLoginSteps() { const [orgLoading, setOrgLoading] = useState(false); const [orgList, setOrgList] = useState([]); const [currentStep, setCurrentStep] = useState(CurrentStepEnum.EMAIL); - const [organizationId, setOrganizationId] = useState(); + const [organizationId, setOrganizationId] = useState(props.organizationId); + const [skipWorkspaceStep, setSkipWorkspaceStep] = useState(false); const { onSubmit, loading } = useAuthSubmit( () => @@ -119,6 +125,15 @@ export default function FormLoginSteps() { ); const fetchOrgsByEmail = () => { + // if user is invited or using org's login url then avoid fetching workspaces + // and skip workspace selection step + if (organizationId) { + setSkipWorkspaceStep(true); + dispatch(fetchConfigAction(organizationId)); + setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS); + return; + } + setOrgLoading(true); OrgApi.fetchOrgsByEmail(account) .then((resp) => { @@ -209,7 +224,10 @@ export default function FormLoginSteps() { return ( <> - setCurrentStep(CurrentStepEnum.WORKSPACES)} /> + { + if (skipWorkspaceStep) return setCurrentStep(CurrentStepEnum.EMAIL); + setCurrentStep(CurrentStepEnum.WORKSPACES) + }} /> - { Boolean(organizationId) + + {/* { Boolean(organizationId) ? : - } + } */} ); From 5d1f6362d49166a6dba956540477489acc0fd245 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Mon, 28 Oct 2024 22:07:51 +0500 Subject: [PATCH 07/12] added admin login page --- client/packages/lowcoder/src/app.tsx | 2 + .../lowcoder/src/constants/routesURL.ts | 1 + .../{formLogin.tsx => formLoginAdmin.tsx} | 50 ++----------------- .../src/pages/userAuth/formLoginSteps.tsx | 3 +- .../lowcoder/src/pages/userAuth/index.tsx | 4 +- .../lowcoder/src/pages/userAuth/login.tsx | 6 +-- .../src/pages/userAuth/loginAdmin.tsx | 23 +++++++++ 7 files changed, 34 insertions(+), 55 deletions(-) rename client/packages/lowcoder/src/pages/userAuth/{formLogin.tsx => formLoginAdmin.tsx} (52%) create mode 100644 client/packages/lowcoder/src/pages/userAuth/loginAdmin.tsx diff --git a/client/packages/lowcoder/src/app.tsx b/client/packages/lowcoder/src/app.tsx index f6cbdac583..8ff890129a 100644 --- a/client/packages/lowcoder/src/app.tsx +++ b/client/packages/lowcoder/src/app.tsx @@ -28,6 +28,7 @@ import { ADMIN_APP_URL, ORG_AUTH_FORGOT_PASSWORD_URL, ORG_AUTH_RESET_PASSWORD_URL, + ADMIN_AUTH_URL, } from "constants/routesURL"; import React from "react"; import { createRoot } from "react-dom/client"; @@ -337,6 +338,7 @@ class AppIndex extends React.Component { // component={ApplicationListPage} component={LazyApplicationHome} /> + ().orgId; + const { fetchUserAfterAuthSuccess } = useContext(AuthContext); const { onSubmit, loading } = useAuthSubmit( () => @@ -51,13 +37,11 @@ export default function FormLogin(props: FormLoginProps) { register: false, loginId: account, password: password, - invitationId: invitationId, source: UserConnectionSource.email, orgId: props.organizationId, - authId, }), false, - redirectUrl, + null, fetchUserAfterAuthSuccess, ); @@ -79,38 +63,10 @@ export default function FormLogin(props: FormLoginProps) { onChange={(value) => setPassword(value)} valueCheck={() => [true, ""]} /> - - - {`${trans("userAuth.forgotPassword")}?`} - - - + {trans("userAuth.login")} - {props.organizationId && ( - - )} - - - {trans("userAuth.register")} - - ); } diff --git a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx index 46becb1419..3e7c561436 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx @@ -21,8 +21,7 @@ import { Divider } from "antd"; import Flex from "antd/es/flex"; import { validateResponse } from "@lowcoder-ee/api/apiUtils"; import OrgApi from "@lowcoder-ee/api/orgApi"; -import Card from "antd/es/card/Card"; -import { AccountLoginWrapper } from "./formLogin"; +import { AccountLoginWrapper } from "./formLoginAdmin"; import { default as Button } from "antd/es/button"; import LeftOutlined from "@ant-design/icons/LeftOutlined"; import { fetchConfigAction } from "@lowcoder-ee/redux/reduxActions/configActions"; diff --git a/client/packages/lowcoder/src/pages/userAuth/index.tsx b/client/packages/lowcoder/src/pages/userAuth/index.tsx index 7d28cd551c..40e7a1bc15 100644 --- a/client/packages/lowcoder/src/pages/userAuth/index.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/index.tsx @@ -1,4 +1,4 @@ -import { AUTH_LOGIN_URL, USER_AUTH_URL } from "constants/routesURL"; +import { ADMIN_AUTH_URL, AUTH_LOGIN_URL, USER_AUTH_URL } from "constants/routesURL"; import { Redirect, Route, Switch, useLocation, useParams } from "react-router-dom"; import React, { useEffect, useMemo } from "react"; import { useSelector, useDispatch } from "react-redux"; @@ -9,6 +9,7 @@ import { AuthLocationState } from "constants/authConstants"; import { ProductLoading } from "components/ProductLoading"; import { fetchConfigAction } from "redux/reduxActions/configActions"; import { fetchUserAction } from "redux/reduxActions/userActions"; +import LoginAdmin from "./loginAdmin"; import _ from "lodash"; export default function UserAuth() { @@ -51,6 +52,7 @@ export default function UserAuth() { > + {AuthRoutes.map((route) => ( ))} diff --git a/client/packages/lowcoder/src/pages/userAuth/login.tsx b/client/packages/lowcoder/src/pages/userAuth/login.tsx index be6f68136f..bad5349099 100644 --- a/client/packages/lowcoder/src/pages/userAuth/login.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/login.tsx @@ -3,7 +3,7 @@ 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 FormLogin from "@lowcoder-ee/pages/userAuth/formLoginAdmin"; import { AuthContainer } from "pages/userAuth/authComponents"; import React, { useContext, useMemo } from "react"; import { AuthContext, getLoginTitle } from "pages/userAuth/authUtils"; @@ -145,10 +145,6 @@ function Login() { subHeading={loginSubHeading} > - {/* { Boolean(organizationId) - ? - : - } */} ); diff --git a/client/packages/lowcoder/src/pages/userAuth/loginAdmin.tsx b/client/packages/lowcoder/src/pages/userAuth/loginAdmin.tsx new file mode 100644 index 0000000000..f91663128a --- /dev/null +++ b/client/packages/lowcoder/src/pages/userAuth/loginAdmin.tsx @@ -0,0 +1,23 @@ +import { trans } from "i18n"; +import FormLogin from "@lowcoder-ee/pages/userAuth/formLoginAdmin"; +import { AuthContainer } from "pages/userAuth/authComponents"; +import { requiresUnAuth } from "pages/userAuth/authHOC"; + +// this is the classic Sign In for super admin +function LoginAdmin() { + const loginHeading = trans("userAuth.userLogin"); + const loginSubHeading = trans("userAuth.poweredByLowcoder"); + + return ( + <> + + + + + ); +} + +export default requiresUnAuth(LoginAdmin); From 0f71d0f21c29d945b6ddf371cc2468493b3cd4cc Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 6 Nov 2024 23:43:45 +0500 Subject: [PATCH 08/12] Allow delete for disabled auth provider --- .../setting/idSource/detail/deleteConfig.tsx | 19 +++++++++++++++---- .../pages/setting/idSource/detail/index.tsx | 19 +++++++++---------- .../setting/idSource/styledComponents.tsx | 1 - 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/idSource/detail/deleteConfig.tsx b/client/packages/lowcoder/src/pages/setting/idSource/detail/deleteConfig.tsx index c0af7c3f75..aa05b4e3ff 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/detail/deleteConfig.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/detail/deleteConfig.tsx @@ -15,6 +15,7 @@ export const DeleteConfig = (props: { id: string, allowDelete?: boolean, allowDisable?: boolean, + isLastEnabledConfig?: boolean, }) => { const [disableLoading, setDisableLoading] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false); @@ -50,12 +51,22 @@ export const DeleteConfig = (props: { type="warning" showIcon /> + {props.isLastEnabledConfig && ( + + )} - + {props.allowDisable && ( + + )} {props.allowDelete && ( - )} diff --git a/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx b/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx index 0f3f324448..20619731bf 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx @@ -324,16 +324,15 @@ export const IdSourceDetail = (props: IdSourceDetailProps) => { )} - {configDetail.enable && ( - <> - - - - )} + <> + + + ); diff --git a/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx b/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx index 7cdc3e4fa3..091aae36eb 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx @@ -262,7 +262,6 @@ export const DeleteWrapper = styled.div` padding: 8px 16px; margin: 5px 0 12px 0; border-radius: 4px; - display: inline-flex; align-items: center; svg { From 5fd7403222cafa8f7ec54eae3f7805ff3f0307db Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 6 Nov 2024 23:44:46 +0500 Subject: [PATCH 09/12] hide password field and signup option when Email based auth is disabled --- .../packages/lowcoder/src/i18n/locales/en.ts | 2 + .../src/pages/userAuth/formLoginSteps.tsx | 71 +++++++++++-------- .../userAuth/thirdParty/thirdPartyAuth.tsx | 3 +- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 0bb42fb5f0..d30bd72d40 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -3011,6 +3011,7 @@ export const en = { "poweredByLowcoder": "Powered by: Lowcoder.cloud", "continue": "Continue", "enterPassword": "Enter your password", + "selectAuthProvider": "Select Authentication Provider", "selectWorkspace": "Select your workspace", }, "preLoad": { @@ -3546,6 +3547,7 @@ export const en = { "saveSuccess": "Saved Successfully", "dangerLabel": "Danger Zone", "dangerTip": "Disabling or Deleting This ID Provider May Result in Some Users Being Unable to Log In. Proceed With Caution.", + "lastEnabledConfig": "You can't disable/delete config as this is the only enabled configuration.", "disable": "Disable", "disableSuccess": "Disabled Successfully", "encryptedServer": "-------- Encrypted on the Server Side --------", diff --git a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx index 3e7c561436..f340935f7a 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx @@ -101,6 +101,7 @@ export default function FormLoginSteps(props: FormLoginProps) { const { systemConfig, inviteInfo, fetchUserAfterAuthSuccess } = useContext(AuthContext); const invitationId = inviteInfo?.invitationId; const authId = systemConfig?.form.id; + const isFormLoginEnabled = systemConfig?.form.enableLogin; const [orgLoading, setOrgLoading] = useState(false); const [orgList, setOrgList] = useState([]); const [currentStep, setCurrentStep] = useState(CurrentStepEnum.EMAIL); @@ -227,27 +228,35 @@ export default function FormLoginSteps(props: FormLoginProps) { if (skipWorkspaceStep) return setCurrentStep(CurrentStepEnum.EMAIL); setCurrentStep(CurrentStepEnum.WORKSPACES) }} /> - - setPassword(value)} - valueCheck={() => [true, ""]} + - - - {`${trans("userAuth.forgotPassword")}?`} - - - - {trans("userAuth.login")} - + {isFormLoginEnabled && ( + <> + setPassword(value)} + valueCheck={() => [true, ""]} + /> + + + {`${trans("userAuth.forgotPassword")}?`} + + + + {trans("userAuth.login")} + + + )} {organizationId && ( )}
- - - - {trans("userAuth.register")} - - + {isFormLoginEnabled && ( + <> + + + + {trans("userAuth.register")} + + + + )} ); } diff --git a/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx b/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx index daf1cbf311..189afe5737 100644 --- a/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx @@ -111,6 +111,7 @@ export function ThirdPartyAuth(props: { }) { const systemConfigFetching = useSelector(getSystemConfigFetching); const systemConfig = useSelector(selectSystemConfig); + const isFormLoginEnabled = systemConfig?.form.enableLogin; if (systemConfigFetching) { return } />; @@ -139,7 +140,7 @@ export function ThirdPartyAuth(props: { }); return ( - { Boolean(socialLoginButtons.length) && ( + { isFormLoginEnabled && Boolean(socialLoginButtons.length) && ( or From 9c0b99770bac9e68cb3f7bd4baf9aaa81a8ae0cb Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 7 Nov 2024 00:59:33 +0500 Subject: [PATCH 10/12] On signup, when user enter emails and it already exists then redirect to signin --- .../src/components/tacoInput.tsx | 4 +- .../src/pages/userAuth/formLoginSteps.tsx | 3 +- .../lowcoder/src/pages/userAuth/register.tsx | 133 +++++++++++------- 3 files changed, 88 insertions(+), 52 deletions(-) diff --git a/client/packages/lowcoder-design/src/components/tacoInput.tsx b/client/packages/lowcoder-design/src/components/tacoInput.tsx index f0208e52fc..e7ce12111c 100644 --- a/client/packages/lowcoder-design/src/components/tacoInput.tsx +++ b/client/packages/lowcoder-design/src/components/tacoInput.tsx @@ -331,13 +331,14 @@ const FormInput = (props: { check: (value: string) => boolean; }; formName?: string; + onBlur?: () => void; onChange?: (value: string, valid: boolean) => void; className?: string; inputRef?: Ref; msg?: string; defaultValue?: string; }) => { - const { mustFill, checkRule, label, placeholder, onChange, formName, className, inputRef, defaultValue } = + const { mustFill, checkRule, label, placeholder, onBlur, onChange, formName, className, inputRef, defaultValue } = props; const [valueValid, setValueValid] = useState(true); return ( @@ -360,6 +361,7 @@ const FormInput = (props: { } onChange && onChange(e.target.value, valid); }} + onBlur={() => onBlur?.()} /> ); diff --git a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx index f340935f7a..f3f0d30318 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx @@ -127,7 +127,7 @@ export default function FormLoginSteps(props: FormLoginProps) { const fetchOrgsByEmail = () => { // if user is invited or using org's login url then avoid fetching workspaces // and skip workspace selection step - if (organizationId) { + if (Boolean(organizationId)) { setSkipWorkspaceStep(true); dispatch(fetchConfigAction(organizationId)); setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS); @@ -148,6 +148,7 @@ export default function FormLoginSteps(props: FormLoginProps) { } if (resp.data.data.length === 1) { setOrganizationId(resp.data.data[0].orgId); + dispatch(fetchConfigAction(resp.data.data[0].orgId)); setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS); return; } diff --git a/client/packages/lowcoder/src/pages/userAuth/register.tsx b/client/packages/lowcoder/src/pages/userAuth/register.tsx index 16c070a2e8..cfae0f0938 100644 --- a/client/packages/lowcoder/src/pages/userAuth/register.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/register.tsx @@ -7,7 +7,7 @@ import { StyledRouteLinkLogin, TermsAndPrivacyInfo, } from "pages/userAuth/authComponents"; -import { FormInput, PasswordInput } from "lowcoder-design"; +import { CustomModal, FormInput, messageInstance, PasswordInput } from "lowcoder-design"; import { AUTH_LOGIN_URL, ORG_AUTH_LOGIN_URL } from "constants/routesURL"; import UserApi from "api/userApi"; import { useRedirectUrl } from "util/hooks"; @@ -21,6 +21,11 @@ import { AuthContext, checkPassWithMsg, useAuthSubmit } from "pages/userAuth/aut import { ThirdPartyAuth } from "pages/userAuth/thirdParty/thirdPartyAuth"; import { useParams } from "react-router-dom"; import { Divider } from "antd"; +import { OrgApi } from "@lowcoder-ee/api/orgApi"; +import { validateResponse } from "@lowcoder-ee/api/apiUtils"; +import history from "util/history"; +import LoadingOutlined from "@ant-design/icons/LoadingOutlined"; +import Spin from "antd/es/spin"; const StyledFormInput = styled(FormInput)` margin-bottom: 16px; @@ -44,6 +49,8 @@ function UserRegister() { return email ?? ''; }); const [password, setPassword] = useState(""); + const [orgLoading, setOrgLoading] = useState(false); + const [lastEmailChecked, setLastEmailChecked] = useState(""); const redirectUrl = useRedirectUrl(); const { systemConfig, inviteInfo, fetchUserAfterAuthSuccess } = useContext(AuthContext); const invitationId = inviteInfo?.invitationId; @@ -74,60 +81,86 @@ function UserRegister() { fetchUserAfterAuthSuccess, ); + const checkEmailExist = () => { + if (!Boolean(account.length) || lastEmailChecked === account) return; + + setOrgLoading(true); + OrgApi.fetchOrgsByEmail(account) + .then((resp) => { + if (validateResponse(resp)) { + const orgList = resp.data.data; + if (orgList.length) { + messageInstance.error('Email is already registered'); + history.push( + AUTH_LOGIN_URL, + {...location.state || {}, email: account}, + ) + } + } + }) + .finally(() => { + setLastEmailChecked(account) + setOrgLoading(false); + }); + } + const registerHeading = trans("userAuth.register") const registerSubHeading = trans("userAuth.poweredByLowcoder"); return ( - - - setAccount(valid ? value : "")} - placeholder={trans("userAuth.inputEmail")} - checkRule={{ - check: checkEmailValid, - errorMsg: trans("userAuth.inputValidEmail"), - }} - /> - setPassword(valid ? value : "")} - doubleCheck - /> - - {trans("userAuth.register")} - - setSubmitBtnDisable(!e.target.checked)} /> - {organizationId && ( - } spinning={orgLoading}> + + + setAccount(valid ? value : "")} + onBlur={checkEmailExist} + placeholder={trans("userAuth.inputEmail")} + checkRule={{ + check: checkEmailValid, + errorMsg: trans("userAuth.inputValidEmail"), + }} + /> + setPassword(valid ? value : "")} + doubleCheck /> - )} - - - {trans("userAuth.userLogin")} - - + + {trans("userAuth.register")} + + setSubmitBtnDisable(!e.target.checked)} /> + {organizationId && ( + + )} + + + {trans("userAuth.userLogin")} + + + ); } From 2a95c201a49f1f30c2182feb331acd34469e72bb Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 7 Nov 2024 12:04:50 +0500 Subject: [PATCH 11/12] small fix --- .../packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx | 4 ++-- client/packages/lowcoder/src/pages/userAuth/register.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx index f3f0d30318..afc04266af 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx @@ -247,7 +247,7 @@ export default function FormLoginSteps(props: FormLoginProps) { {`${trans("userAuth.forgotPassword")}?`} @@ -272,7 +272,7 @@ export default function FormLoginSteps(props: FormLoginProps) { {trans("userAuth.register")} diff --git a/client/packages/lowcoder/src/pages/userAuth/register.tsx b/client/packages/lowcoder/src/pages/userAuth/register.tsx index cfae0f0938..b7a2ecc6ef 100644 --- a/client/packages/lowcoder/src/pages/userAuth/register.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/register.tsx @@ -7,7 +7,7 @@ import { StyledRouteLinkLogin, TermsAndPrivacyInfo, } from "pages/userAuth/authComponents"; -import { CustomModal, FormInput, messageInstance, PasswordInput } from "lowcoder-design"; +import { FormInput, messageInstance, PasswordInput } from "lowcoder-design"; import { AUTH_LOGIN_URL, ORG_AUTH_LOGIN_URL } from "constants/routesURL"; import UserApi from "api/userApi"; import { useRedirectUrl } from "util/hooks"; From f838d869bb5975d13d932e1c237a4f663ca2b6f9 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 7 Nov 2024 22:53:41 +0500 Subject: [PATCH 12/12] show/hide sign up page based on LOWCODER_EMAIL_SIGNUP_ENABLED flag from serverSettings --- .../lowcoder/src/api/applicationApi.ts | 5 +++ client/packages/lowcoder/src/app.tsx | 7 +++- .../src/constants/reduxActionConstants.ts | 2 + .../src/pages/userAuth/formLoginSteps.tsx | 40 +++++++++++++------ .../lowcoder/src/pages/userAuth/register.tsx | 17 ++++++++ .../reducers/uiReducers/applicationReducer.ts | 8 ++++ .../redux/reduxActions/applicationActions.ts | 4 ++ .../src/redux/sagas/applicationSagas.ts | 18 +++++++++ .../redux/selectors/applicationSelector.ts | 4 ++ 9 files changed, 92 insertions(+), 13 deletions(-) diff --git a/client/packages/lowcoder/src/api/applicationApi.ts b/client/packages/lowcoder/src/api/applicationApi.ts index d38ee18438..a0edb74243 100644 --- a/client/packages/lowcoder/src/api/applicationApi.ts +++ b/client/packages/lowcoder/src/api/applicationApi.ts @@ -98,6 +98,7 @@ class ApplicationApi extends Api { static publicToMarketplaceURL = (applicationId: string) => `/applications/${applicationId}/public-to-marketplace`; static getMarketplaceAppURL = (applicationId: string) => `/applications/${applicationId}/view_marketplace`; static setAppEditingStateURL = (applicationId: string) => `/applications/editState/${applicationId}`; + static serverSettingsURL = () => `/serverSettings`; static fetchHomeData(request: HomeDataPayload): AxiosPromise { return Api.get(ApplicationApi.fetchHomeDataURL, request); @@ -240,6 +241,10 @@ class ApplicationApi extends Api { editingFinished, }); } + + static fetchServerSettings(): AxiosPromise { + return Api.get(ApplicationApi.serverSettingsURL()); + } } export default ApplicationApi; diff --git a/client/packages/lowcoder/src/app.tsx b/client/packages/lowcoder/src/app.tsx index 8ff890129a..539844834a 100644 --- a/client/packages/lowcoder/src/app.tsx +++ b/client/packages/lowcoder/src/app.tsx @@ -56,7 +56,7 @@ import { getBrandingConfig } from "./redux/selectors/configSelectors"; import { buildMaterialPreviewURL } from "./util/materialUtils"; import GlobalInstances from 'components/GlobalInstances'; // import posthog from 'posthog-js' -import { fetchHomeData } from "./redux/reduxActions/applicationActions"; +import { fetchHomeData, fetchServerSettingsAction } from "./redux/reduxActions/applicationActions"; import { getNpmPackageMeta } from "./comps/utils/remote"; import { packageMetaReadyAction, setLowcoderCompsLoading } from "./redux/reduxActions/npmPluginActions"; @@ -95,6 +95,7 @@ type AppIndexProps = { fetchHomeData: (currentUserAnonymous?: boolean | undefined) => void; fetchLowcoderCompVersions: () => void; getCurrentUser: () => void; + fetchServerSettings: () => void; favicon: string; brandName: string; uiLanguage: string; @@ -103,6 +104,7 @@ type AppIndexProps = { class AppIndex extends React.Component { componentDidMount() { this.props.getCurrentUser(); + this.props.fetchServerSettings(); // if (!this.props.currentUserAnonymous) { // this.props.fetchHomeData(this.props.currentUserAnonymous); // } @@ -439,6 +441,9 @@ const mapDispatchToProps = (dispatch: any) => ({ dispatch(setLowcoderCompsLoading(false)); } }, + fetchServerSettings: () => { + dispatch(fetchServerSettingsAction()); + } }); const AppIndexWithProps = connect(mapStateToProps, mapDispatchToProps)(AppIndex); diff --git a/client/packages/lowcoder/src/constants/reduxActionConstants.ts b/client/packages/lowcoder/src/constants/reduxActionConstants.ts index 316103c3df..1dc12e2029 100644 --- a/client/packages/lowcoder/src/constants/reduxActionConstants.ts +++ b/client/packages/lowcoder/src/constants/reduxActionConstants.ts @@ -145,6 +145,8 @@ export const ReduxActionTypes = { FETCH_ALL_MARKETPLACE_APPS: "FETCH_ALL_MARKETPLACE_APPS", FETCH_ALL_MARKETPLACE_APPS_SUCCESS: "FETCH_ALL_MARKETPLACE_APPS_SUCCESS", SET_APP_EDITING_STATE: "SET_APP_EDITING_STATE", + FETCH_SERVER_SETTINGS: "FETCH_SERVER_SETTINGS", + FETCH_SERVER_SETTINGS_SUCCESS: "FETCH_SERVER_SETTINGS_SUCCESS", /* user profile */ SET_USER_PROFILE_SETTING_MODAL_VISIBLE: "SET_USER_PROFILE_SETTING_MODAL_VISIBLE", diff --git a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx index afc04266af..958995e74c 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx @@ -6,7 +6,7 @@ import { LoginCardTitle, StyledRouteLink, } from "pages/userAuth/authComponents"; -import React, { useContext, useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import styled from "styled-components"; import UserApi from "api/userApi"; import { useRedirectUrl } from "util/hooks"; @@ -25,8 +25,10 @@ import { AccountLoginWrapper } from "./formLoginAdmin"; import { default as Button } from "antd/es/button"; import LeftOutlined from "@ant-design/icons/LeftOutlined"; import { fetchConfigAction } from "@lowcoder-ee/redux/reduxActions/configActions"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import history from "util/history"; +import ApplicationApi from "@lowcoder-ee/api/applicationApi"; +import { getServerSettings } from "@lowcoder-ee/redux/selectors/applicationSelector"; const StyledCard = styled.div<{$selected: boolean}>` display: flex; @@ -107,6 +109,16 @@ export default function FormLoginSteps(props: FormLoginProps) { const [currentStep, setCurrentStep] = useState(CurrentStepEnum.EMAIL); const [organizationId, setOrganizationId] = useState(props.organizationId); const [skipWorkspaceStep, setSkipWorkspaceStep] = useState(false); + const [signupEnabled, setSignupEnabled] = useState(true); + const serverSettings = useSelector(getServerSettings); + + useEffect(() => { + const { LOWCODER_EMAIL_SIGNUP_ENABLED } = serverSettings; + if (!LOWCODER_EMAIL_SIGNUP_ENABLED) { + return setSignupEnabled(true); + } + setSignupEnabled(LOWCODER_EMAIL_SIGNUP_ENABLED === 'true'); + }, [serverSettings]); const { onSubmit, loading } = useAuthSubmit( () => @@ -185,15 +197,19 @@ export default function FormLoginSteps(props: FormLoginProps) { {trans("userAuth.continue")}
- - - - {trans("userAuth.register")} - - + {signupEnabled && ( + <> + + + + {trans("userAuth.register")} + + + + )} ) } @@ -266,7 +282,7 @@ export default function FormLoginSteps(props: FormLoginProps) { /> )} - {isFormLoginEnabled && ( + {isFormLoginEnabled && signupEnabled && ( <> diff --git a/client/packages/lowcoder/src/pages/userAuth/register.tsx b/client/packages/lowcoder/src/pages/userAuth/register.tsx index b7a2ecc6ef..62bd7f7c2c 100644 --- a/client/packages/lowcoder/src/pages/userAuth/register.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/register.tsx @@ -26,6 +26,8 @@ import { validateResponse } from "@lowcoder-ee/api/apiUtils"; import history from "util/history"; import LoadingOutlined from "@ant-design/icons/LoadingOutlined"; import Spin from "antd/es/spin"; +import { useSelector } from "react-redux"; +import { getServerSettings } from "@lowcoder-ee/redux/selectors/applicationSelector"; const StyledFormInput = styled(FormInput)` margin-bottom: 16px; @@ -65,6 +67,21 @@ function UserRegister() { const authId = systemConfig?.form.id; + const serverSettings = useSelector(getServerSettings); + + useEffect(() => { + const { LOWCODER_EMAIL_SIGNUP_ENABLED } = serverSettings; + if( + serverSettings.hasOwnProperty('LOWCODER_EMAIL_SIGNUP_ENABLED') + && LOWCODER_EMAIL_SIGNUP_ENABLED === 'false' + ) { + history.push( + AUTH_LOGIN_URL, + {...location.state || {}, email: account}, + ) + }; + }, [serverSettings]); + const { loading, onSubmit } = useAuthSubmit( () => UserApi.formLogin({ diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/applicationReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/applicationReducer.ts index dda424c1f5..6725028072 100644 --- a/client/packages/lowcoder/src/redux/reducers/uiReducers/applicationReducer.ts +++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/applicationReducer.ts @@ -337,6 +337,13 @@ const usersReducer = createReducer(initialState, { fetchingAppDetail: false, }, }), + [ReduxActionTypes.FETCH_SERVER_SETTINGS_SUCCESS]: ( + state: ApplicationReduxState, + action: ReduxAction> + ): ApplicationReduxState => ({ + ...state, + serverSettings: action.payload, + }), }); export interface ApplicationReduxState { @@ -348,6 +355,7 @@ export interface ApplicationReduxState { appPermissionInfo?: AppPermissionInfo; currentApplication?: ApplicationMeta; templateId?: string; + serverSettings?: Record; loadingStatus: { deletingApplication: boolean; isFetchingHomeData: boolean; // fetching app list diff --git a/client/packages/lowcoder/src/redux/reduxActions/applicationActions.ts b/client/packages/lowcoder/src/redux/reduxActions/applicationActions.ts index 6d3a0310c1..83be6cdbb1 100644 --- a/client/packages/lowcoder/src/redux/reduxActions/applicationActions.ts +++ b/client/packages/lowcoder/src/redux/reduxActions/applicationActions.ts @@ -181,3 +181,7 @@ export const setAppEditingState = (payload: SetAppEditingStatePayload) => ({ type: ReduxActionTypes.SET_APP_EDITING_STATE, payload: payload, }); + +export const fetchServerSettingsAction = () => ({ + type: ReduxActionTypes.FETCH_SERVER_SETTINGS, +}); diff --git a/client/packages/lowcoder/src/redux/sagas/applicationSagas.ts b/client/packages/lowcoder/src/redux/sagas/applicationSagas.ts index b6299d6266..a2d4247873 100644 --- a/client/packages/lowcoder/src/redux/sagas/applicationSagas.ts +++ b/client/packages/lowcoder/src/redux/sagas/applicationSagas.ts @@ -403,6 +403,23 @@ function* setAppEditingStateSaga(action: ReduxAction) } } +export function* fetchServerSettingsSaga() { + try { + const response: AxiosResponse>> = yield call( + ApplicationApi.fetchServerSettings + ); + if (Boolean(response.data)) { + yield put({ + type: ReduxActionTypes.FETCH_SERVER_SETTINGS_SUCCESS, + payload: response.data, + }); + } + } catch (error: any) { + log.debug("fetch server settings error: ", error); + messageInstance.error(error.message); + } +} + export default function* applicationSagas() { yield all([ takeLatest(ReduxActionTypes.FETCH_HOME_DATA, fetchHomeDataSaga), @@ -429,5 +446,6 @@ export default function* applicationSagas() { fetchAllMarketplaceAppsSaga, ), takeLatest(ReduxActionTypes.SET_APP_EDITING_STATE, setAppEditingStateSaga), + takeLatest(ReduxActionTypes.FETCH_SERVER_SETTINGS, fetchServerSettingsSaga), ]); } diff --git a/client/packages/lowcoder/src/redux/selectors/applicationSelector.ts b/client/packages/lowcoder/src/redux/selectors/applicationSelector.ts index 2d888a9c97..308543d5eb 100644 --- a/client/packages/lowcoder/src/redux/selectors/applicationSelector.ts +++ b/client/packages/lowcoder/src/redux/selectors/applicationSelector.ts @@ -43,3 +43,7 @@ export const isApplicationPublishing = (state: AppState): boolean => { export const getTemplateId = (state: AppState): any => { return state.ui.application.templateId; }; + +export const getServerSettings = (state: AppState): Record => { + return state.ui.application.serverSettings || {}; +}