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 125840011..f0208e52f 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 c3d66e557..6e7c532e4 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 e43e3b94f..2d0ab4f42 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 4b2d89524..1f9b0f374 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 8e76f37a0..cdaa46f82 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 61450a61f..6b9becf13 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 cdaa46f82..7ece4e31f 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 7ece4e31f..4ae2878ba 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 88e6cadd7..16c070a2e 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 564aa4f60..daf1cbf31 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 98e6141d7..00f2b7fcf 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 6b9becf13..0bb42fb5f 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 ec0a33257..c0af7c3f7 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 b5e1fdb9e..0f3f32444 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 4843d0492..fa235a9e6 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 e05a87a27..7cdc3e4fa 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 4ae2878ba..46becb141 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 f6cbdac58..8ff890129 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 46becb141..3e7c56143 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 7d28cd551..40e7a1bc1 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 be6f68136..bad534909 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 000000000..f91663128 --- /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 c0af7c3f7..aa05b4e3f 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 0f3f32444..20619731b 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 7cdc3e4fa..091aae36e 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 0bb42fb5f..d30bd72d4 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 3e7c56143..f340935f7 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 daf1cbf31..189afe573 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 f0208e52f..e7ce12111 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 f340935f7..f3f0d3031 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 16c070a2e..cfae0f093 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 f3f0d3031..afc04266a 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 cfae0f093..b7a2ecc6e 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 d38ee1843..a0edb7424 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 8ff890129..539844834 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 316103c3d..1dc12e202 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 afc04266a..958995e74 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 b7a2ecc6e..62bd7f7c2 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 dda424c1f..672502807 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 6d3a0310c..83be6cdbb 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 b6299d626..a2d424787 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 2d888a9c9..308543d5e 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 || {}; +}