Skip to content

Commit 69d75d6

Browse files
authored
Merge branch 'dev' into fix_org_by_email
2 parents 4f36ff4 + 2b36ad3 commit 69d75d6

File tree

16 files changed

+259
-80
lines changed

16 files changed

+259
-80
lines changed

client/packages/lowcoder/src/api/inviteApi.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type InviteInfo = {
2020
class InviteApi extends Api {
2121
static getInviteURL = "/invitation";
2222
static acceptInviteURL = (invitationId: string) => `/invitation/${invitationId}/invite`;
23+
static sendInvitationURL = `${this.getInviteURL}/email/invite`;
2324

2425
// generate invitation
2526
static getInvite(request: GetInviteRequest): AxiosPromise<GenericApiResponse<InviteInfo>> {
@@ -36,6 +37,11 @@ class InviteApi extends Api {
3637
// the same api as getInviteInfo, method is by post
3738
return Api.get(InviteApi.acceptInviteURL(request.invitationId));
3839
}
40+
41+
// send invitations
42+
static sendInvitations(request: {emails: string[], orgId: string}): AxiosPromise<GenericApiResponse<any>> {
43+
return Api.post(InviteApi.sendInvitationURL, request);
44+
}
3945
}
4046

4147
export default InviteApi;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,10 @@ export function Layout(props: LayoutProps) {
111111
placement="right"
112112
closable={true}
113113
onClose={toggleDrawer}
114-
visible={drawerVisible}
115-
bodyStyle={{ padding: "0px" }}
114+
open={drawerVisible}
115+
styles={{
116+
body: { padding: "0px" }
117+
}}
116118
destroyOnClose // Ensure drawer content is removed when closed
117119
>
118120
<DrawerContentWrapper>

client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ export function ListView(props: Props) {
369369
newData.splice(toIndex, 0, movedItem);
370370

371371
children.listData.dispatchChangeValueAction(newData);
372+
children.onEvent.getView()('sortChange');
372373
};
373374

374375
// log.debug("renders: ", renders);

client/packages/lowcoder/src/comps/comps/listViewComp/listViewComp.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
withFunction,
3030
WrapContextNodeV2,
3131
} from "lowcoder-core";
32-
import { JSONArray, JSONValue } from "util/jsonTypes";
32+
import { JSONArray, JSONObject, JSONValue } from "util/jsonTypes";
3333
import { depthEqual, lastValueIfEqual, shallowEqual } from "util/objectUtils";
3434
import { CompTree, getAllCompItems, IContainer } from "../containerBase";
3535
import { SimpleContainerComp, toSimpleContainerData } from "../containerBase/simpleContainerComp";
@@ -40,6 +40,7 @@ import { listPropertyView } from "./listViewPropertyView";
4040
import { getData } from "./listViewUtils";
4141
import { withMethodExposing } from "comps/generators/withMethodExposing";
4242
import { SliderControl } from "@lowcoder-ee/comps/controls/sliderControl";
43+
import { eventHandlerControl, sortChangeEvent } from "@lowcoder-ee/comps/controls/eventHandlerControl";
4344

4445
const childrenMap = {
4546
noOfRows: withIsLoadingMethod(NumberOrJSONObjectArrayControl), // FIXME: migrate "noOfRows" to "data"
@@ -62,6 +63,7 @@ const childrenMap = {
6263
horizontal: withDefault(BoolControl, false),
6364
minHorizontalWidth: withDefault(RadiusControl, '100px'),
6465
enableSorting: withDefault(BoolControl, false),
66+
onEvent: eventHandlerControl([sortChangeEvent] as const),
6567
};
6668

6769
const ListViewTmpComp = new UICompBuilder(childrenMap, () => <></>)
@@ -183,6 +185,15 @@ ListViewPropertyComp = withExposingConfigs(ListViewPropertyComp, [
183185
return data;
184186
},
185187
}),
188+
depsConfig({
189+
name: "sortedData",
190+
desc: trans("listView.dataDesc"),
191+
depKeys: ["listData"],
192+
func: (input) => {
193+
const { data } = getData(input.listData as JSONObject[]);
194+
return data;
195+
},
196+
}),
186197
new CompDepsConfig(
187198
"pageNo",
188199
(comp) => ({index: comp.children.pagination.children.pageNo.exposingNode() }),

client/packages/lowcoder/src/comps/comps/listViewComp/listViewPropertyView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export function listPropertyView(compType: ListCompType) {
5555

5656
{(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && (
5757
<Section name={sectionNames.interaction}>
58+
{children.onEvent.getPropertyView()}
5859
{hiddenPropertyView(children)}
5960
{showDataLoadingIndicatorsPropertyView(children)}
6061
{children.enableSorting.propertyView({

client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,11 @@ export const resetEvent: EventConfigType = {
489489
value: "reset",
490490
description: trans("event.resetDesc"),
491491
};
492-
492+
export const sortChangeEvent: EventConfigType = {
493+
label: trans("event.sortChange"),
494+
value: "sortChange",
495+
description: trans("event.sortChangeDesc"),
496+
};
493497

494498
// Meeting Events
495499

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,8 @@ export const en = {
463463
"resetDesc": "Triggers on Reset timer",
464464
"refresh": "Refresh",
465465
"refreshDesc": "Triggers on Refresh",
466+
"sortChange": "Sort Change",
467+
"sortChangeDesc": "Triggers on Sorting Changes",
466468
},
467469

468470

@@ -2484,6 +2486,12 @@ export const en = {
24842486
"inviteUserLabel": "Invitation Link:",
24852487
"inviteCopyLink": "Copy Link",
24862488
"inviteText": "{userName} Invites You to Join the Workspace \"{organization}\", Click on the Link to Join: {inviteLink}",
2489+
"inviteByEmailHelp": "You can enter one or more email addresses to send invitation links",
2490+
"inviteByEmailLabel": "Enter emails:",
2491+
"inviteByEmailButton": "Send Invitations",
2492+
"inviteByEmailSuccess": "Invitations sent successfully!",
2493+
"inviteByEmailError": "Something went wrong while sending invitations. Please try again.",
2494+
"noValidEmails": "No valid emails found",
24872495
"groupName": "Group Name",
24882496
"createTime": "Create Time",
24892497
"manageBtn": "Manage",
@@ -3079,6 +3087,7 @@ export const en = {
30793087
"memberOfOrgs": "Workspaces Membership",
30803088
"apiKeys": "API Keys",
30813089
"createApiKey": "Create API Key",
3090+
"apiKeyInfo": "Make sure to copy your new API key now. You won't be able to see it again.",
30823091
"apiKeyName": "Name",
30833092
"apiKeyDescription": "Description",
30843093
"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/common/inviteDialog.tsx

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import { HelpText } from "components/HelpText";
1212
import copyToClipboard from "copy-to-clipboard";
1313
import { trans } from "i18n";
1414
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
15+
import Divider from "antd/es/divider";
16+
import Flex from "antd/es/flex";
17+
import Select from "antd/es/select";
1518

1619
const InviteButton = styled(TacoButton)`
1720
width: 76px;
@@ -23,14 +26,36 @@ const StyledLoading = styled(WhiteLoading)`
2326
height: 170px;
2427
`;
2528

26-
function InviteContent(props: { inviteInfo: InviteInfo }) {
27-
const { inviteInfo } = props;
29+
function InviteContent(props: { inviteInfo: InviteInfo, onClose?: () => void }) {
30+
const { inviteInfo, onClose } = props;
2831
const inviteLink = genInviteLink(inviteInfo?.inviteCode);
2932
const inviteText = trans("memberSettings.inviteText", {
3033
userName: inviteInfo.createUserName,
3134
organization: inviteInfo.invitedOrganizationName,
3235
inviteLink,
3336
});
37+
const [emails, setEmails] = useState<string[]>([]);
38+
39+
const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
40+
41+
const sendInvitations = async () => {
42+
const filteredEmails = emails.filter(isValidEmail);
43+
if (!filteredEmails.length) {
44+
return messageInstance.error(trans("memberSettings.noValidEmails"));
45+
}
46+
try {
47+
const resp = await InviteApi.sendInvitations({emails: filteredEmails, orgId: inviteInfo.invitedOrganizationId})
48+
if (validateResponse(resp) && resp.data.success) {
49+
messageInstance.success(trans('membersSettings.inviteByEmailSuccess'));
50+
onClose?.();
51+
return;
52+
}
53+
throw new Error(trans('membersSettings.inviteByEmailError'));
54+
} catch(e: any) {
55+
messageInstance.error(e.message);
56+
}
57+
}
58+
3459
return (
3560
<>
3661
<HelpText style={{ marginBottom: 16 }}>{trans("memberSettings.inviteUserHelp")}</HelpText>
@@ -48,6 +73,34 @@ function InviteContent(props: { inviteInfo: InviteInfo }) {
4873
{trans("memberSettings.inviteCopyLink")}
4974
</InviteButton>
5075
</div>
76+
<Divider style={{marginTop: '60px'}}/>
77+
<HelpText style={{ marginBottom: 16 }}>{trans("memberSettings.inviteByEmailHelp")}</HelpText>
78+
<CommonTextLabel>{trans("memberSettings.inviteByEmailLabel")}</CommonTextLabel>
79+
<Select
80+
mode="tags"
81+
allowClear
82+
open={false}
83+
style={{ width: '100%', marginTop: '8px', marginBottom: '8px' }}
84+
placeholder="Enter emails"
85+
defaultValue={[]}
86+
onChange={(value) => {
87+
setEmails(value);
88+
}}
89+
options={[]}
90+
showSearch={false}
91+
suffixIcon={''}
92+
/>
93+
<Flex justify="end">
94+
<TacoButton
95+
buttonType="primary"
96+
onClick={() => {
97+
sendInvitations();
98+
}}
99+
disabled={!Boolean(emails?.length)}
100+
>
101+
{trans("memberSettings.inviteByEmailButton")}
102+
</TacoButton>
103+
</Flex>
51104
</>
52105
);
53106
}
@@ -101,7 +154,7 @@ function InviteDialog(props: {
101154
showCancelButton={false}
102155
width="440px"
103156
>
104-
{!inviteInfo ? <StyledLoading size={20} /> : <InviteContent inviteInfo={inviteInfo} />}
157+
{!inviteInfo ? <StyledLoading size={20} /> : <InviteContent inviteInfo={inviteInfo} onClose={() => setInviteDialogVisible(false)} />}
105158
</CustomModal>
106159
</>
107160
);

client/packages/lowcoder/src/pages/common/inviteLanding.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function InviteLanding(props: InviteLandingProp) {
4545
orgId = inviteInfo.invitedOrganizationId;
4646
const inviteState = inviteInfo ? { ...inviteInfo, invitationId } : { invitationId };
4747
history.push({
48-
pathname: AUTH_LOGIN_URL,
48+
pathname: `/org/${orgId}/auth/login`,
4949
state: {
5050
inviteInfo: inviteState,
5151
},

client/packages/lowcoder/src/pages/userAuth/formLoginAdmin.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ export default function FormLogin(props: FormLoginProps) {
3131
const [password, setPassword] = useState("");
3232
const { fetchUserAfterAuthSuccess } = useContext(AuthContext);
3333

34+
const afterLoginSuccess = () => {
35+
if (props.organizationId) {
36+
localStorage.setItem("lowcoder_login_orgId", props.organizationId);
37+
}
38+
fetchUserAfterAuthSuccess?.();
39+
}
40+
3441
const { onSubmit, loading } = useAuthSubmit(
3542
() =>
3643
UserApi.formLogin({
@@ -42,7 +49,7 @@ export default function FormLogin(props: FormLoginProps) {
4249
}),
4350
false,
4451
null,
45-
fetchUserAfterAuthSuccess,
52+
afterLoginSuccess,
4653
);
4754

4855
return (

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy