Skip to content

Commit b88d332

Browse files
show complete api key when created to copy by hand
1 parent 0f7c5f3 commit b88d332

File tree

1 file changed

+118
-92
lines changed

1 file changed

+118
-92
lines changed

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

Lines changed: 118 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { getApiKeys } from "redux/selectors/usersSelectors";
22
import Card from "antd/es/card";
33
import Flex from "antd/es/flex";
44
import Title from "antd/es/typography/Title";
5-
import Table from "antd/es/table";
5+
import Table, { ColumnsType } from "antd/es/table";
66
import Tooltip from "antd/es/tooltip";
77
import { useDispatch, useSelector } from "react-redux";
8-
import { useState } from "react";
8+
import { useState, useCallback, useMemo } from "react";
99
import { styled } from "styled-components";
1010
import { AddIcon, CustomModal, EditPopover, TacoButton, messageInstance } from "lowcoder-design";
1111
import { trans } from "i18n";
@@ -21,7 +21,7 @@ const TableStyled = styled(Table)`
2121
.ant-table-tbody > tr > td {
2222
padding: 11px 12px;
2323
}
24-
`;
24+
` as typeof Table;
2525

2626
const OperationWrapper = styled.div`
2727
display: flex;
@@ -42,6 +42,8 @@ const CreateButton = styled(TacoButton)`
4242
export type ApiKeyType = {
4343
id: string;
4444
token: string;
45+
name: string;
46+
description?: string;
4547
}
4648

4749
export default function UserApiKeysCard() {
@@ -50,116 +52,140 @@ export default function UserApiKeysCard() {
5052
const [modalVisible, setModalVisible] = useState(false);
5153
const [newApiKey, setNewApiKey] = useState<ApiKeyType>();
5254

53-
const handleCopy = (value: string) => {
54-
navigator.clipboard.writeText(value).then(() => {
55-
messageInstance.success('Copied to clipboard!');
56-
}).catch(err => {
57-
messageInstance.error('Failed to copy!');
55+
const handleCopy = useCallback((value: string) => {
56+
navigator.clipboard.writeText(value)
57+
.then(() => messageInstance.success('Copied to clipboard!'))
58+
.catch(() => messageInstance.error('Failed to copy!'));
59+
}, []);
60+
61+
const handleDeleteApiKey = useCallback((apiKeyId: string) => {
62+
CustomModal.confirm({
63+
title: trans("profile.deleteApiKey"),
64+
content: trans("profile.deleteApiKeyContent"),
65+
onConfirm: () => {
66+
UserApi.deleteApiKey(apiKeyId)
67+
.then(resp => {
68+
if(validateResponse(resp)) {
69+
dispatch(fetchApiKeysAction());
70+
}
71+
})
72+
.catch(() => {
73+
messageInstance.error(trans("profile.deleteApiKeyError"));
74+
});
75+
},
76+
confirmBtnType: "delete",
77+
okText: trans("delete"),
5878
});
59-
};
79+
}, [dispatch]);
80+
81+
const handleModalClose = useCallback(() => {
82+
setModalVisible(false);
83+
}, []);
84+
85+
const handleConfigCreate = useCallback((apiKey?: ApiKeyType) => {
86+
setModalVisible(false);
87+
setNewApiKey(apiKey);
88+
dispatch(fetchApiKeysAction());
89+
}, [dispatch]);
90+
91+
const columns: ColumnsType<ApiKeyType> = useMemo(() => [
92+
{
93+
title: trans("profile.apiKeyName"),
94+
dataIndex: "name",
95+
ellipsis: true,
96+
},
97+
{
98+
title: trans("profile.apiKeyDescription"),
99+
dataIndex: "description",
100+
width: 400,
101+
render: (value: string) => value || '-',
102+
},
103+
{
104+
title: trans("profile.apiKey"),
105+
dataIndex: "token",
106+
width: 500,
107+
render: (value: string, record: ApiKeyType) => {
108+
if (newApiKey?.id === record.id) {
109+
return (
110+
<Tooltip placement="topLeft" title={trans("profile.apiKeyCopy")}>
111+
<div
112+
onClick={() => handleCopy(newApiKey.token)}
113+
style={{ cursor: 'pointer', width: '500px' }}
114+
>
115+
{newApiKey.token}
116+
&nbsp;
117+
<CopyOutlined />
118+
</div>
119+
</Tooltip>
120+
);
121+
}
122+
return <div>{value}</div>;
123+
}
124+
},
125+
{
126+
title: " ",
127+
dataIndex: "operation",
128+
width: "208px",
129+
render: (_: unknown, record: ApiKeyType) => (
130+
<OperationWrapper>
131+
<EditPopover
132+
del={() => handleDeleteApiKey(record.id)}
133+
>
134+
<PopoverIcon tabIndex={-1} />
135+
</EditPopover>
136+
</OperationWrapper>
137+
),
138+
},
139+
], [newApiKey, handleCopy, handleDeleteApiKey]);
140+
141+
const dataSource = useMemo(() =>
142+
apiKeys.map((apiKey, i) => ({
143+
...apiKey,
144+
key: i,
145+
}))
146+
, [apiKeys]);
60147

61148
return (
62149
<>
63150
<Card style={{ marginBottom: "20px" }}>
64151
<Flex justify="space-between" align="center" style={{marginBottom: '8px'}}>
65152
<Title level={4}>{trans("profile.apiKeys")}</Title>
66-
<h4><a href={trans("docUrls.apiDocHome")} target="_blank">{trans("home.howToUseAPI")}</a></h4>
153+
<h4>
154+
<a href={trans("docUrls.apiDocHome")} target="_blank" rel="noopener noreferrer">
155+
{trans("home.howToUseAPI")}
156+
</a>
157+
</h4>
67158
<CreateButton
68-
buttonType={"primary"}
159+
buttonType="primary"
69160
icon={<AddIcon />}
70-
onClick={() =>
71-
setModalVisible(true)
72-
}
161+
onClick={() => setModalVisible(true)}
73162
>
74163
{trans("profile.createApiKey")}
75164
</CreateButton>
76165
</Flex>
77-
{Boolean(newApiKey) && <Alert message={trans("profile.apiKeyInfo")} type="info" style={{marginBottom: '16px'}}/>}
166+
167+
{Boolean(newApiKey) && (
168+
<Alert
169+
message={trans("profile.apiKeyInfo")}
170+
type="info"
171+
style={{marginBottom: '16px'}}
172+
/>
173+
)}
174+
78175
<TableStyled
79-
tableLayout={"auto"}
176+
tableLayout="auto"
80177
scroll={{ x: "100%" }}
81178
pagination={false}
82-
columns={[
83-
{
84-
title: trans("profile.apiKeyName"),
85-
dataIndex: "name",
86-
ellipsis: true,
87-
},
88-
{
89-
title: trans("profile.apiKeyDescription"),
90-
dataIndex: "description",
91-
ellipsis: true,
92-
render: (value: string) => {
93-
return (
94-
<>
95-
{ value || '-'}
96-
</>
97-
)
98-
}
99-
},
100-
{
101-
title: trans("profile.apiKey"),
102-
dataIndex: "token",
103-
ellipsis: true,
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>
117-
}
118-
},
119-
{ title: " ", dataIndex: "operation", width: "208px" },
120-
]}
121-
dataSource={apiKeys.map((apiKey, i) => ({
122-
...apiKey,
123-
key: i,
124-
operation: (
125-
<OperationWrapper>
126-
<EditPopover
127-
del={() => {
128-
CustomModal.confirm({
129-
title: trans("profile.deleteApiKey"),
130-
content: trans("profile.deleteApiKeyContent"),
131-
onConfirm: () => {
132-
UserApi.deleteApiKey(apiKey.id).then(resp => {
133-
if(validateResponse(resp)) {
134-
dispatch(fetchApiKeysAction());
135-
}
136-
})
137-
.catch((e) => {
138-
messageInstance.error(trans("profile.deleteApiKeyError"));
139-
})
140-
},
141-
confirmBtnType: "delete",
142-
okText: trans("delete"),
143-
})
144-
}}
145-
>
146-
<PopoverIcon tabIndex={-1} />
147-
</EditPopover>
148-
</OperationWrapper>
149-
),
150-
}))}
179+
columns={columns}
180+
dataSource={dataSource}
151181
/>
152182
</Card>
153183

154184
<CreateApiKeyModal
155185
modalVisible={modalVisible}
156-
closeModal={() => setModalVisible(false)}
157-
onConfigCreate={(apiKey?: ApiKeyType) => {
158-
setModalVisible(false);
159-
setNewApiKey(apiKey);
160-
dispatch(fetchApiKeysAction());
161-
}}
186+
closeModal={handleModalClose}
187+
onConfigCreate={handleConfigCreate}
162188
/>
163189
</>
164-
)
190+
);
165191
}

0 commit comments

Comments
 (0)