Skip to content

Commit 795ae09

Browse files
authored
Merge pull request #2307 from lindapaiste/refactor/api-key-form
Convert `APIKeyForm` to a function component and connect to Redux
2 parents 1ac9b91 + 994b370 commit 795ae09

File tree

2 files changed

+83
-115
lines changed

2 files changed

+83
-115
lines changed
Lines changed: 81 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,113 @@
11
import PropTypes from 'prop-types';
2-
import React from 'react';
2+
import React, { useState } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
import { useDispatch, useSelector } from 'react-redux';
35
import Button from '../../../common/Button';
46
import { PlusIcon } from '../../../common/icons';
57
import CopyableInput from '../../IDE/components/CopyableInput';
8+
import { createApiKey, removeApiKey } from '../actions';
69

710
import APIKeyList from './APIKeyList';
811

912
export const APIKeyPropType = PropTypes.shape({
10-
id: PropTypes.objectOf(PropTypes.shape()).isRequired,
11-
token: PropTypes.objectOf(PropTypes.shape()),
13+
id: PropTypes.string.isRequired,
14+
token: PropTypes.string,
1215
label: PropTypes.string.isRequired,
1316
createdAt: PropTypes.string.isRequired,
1417
lastUsedAt: PropTypes.string
1518
});
1619

