Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1ec6c93

Browse files
authoredApr 21, 2025··
Merge branch 'dev' into ee-setup
2 parents 9a23b4a + c8c288e commit 1ec6c93

File tree

10 files changed

+177
-85
lines changed

10 files changed

+177
-85
lines changed
 

‎client/packages/lowcoder/src/app.tsx

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import React from "react";
3535
import { createRoot } from "react-dom/client";
3636
import { Helmet } from "react-helmet";
3737
import { connect, Provider } from "react-redux";
38-
import { Redirect, Router, Switch } from "react-router-dom";
38+
import { Redirect, Route, Router, Switch } from "react-router-dom";
3939
import type { AppState } from "redux/reducers";
4040
import { fetchConfigAction } from "redux/reduxActions/configActions";
4141
import { fetchUserAction } from "redux/reduxActions/userActions";
@@ -315,15 +315,10 @@ class AppIndex extends React.Component<AppIndexProps, any> {
315315
component={LazyPublicAppEditor}
316316
/>
317317

318-
<SimpleSubscriptionContextProvider>
319-
<LazyRoute
320-
fallback="layout"
321-
path={APP_EDITOR_URL}
322-
component={LazyAppEditor}
323-
/>
324-
<LazyRoute
325-
fallback="layout"
326-
path={[
318+
<Route
319+
path={
320+
[
321+
APP_EDITOR_URL,
327322
USER_PROFILE_URL,
328323
NEWS_URL,
329324
ORG_HOME_URL,
@@ -339,11 +334,41 @@ class AppIndex extends React.Component<AppIndexProps, any> {
339334
SETTING_URL,
340335
MARKETPLACE_URL,
341336
ADMIN_APP_URL
342-
]}
343-
// component={ApplicationListPage}
344-
component={LazyApplicationHome}
345-
/>
346-
</SimpleSubscriptionContextProvider>
337+
]
338+
}
339+
>
340+
<SimpleSubscriptionContextProvider>
341+
<Switch>
342+
<LazyRoute
343+
fallback="layout"
344+
path={APP_EDITOR_URL}
345+
component={LazyAppEditor}
346+
/>
347+
<LazyRoute
348+
fallback="layout"
349+
path={[
350+
USER_PROFILE_URL,
351+
NEWS_URL,
352+
ORG_HOME_URL,
353+
ALL_APPLICATIONS_URL,
354+
DATASOURCE_CREATE_URL,
355+
DATASOURCE_EDIT_URL,
356+
DATASOURCE_URL,
357+
SUPPORT_URL,
358+
QUERY_LIBRARY_URL,
359+
FOLDERS_URL,
360+
FOLDER_URL,
361+
TRASH_URL,
362+
SETTING_URL,
363+
MARKETPLACE_URL,
364+
ADMIN_APP_URL
365+
]}
366+
// component={ApplicationListPage}
367+
component={LazyApplicationHome}
368+
/>
369+
</Switch>
370+
</SimpleSubscriptionContextProvider>
371+
</Route>
347372
<LazyRoute exact path={ADMIN_AUTH_URL} component={LazyUserAuthComp} />
348373
<LazyRoute path={USER_AUTH_URL} component={LazyUserAuthComp} />
349374
<LazyRoute

‎client/packages/lowcoder/src/components/layout/Layout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,10 @@ export function Layout(props: LayoutProps) {
117117
placement="right"
118118
closable={true}
119119
onClose={toggleDrawer}
120-
visible={drawerVisible}
121-
bodyStyle={{ padding: "0px" }}
120+
open={drawerVisible}
121+
styles={{
122+
body: { padding: "0px" }
123+
}}
122124
destroyOnClose // Ensure drawer content is removed when closed
123125
>
124126
<DrawerContentWrapper>

‎client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3181,6 +3181,7 @@ export const en = {
31813181
"memberOfOrgs": "Workspaces Membership",
31823182
"apiKeys": "API Keys",
31833183
"createApiKey": "Create API Key",
3184+
"apiKeyInfo": "Make sure to copy your new API key now. You won't be able to see it again.",
31843185
"apiKeyName": "Name",
31853186
"apiKeyDescription": "Description",
31863187
"apiKeyCopy": "Click the Api Key to get the value in your clipboard",

‎client/packages/lowcoder/src/pages/ApplicationV2/components/CreateApiKeyModal.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { validateResponse } from "api/apiUtils";
1313
import _ from "lodash";
1414
import { styled } from "styled-components";
1515
import UserApi, { ApiKeyPayload } from "api/userApi";
16+
import { ApiKeyType } from "./UserApiKeysCard";
1617

1718
const CustomModalStyled = styled(CustomModal)`
1819
button {
@@ -90,7 +91,7 @@ const FormStyled = styled(Form)`
9091
type CreateApiKeyModalProps = {
9192
modalVisible: boolean;
9293
closeModal: () => void;
93-
onConfigCreate: () => void;
94+
onConfigCreate: (apiKey?: ApiKeyType) => void;
9495
};
9596

9697
function CreateApiKeyModal(props: CreateApiKeyModalProps) {
@@ -101,6 +102,7 @@ function CreateApiKeyModal(props: CreateApiKeyModalProps) {
101102
} = props;
102103
const [form] = Form.useForm();
103104
const [saveLoading, setSaveLoading] = useState(false);
105+
const [apiKey, setApiKey] = useState<{id: string, token: string}>();
104106

105107
const handleOk = () => {
106108
form.validateFields().then(values => {
@@ -115,12 +117,15 @@ function CreateApiKeyModal(props: CreateApiKeyModalProps) {
115117
.then((resp) => {
116118
if (validateResponse(resp)) {
117119
messageInstance.success(trans("idSource.saveSuccess"));
120+
onConfigCreate(resp.data.data);
118121
}
119122
})
120-
.catch((e) => messageInstance.error(e.message))
123+
.catch((e) => {
124+
messageInstance.error(e.message);
125+
onConfigCreate();
126+
})
121127
.finally(() => {
122128
setSaveLoading(false);
123-
onConfigCreate();
124129
});
125130
}
126131

‎client/packages/lowcoder/src/pages/ApplicationV2/components/UserApiKeysCard.tsx

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import CreateApiKeyModal from "./CreateApiKeyModal";
1414
import { fetchApiKeysAction } from "redux/reduxActions/userActions";
1515
import UserApi from "@lowcoder-ee/api/userApi";
1616
import { validateResponse } from "@lowcoder-ee/api/apiUtils";
17+
import Alert from "antd/es/alert";
18+
import { CopyOutlined } from "@ant-design/icons";
1719

1820
const TableStyled = styled(Table)`
1921
.ant-table-tbody > tr > td {
@@ -37,10 +39,16 @@ const CreateButton = styled(TacoButton)`
3739
box-shadow: none;
3840
`;
3941

42+
export type ApiKeyType = {
43+
id: string;
44+
token: string;
45+
}
46+
4047
export default function UserApiKeysCard() {
4148
const dispatch = useDispatch();
4249
const apiKeys = useSelector(getApiKeys);
4350
const [modalVisible, setModalVisible] = useState(false);
51+
const [newApiKey, setNewApiKey] = useState<ApiKeyType>();
4452

4553
const handleCopy = (value: string) => {
4654
navigator.clipboard.writeText(value).then(() => {
@@ -66,13 +74,11 @@ export default function UserApiKeysCard() {
6674
{trans("profile.createApiKey")}
6775
</CreateButton>
6876
</Flex>
77+
{Boolean(newApiKey) && <Alert message={trans("profile.apiKeyInfo")} type="info" style={{marginBottom: '16px'}}/>}
6978
<TableStyled
7079
tableLayout={"auto"}
7180
scroll={{ x: "100%" }}
7281
pagination={false}
73-
onRow={(record) => ({
74-
75-
})}
7682
columns={[
7783
{
7884
title: trans("profile.apiKeyName"),
@@ -95,16 +101,19 @@ export default function UserApiKeysCard() {
95101
title: trans("profile.apiKey"),
96102
dataIndex: "token",
97103
ellipsis: true,
98-
render: (value: string) => {
99-
const startToken = value.substring(0, 6);
100-
const endToken = value.substring(value.length - 6);
101-
return (
102-
<Tooltip placement="topLeft" title={ trans("profile.apiKeyCopy")}>
103-
<div onClick={() => handleCopy(value)} style={{ cursor: 'pointer' }}>
104-
{`${startToken}********************${endToken}`}
105-
</div>
106-
</Tooltip>
107-
)
104+
render: (value: string, record: any) => {
105+
if (newApiKey?.id === record.id) {
106+
return (
107+
<Tooltip placement="topLeft" title={ trans("profile.apiKeyCopy")}>
108+
<div onClick={() => handleCopy(newApiKey?.token!)} style={{ cursor: 'pointer' }}>
109+
{value}
110+
&nbsp;
111+
<CopyOutlined />
112+
</div>
113+
</Tooltip>
114+
)
115+
}
116+
return <div>{value}</div>
108117
}
109118
},
110119
{ title: " ", dataIndex: "operation", width: "208px" },
@@ -145,8 +154,9 @@ export default function UserApiKeysCard() {
145154
<CreateApiKeyModal
146155
modalVisible={modalVisible}
147156
closeModal={() => setModalVisible(false)}
148-
onConfigCreate={() => {
157+
onConfigCreate={(apiKey?: ApiKeyType) => {
149158
setModalVisible(false);
159+
setNewApiKey(apiKey);
150160
dispatch(fetchApiKeysAction());
151161
}}
152162
/>

‎client/packages/lowcoder/src/pages/ApplicationV2/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,15 @@ export default function ApplicationHome() {
104104
if (user.currentOrgId) {
105105
dispatch(fetchDeploymentIdAction());
106106
}
107-
dispatch(fetchHomeData({}));
108107
}, [user.currentOrgId]);
109108

109+
useEffect(() => {
110+
// tricky check, will be called for anonymous user to redirect to login page
111+
if (user.isAnonymous) {
112+
dispatch(fetchHomeData({}));
113+
}
114+
}, [user.isAnonymous])
115+
110116
useEffect(() => {
111117
if(Boolean(deploymentId)) {
112118
dispatch(fetchSubscriptionsAction())

‎client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ export default function FormLoginSteps(props: FormLoginProps) {
204204
}
205205
if (resp.data.length === 1) {
206206
// in Enterprise mode, we will get org data in different format
207-
const selectedOrgId = isEnterpriseMode ? resp.data[0].id : resp.data[0].orgId;
207+
const selectedOrgId = resp.data[0]?.id || resp.data[0]?.orgId;
208208
setOrganizationId(selectedOrgId);
209209
dispatch(fetchConfigAction(selectedOrgId));
210210
setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS);
@@ -239,7 +239,7 @@ export default function FormLoginSteps(props: FormLoginProps) {
239239
invitedOrganizationId={organizationId}
240240
authGoal="login"
241241
/>
242-
{signupEnabled && (
242+
{(isFormLoginEnabled && signupEnabled) && (
243243
<>
244244
<Divider/>
245245
<AuthBottomView>

‎client/packages/lowcoder/src/pages/userAuth/register.tsx

Lines changed: 78 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ import { validateResponse } from "@lowcoder-ee/api/apiUtils";
2626
import history from "util/history";
2727
import LoadingOutlined from "@ant-design/icons/LoadingOutlined";
2828
import Spin from "antd/es/spin";
29-
import { useSelector } from "react-redux";
29+
import { useDispatch, useSelector } from "react-redux";
3030
import { getServerSettings } from "@lowcoder-ee/redux/selectors/applicationSelector";
3131
import { useEnterpriseContext } from "@lowcoder-ee/util/context/EnterpriseContext";
32+
import { fetchConfigAction } from "@lowcoder-ee/redux/reduxActions/configActions";
33+
import { fetchOrgPaginationByEmail } from "@lowcoder-ee/util/pagination/axios";
3234

3335
const StyledFormInput = styled(FormInput)`
3436
margin-bottom: 16px;
@@ -46,6 +48,7 @@ const RegisterContent = styled(FormWrapperMobile)`
4648

4749
function UserRegister() {
4850
const location = useLocation();
51+
const dispatch = useDispatch();
4952
const [submitBtnDisable, setSubmitBtnDisable] = useState(false);
5053
const [account, setAccount] = useState(() => {
5154
const { email } = (location.state || {}) as any;
@@ -54,27 +57,53 @@ function UserRegister() {
5457
const [password, setPassword] = useState("");
5558
const [orgLoading, setOrgLoading] = useState(false);
5659
const [lastEmailChecked, setLastEmailChecked] = useState("");
60+
const [signupEnabled, setSignupEnabled] = useState<boolean>(true);
61+
const [signinEnabled, setSigninEnabled] = useState<boolean>(true);
5762
const redirectUrl = useRedirectUrl();
63+
const serverSettings = useSelector(getServerSettings);
5864
const { systemConfig, inviteInfo, fetchUserAfterAuthSuccess } = useContext(AuthContext);
5965
const { isEnterpriseActive } = useEnterpriseContext();
6066
const invitationId = inviteInfo?.invitationId;
61-
67+
const isFormLoginEnabled = systemConfig?.form.enableLogin;
68+
const authId = systemConfig?.form.id;
6269
const orgId = useParams<any>().orgId;
70+
6371
const organizationId = useMemo(() => {
6472
if(inviteInfo?.invitedOrganizationId) {
6573
return inviteInfo?.invitedOrganizationId;
6674
}
6775
return orgId;
6876
}, [ inviteInfo, orgId ]);
6977

70-
const authId = systemConfig?.form.id;
71-
72-
const serverSettings = useSelector(getServerSettings);
78+
const isEmailLoginEnabled = useMemo(() => {
79+
return isFormLoginEnabled && signinEnabled;
80+
}, [isFormLoginEnabled, signinEnabled]);
7381

7482
const isEnterpriseMode = useMemo(() => {
7583
return serverSettings?.LOWCODER_WORKSPACE_MODE === "ENTERPRISE" || serverSettings?.LOWCODER_WORKSPACE_MODE === "SINGLEWORKSPACE";
7684
}, [serverSettings]);
7785

86+
useEffect(() => {
87+
if (isEnterpriseMode) {
88+
// dispatch(fetchConfigAction());
89+
fetchOrgPaginationByEmail({
90+
email: ' ',
91+
pageNum: 1,
92+
pageSize: 10,
93+
})
94+
.then((resp) => {
95+
if (resp.success) {
96+
const orgList = resp.data || [];
97+
if (orgList.length) {
98+
// in Enterprise mode, we will get org data in different format
99+
const selectedOrgId = orgList[0]?.id || orgList[0]?.orgId;
100+
dispatch(fetchConfigAction(selectedOrgId));
101+
}
102+
}
103+
})
104+
}
105+
}, [isEnterpriseMode]);
106+
78107
useEffect(() => {
79108
const { LOWCODER_EMAIL_SIGNUP_ENABLED } = serverSettings;
80109
if(
@@ -146,34 +175,38 @@ function UserRegister() {
146175
isEE={isEnterpriseActive}
147176
>
148177
<RegisterContent>
149-
<StyledFormInput
150-
className="form-input"
151-
label={trans("userAuth.email")}
152-
defaultValue={account}
153-
onChange={(value, valid) => setAccount(valid ? value : "")}
154-
onBlur={checkEmailExist}
155-
placeholder={trans("userAuth.inputEmail")}
156-
checkRule={{
157-
check: checkEmailValid,
158-
errorMsg: trans("userAuth.inputValidEmail"),
159-
}}
160-
/>
161-
<StyledPasswordInput
162-
className="form-input"
163-
passInputConf={{label:trans("password.label"), placeholder: trans("password.placeholder")}}
164-
confirmPassConf={{label:trans("password.conformLabel"), placeholder: trans("password.conformPlaceholder")}}
165-
valueCheck={checkPassWithMsg}
166-
onChange={(value, valid) => setPassword(valid ? value : "")}
167-
doubleCheck
168-
/>
169-
<ConfirmButton
170-
disabled={!account || !password || submitBtnDisable}
171-
onClick={onSubmit}
172-
loading={loading}
173-
>
174-
{trans("userAuth.register")}
175-
</ConfirmButton>
176-
<TermsAndPrivacyInfo onCheckChange={(e) => setSubmitBtnDisable(!e.target.checked)} />
178+
{ isFormLoginEnabled && (
179+
<>
180+
<StyledFormInput
181+
className="form-input"
182+
label={trans("userAuth.email")}
183+
defaultValue={account}
184+
onChange={(value, valid) => setAccount(valid ? value : "")}
185+
onBlur={checkEmailExist}
186+
placeholder={trans("userAuth.inputEmail")}
187+
checkRule={{
188+
check: checkEmailValid,
189+
errorMsg: trans("userAuth.inputValidEmail"),
190+
}}
191+
/>
192+
<StyledPasswordInput
193+
className="form-input"
194+
passInputConf={{label:trans("password.label"), placeholder: trans("password.placeholder")}}
195+
confirmPassConf={{label:trans("password.conformLabel"), placeholder: trans("password.conformPlaceholder")}}
196+
valueCheck={checkPassWithMsg}
197+
onChange={(value, valid) => setPassword(valid ? value : "")}
198+
doubleCheck
199+
/>
200+
<ConfirmButton
201+
disabled={!account || !password || submitBtnDisable}
202+
onClick={onSubmit}
203+
loading={loading}
204+
>
205+
{trans("userAuth.register")}
206+
</ConfirmButton>
207+
<TermsAndPrivacyInfo onCheckChange={(e) => setSubmitBtnDisable(!e.target.checked)} />
208+
</>
209+
)}
177210
{(organizationId || isEnterpriseMode) && (
178211
<ThirdPartyAuth
179212
invitationId={invitationId}
@@ -182,14 +215,18 @@ function UserRegister() {
182215
/>
183216
)}
184217
</RegisterContent>
185-
<Divider/>
186-
<StyledRouteLinkLogin to={{
187-
pathname: orgId
188-
? ORG_AUTH_LOGIN_URL.replace(':orgId', orgId)
189-
: AUTH_LOGIN_URL,
190-
state: location.state
191-
}}>{trans("userAuth.userLogin")}
192-
</StyledRouteLinkLogin>
218+
{isFormLoginEnabled && (
219+
<>
220+
<Divider/>
221+
<StyledRouteLinkLogin to={{
222+
pathname: orgId
223+
? ORG_AUTH_LOGIN_URL.replace(':orgId', orgId)
224+
: AUTH_LOGIN_URL,
225+
state: location.state
226+
}}>{trans("userAuth.userLogin")}
227+
</StyledRouteLinkLogin>
228+
</>
229+
)}
193230
</AuthContainer>
194231
</Spin>
195232
);

‎client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import {
55
} from "constants/authConstants";
66
import { WhiteLoading } from "lowcoder-design";
77
import history from "util/history";
8-
import { LoginLogoStyle, LoginLabelStyle, StyledLoginButton } from "pages/userAuth/authComponents";
8+
import { LoginLogoStyle, LoginLabelStyle, StyledLoginButton, TermsAndPrivacyInfo } from "pages/userAuth/authComponents";
99
import { useSelector } from "react-redux";
1010
import { getSystemConfigFetching, selectSystemConfig } from "redux/selectors/configSelectors";
11-
import React, { useMemo } from "react";
11+
import React, { useMemo, useState } from "react";
1212
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
1313
import styled from "styled-components";
1414
import { trans } from "i18n";
@@ -40,6 +40,7 @@ function ThirdPartyLoginButton(props: {
4040
autoJump?: boolean;
4141
authGoal: ThirdPartyAuthGoal;
4242
label: string;
43+
disabled?: boolean;
4344
}) {
4445
const { config, label } = props;
4546
const loginRedirectUrl = useRedirectUrl();
@@ -93,7 +94,7 @@ function ThirdPartyLoginButton(props: {
9394
: `Sign in with ${label}`;
9495

9596
return (
96-
<StyledLoginButton buttonType="normal" onClick={onLoginClick}>
97+
<StyledLoginButton buttonType="normal" onClick={onLoginClick} disabled={props.disabled}>
9798
{config.icon && <MultiIconDisplay identifier={config.icon} width="20px" height="20px" style={{ marginRight: "20px", flexShrink: 0, color: "#000" }} />}
9899
{!config.icon && <LoginLogoStyle alt={config.name} src={config.logo} title={config.name} />}
99100
<LoginLabelStyle className="auth-label">
@@ -114,6 +115,7 @@ export function ThirdPartyAuth(props: {
114115
const systemConfig = useSelector(selectSystemConfig);
115116
const serverSettings = useSelector(getServerSettings);
116117
const isFormLoginEnabled = systemConfig?.form.enableLogin;
118+
const [disableButtons, setDisableButtons] = useState(false);
117119

118120
const isEmailLoginEnabled = useMemo(() => {
119121
return isFormLoginEnabled && serverSettings.LOWCODER_EMAIL_AUTH_ENABLED === 'true';
@@ -145,20 +147,24 @@ export function ThirdPartyAuth(props: {
145147
invitationId={props.invitationId}
146148
invitedOrganizationId={props.invitedOrganizationId}
147149
label={props.labelFormatter ? props.labelFormatter(config.name) : config.name}
150+
disabled={disableButtons}
148151
/>
149152
);
150153
});
151154
return (
152155
<ThirdPartyLoginButtonWrapper>
153156
{ (
154-
(isEmailLoginEnabled && props.authGoal === 'login')
155-
|| (isEmailSignupEnabled && props.authGoal === 'register')
157+
(isFormLoginEnabled && props.authGoal === 'login')
158+
|| (isFormLoginEnabled && isEmailSignupEnabled && props.authGoal === 'register')
156159
) && Boolean(socialLoginButtons.length) && (
157160
<Divider plain>
158161
<Text type="secondary">or</Text>
159162
</Divider>
160163
)}
161164
{socialLoginButtons}
165+
{!isFormLoginEnabled && (
166+
<TermsAndPrivacyInfo onCheckChange={(e) => setDisableButtons(!e.target.checked)} />
167+
)}
162168
</ThirdPartyLoginButtonWrapper>
163169
);
164170
}

‎client/packages/lowcoder/src/util/context/SimpleSubscriptionContext.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ export const SimpleSubscriptionContextProvider = (props: {
9595
}
9696
};
9797

98-
if (!productsLoaded && !subscriptionProductsLoading) {
98+
if (!productsLoaded && !subscriptionProductsLoading && !user.isAnonymous) {
9999
console.log("Outer context: Fetching products...");
100100
setSubscriptionProductsLoading(true);
101101
fetchProducts();
102102
}
103-
}, [productsLoaded, subscriptionProductsLoading]);
103+
}, [user.isAnonymous, productsLoaded, subscriptionProductsLoading]);
104104

105105
useEffect(() => {
106106
const initializeCustomer = async () => {

0 commit comments

Comments
 (0)
Please sign in to comment.