Skip to content

Convert APIKeyForm to a function component and connect to Redux #2307

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 81 additions & 103 deletions client/modules/User/components/APIKeyForm.jsx
Original file line number Diff line number Diff line change
@@ -1,135 +1,113 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import Button from '../../../common/Button';
import { PlusIcon } from '../../../common/icons';
import CopyableInput from '../../IDE/components/CopyableInput';
import { createApiKey, removeApiKey } from '../actions';

import APIKeyList from './APIKeyList';

export const APIKeyPropType = PropTypes.shape({
id: PropTypes.objectOf(PropTypes.shape()).isRequired,
token: PropTypes.objectOf(PropTypes.shape()),
id: PropTypes.string.isRequired,
token: PropTypes.string,
label: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
lastUsedAt: PropTypes.string
});

class APIKeyForm extends React.Component {
constructor(props) {
super(props);
this.state = { keyLabel: '' };
const APIKeyForm = () => {
const { t } = useTranslation();
const apiKeys = useSelector((state) => state.user.apiKeys);
const dispatch = useDispatch();

this.addKey = this.addKey.bind(this);
this.removeKey = this.removeKey.bind(this);
this.renderApiKeys = this.renderApiKeys.bind(this);
}
const [keyLabel, setKeyLabel] = useState('');

addKey(event) {
const addKey = (event) => {
event.preventDefault();
const { keyLabel } = this.state;
dispatch(createApiKey(keyLabel));
setKeyLabel('');
};

this.setState({
keyLabel: ''
});

this.props.createApiKey(keyLabel);

return false;
}

removeKey(key) {
const message = this.props.t('APIKeyForm.ConfirmDelete', {
const removeKey = (key) => {
const message = t('APIKeyForm.ConfirmDelete', {
key_label: key.label
});

if (window.confirm(message)) {
this.props.removeApiKey(key.id);
dispatch(removeApiKey(key.id));
}
}
};

renderApiKeys() {
const hasApiKeys = this.props.apiKeys && this.props.apiKeys.length > 0;
const renderApiKeys = () => {
const hasApiKeys = apiKeys && apiKeys.length > 0;

if (hasApiKeys) {
return (
<APIKeyList apiKeys={this.props.apiKeys} onRemove={this.removeKey} />
);
return <APIKeyList apiKeys={apiKeys} onRemove={removeKey} />;
}
return <p>{this.props.t('APIKeyForm.NoTokens')}</p>;
}

render() {
const keyWithToken = this.props.apiKeys.find((k) => !!k.token);

return (
<div className="api-key-form">
<p className="api-key-form__summary">
{this.props.t('APIKeyForm.Summary')}
</p>

<div className="api-key-form__section">
<h3 className="api-key-form__title">
{this.props.t('APIKeyForm.CreateToken')}
</h3>
<form className="form form--inline" onSubmit={this.addKey}>
<label
htmlFor="keyLabel"
className="form__label form__label--hidden "
>
{this.props.t('APIKeyForm.TokenLabel')}
</label>
<input
className="form__input"
id="keyLabel"
onChange={(event) => {
this.setState({ keyLabel: event.target.value });
}}
placeholder={this.props.t('APIKeyForm.TokenPlaceholder')}
type="text"
value={this.state.keyLabel}
return <p>{t('APIKeyForm.NoTokens')}</p>;
};

const keyWithToken = apiKeys.find((k) => !!k.token);

return (
<div className="api-key-form">
<p className="api-key-form__summary">{t('APIKeyForm.Summary')}</p>

<div className="api-key-form__section">
<h3 className="api-key-form__title">{t('APIKeyForm.CreateToken')}</h3>
<form className="form form--inline" onSubmit={addKey}>
<label
htmlFor="keyLabel"
className="form__label form__label--hidden "
>
{t('APIKeyForm.TokenLabel')}
</label>
<input
className="form__input"
id="keyLabel"
onChange={(event) => {
setKeyLabel(event.target.value);
}}
placeholder={t('APIKeyForm.TokenPlaceholder')}
type="text"
value={keyLabel}
/>
<Button
disabled={keyLabel === ''}
iconBefore={<PlusIcon />}
label="Create new key"
type="submit"
>
{t('APIKeyForm.CreateTokenSubmit')}
</Button>
</form>

{keyWithToken && (
<div className="api-key-form__new-token">
<h4 className="api-key-form__new-token__title">
{t('APIKeyForm.NewTokenTitle')}
</h4>
<p className="api-key-form__new-token__info">
{t('APIKeyForm.NewTokenInfo')}
</p>
<CopyableInput
label={keyWithToken.label}
value={keyWithToken.token}
/>
<Button
disabled={this.state.keyLabel === ''}
iconBefore={<PlusIcon />}
label="Create new key"
type="submit"
>
{this.props.t('APIKeyForm.CreateTokenSubmit')}
</Button>
</form>

{keyWithToken && (
<div className="api-key-form__new-token">
<h4 className="api-key-form__new-token__title">
{this.props.t('APIKeyForm.NewTokenTitle')}
</h4>
<p className="api-key-form__new-token__info">
{this.props.t('APIKeyForm.NewTokenInfo')}
</p>
<CopyableInput
label={keyWithToken.label}
value={keyWithToken.token}
/>
</div>
)}
</div>

<div className="api-key-form__section">
<h3 className="api-key-form__title">
{this.props.t('APIKeyForm.ExistingTokensTitle')}
</h3>
{this.renderApiKeys()}
</div>
</div>
)}
</div>
);
}
}

APIKeyForm.propTypes = {
apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired,
createApiKey: PropTypes.func.isRequired,
removeApiKey: PropTypes.func.isRequired,
t: PropTypes.func.isRequired
<div className="api-key-form__section">
<h3 className="api-key-form__title">
{t('APIKeyForm.ExistingTokensTitle')}
</h3>
{renderApiKeys()}
</div>
</div>
);
};

export default APIKeyForm;
14 changes: 2 additions & 12 deletions client/modules/User/pages/AccountView.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import PropTypes from 'prop-types';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelector } from 'react-redux';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import { withRouter, browserHistory } from 'react-router';
import { parse } from 'query-string';
import { createApiKey, removeApiKey } from '../actions';
import AccountForm from '../components/AccountForm';
import SocialAuthButton from '../components/SocialAuthButton';
import APIKeyForm from '../components/APIKeyForm';
Expand Down Expand Up @@ -52,9 +51,6 @@ function AccountView({ location }) {
const errorType = queryParams.error;
const accessTokensUIEnabled = window.process.env.UI_ACCESS_TOKEN_ENABLED;

const apiKeys = useSelector((state) => state.user.apiKeys);
const dispatch = useDispatch();

return (
<div className="account-settings__container">
<Helmet>
Expand Down Expand Up @@ -102,13 +98,7 @@ function AccountView({ location }) {
<SocialLoginPanel />
</TabPanel>
<TabPanel>
<APIKeyForm
// TODO: it makes more sense to connect the APIKeyForm component directly -Linda
apiKeys={apiKeys}
createApiKey={() => dispatch(createApiKey)}
removeApiKey={() => dispatch(removeApiKey)}
t={t}
/>
<APIKeyForm />
</TabPanel>
</Tabs>
)}
Expand Down