17-
class APIKeyForm extends React.Component {
18-
constructor(props) {
19-
super(props);
20-
this.state = { keyLabel: '' };
20+
const APIKeyForm = () => {
21+
const { t } = useTranslation();
22+
const apiKeys = useSelector((state) => state.user.apiKeys);
23+
const dispatch = useDispatch();
2124

22-
this.addKey = this.addKey.bind(this);
23-
this.removeKey = this.removeKey.bind(this);
24-
this.renderApiKeys = this.renderApiKeys.bind(this);
25-
}
25+
const [keyLabel, setKeyLabel] = useState('');
2626

27-
addKey(event) {
27+
const addKey = (event) => {
2828
event.preventDefault();
29-
const { keyLabel } = this.state;
29+
dispatch(createApiKey(keyLabel));
30+
setKeyLabel('');
31+
};
3032

31-
this.setState({
32-
keyLabel: ''
33-
});
34-
35-
this.props.createApiKey(keyLabel);
36-
37-
return false;
38-
}
39-
40-
removeKey(key) {
41-
const message = this.props.t('APIKeyForm.ConfirmDelete', {
33+
const removeKey = (key) => {
34+
const message = t('APIKeyForm.ConfirmDelete', {
4235
key_label: key.label
4336
});
4437

4538
if (window.confirm(message)) {
46-
this.props.removeApiKey(key.id);
39+
dispatch(removeApiKey(key.id));
4740
}
48-
}
41+
};
4942

50-
renderApiKeys() {
51-
const hasApiKeys = this.props.apiKeys && this.props.apiKeys.length > 0;
43+
const renderApiKeys = () => {
44+
const hasApiKeys = apiKeys && apiKeys.length > 0;
5245

5346
if (hasApiKeys) {
54-
return (
55-
<APIKeyList apiKeys={this.props.apiKeys} onRemove={this.removeKey} />
56-
);
47+
return <APIKeyList apiKeys={apiKeys} onRemove={removeKey} />;
5748
}
58-
return <p>{this.props.t('APIKeyForm.NoTokens')}</p>;
59-
}
60-
61-
render() {
62-
const keyWithToken = this.props.apiKeys.find((k) => !!k.token);
63-
64-
return (
65-
<div className="api-key-form">
66-
<p className="api-key-form__summary">
67-
{this.props.t('APIKeyForm.Summary')}
68-
</p>
69-
70-
<div className="api-key-form__section">
71-
<h3 className="api-key-form__title">
72-
{this.props.t('APIKeyForm.CreateToken')}
73-
</h3>
74-
<form className="form form--inline" onSubmit={this.addKey}>
75-
<label
76-
htmlFor="keyLabel"
77-
className="form__label form__label--hidden "
78-
>
79-
{this.props.t('APIKeyForm.TokenLabel')}
80-
</label>
81-
<input
82-
className="form__input"
83-
id="keyLabel"
84-
onChange={(event) => {
85-
this.setState({ keyLabel: event.target.value });
86-
}}
87-
placeholder={this.props.t('APIKeyForm.TokenPlaceholder')}
88-
type="text"
89-
value={this.state.keyLabel}
49+
return <p>{t('APIKeyForm.NoTokens')}</p>;
50+
};
51+
52+
const keyWithToken = apiKeys.find((k) => !!k.token);
53+
54+
return (
55+
<div className="api-key-form">
56+
<p className="api-key-form__summary">{t('APIKeyForm.Summary')}</p>
57+
58+
<div className="api-key-form__section">
59+
<h3 className="api-key-form__title">{t('APIKeyForm.CreateToken')}</h3>
60+
<form className="form form--inline" onSubmit={addKey}>
61+
<label
62+
htmlFor="keyLabel"
63+
className="form__label form__label--hidden "
64+
>
65+
{t('APIKeyForm.TokenLabel')}
66+
</label>
67+
<input
68+
className="form__input"
69+
id="keyLabel"
70+
onChange={(event) => {
71+
setKeyLabel(event.target.value);
72+
}}
73+
placeholder={t('APIKeyForm.TokenPlaceholder')}
74+
type="text"
75+
value={keyLabel}
76+
/>
77+
<Button
78+
disabled={keyLabel === ''}
79+
iconBefore={<PlusIcon />}
80+
label="Create new key"
81+
type="submit"
82+
>
83+
{t('APIKeyForm.CreateTokenSubmit')}
84+
</Button>
85+
</form>
86+
87+
{keyWithToken && (
88+
<div className="api-key-form__new-token">
89+
<h4 className="api-key-form__new-token__title">
90+
{t('APIKeyForm.NewTokenTitle')}
91+
</h4>
92+
<p className="api-key-form__new-token__info">
93+
{t('APIKeyForm.NewTokenInfo')}
94+
</p>
95+
<CopyableInput
96+
label={keyWithToken.label}
97+
value={keyWithToken.token}
9098
/>
91-
<Button
92-
disabled={this.state.keyLabel === ''}
93-
iconBefore={<PlusIcon />}
94-
label="Create new key"
95-
type="submit"
96-
>
97-
{this.props.t('APIKeyForm.CreateTokenSubmit')}
98-
</Button>
99-
</form>
100-
101-
{keyWithToken && (
102-
<div className="api-key-form__new-token">
103-
<h4 className="api-key-form__new-token__title">
104-
{this.props.t('APIKeyForm.NewTokenTitle')}
105-
</h4>
106-
<p className="api-key-form__new-token__info">
107-
{this.props.t('APIKeyForm.NewTokenInfo')}
108-
</p>
109-
<CopyableInput
110-
label={keyWithToken.label}
111-
value={keyWithToken.token}
112-
/>
113-
</div>
114-
)}
115-
</div>
116-
117-
<div className="api-key-form__section">
118-
<h3 className="api-key-form__title">
119-
{this.props.t('APIKeyForm.ExistingTokensTitle')}
120-
</h3>
121-
{this.renderApiKeys()}
122-
</div>
99+
</div>
100+
)}
123101
</div>
124-
);
125-
}
126-
}
127102

128-
APIKeyForm.propTypes = {
129-
apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired,
130-
createApiKey: PropTypes.func.isRequired,
131-
removeApiKey: PropTypes.func.isRequired,
132-
t: PropTypes.func.isRequired
103+
<div className="api-key-form__section">
104+
<h3 className="api-key-form__title">
105+
{t('APIKeyForm.ExistingTokensTitle')}
106+
</h3>
107+
{renderApiKeys()}
108+
</div>
109+
</div>
110+
);
133111
};
134112

135113
export default APIKeyForm;

client/modules/User/pages/AccountView.jsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import React from 'react';
2-
import { useDispatch, useSelector } from 'react-redux';
2+
import { useSelector } from 'react-redux';
33
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
44
import { Helmet } from 'react-helmet';
55
import { useTranslation } from 'react-i18next';
66
import { useHistory, useLocation } from 'react-router-dom';
77
import { parse } from 'query-string';
8-
import { createApiKey, removeApiKey } from '../actions';
98
import AccountForm from '../components/AccountForm';
109
import SocialAuthButton from '../components/SocialAuthButton';
1110
import APIKeyForm from '../components/APIKeyForm';
@@ -51,9 +50,6 @@ function AccountView() {
5150
const showError = !!queryParams.error;
5251
const errorType = queryParams.error;
5352
const accessTokensUIEnabled = window.process.env.UI_ACCESS_TOKEN_ENABLED;
54-
55-
const apiKeys = useSelector((state) => state.user.apiKeys);
56-
const dispatch = useDispatch();
5753
const history = useHistory();
5854

5955
return (
@@ -103,13 +99,7 @@ function AccountView() {
10399
<SocialLoginPanel />
104100
</TabPanel>
105101
<TabPanel>
106-
<APIKeyForm
107-
// TODO: it makes more sense to connect the APIKeyForm component directly -Linda
108-
apiKeys={apiKeys}
109-
createApiKey={() => dispatch(createApiKey)}
110-
removeApiKey={() => dispatch(removeApiKey)}
111-
t={t}
112-
/>
102+
<APIKeyForm />
113103
</TabPanel>
114104
</Tabs>
115105
)}

0 commit comments

Comments
 (0)