Displays all API keys in a table, including new token information

This commit is contained in:
Andrew Nicolaou 2019-05-14 19:50:33 +02:00 committed by Cassie Tarakajian
parent 69d5a87861
commit 504eacaf64
3 changed files with 106 additions and 50 deletions

View file

@ -222,10 +222,10 @@ export function updateSettings(formValues) {
.catch(response => Promise.reject(new Error(response.data.error))); .catch(response => Promise.reject(new Error(response.data.error)));
} }
export function createApiKeySuccess(token) { export function createApiKeySuccess(user) {
return { return {
type: ActionTypes.API_KEY_CREATED, type: ActionTypes.API_KEY_CREATED,
token user
}; };
} }
@ -233,9 +233,7 @@ export function createApiKey(label) {
return dispatch => return dispatch =>
axios.post(`${ROOT_URL}/account/api-keys`, { label }, { withCredentials: true }) axios.post(`${ROOT_URL}/account/api-keys`, { label }, { withCredentials: true })
.then((response) => { .then((response) => {
const { token } = response.data; dispatch(createApiKeySuccess(response.data));
dispatch(createApiKeySuccess(token));
return token;
}) })
.catch(response => Promise.reject(new Error(response.data.error))); .catch(response => Promise.reject(new Error(response.data.error)));
} }

View file

@ -1,5 +1,62 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import InlineSVG from 'react-inlinesvg';
import format from 'date-fns/format';
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
import orderBy from 'lodash/orderBy';
const trashCan = require('../../../images/trash-can.svg');
function NewTokenDisplay({ token }) {
return (
<React.Fragment>
<p>Make sure to copy your new personal access token now. You wont be able to see it again!</p>
<p><input type="text" readOnly value={token} /></p>
<button>Copy</button>
</React.Fragment>
);
}
function TokenMetadataList({ tokens, onRemove }) {
return (
<table className="form__table">
<thead>
<tr>
<th>Name</th>
<th>Created on</th>
<th>Last used</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{orderBy(tokens, ['createdAt'], ['desc']).map((v) => {
const keyRow = (
<tr key={v.id}>
<td>{v.label}</td>
<td>{format(new Date(v.createdAt), 'MMM D, YYYY h:mm A')}</td>
<td>{distanceInWordsToNow(new Date(v.lastUsedAt), { addSuffix: true })}</td>
<td>
<button className="account__tokens__delete-button" onClick={() => onRemove(v)}>
<InlineSVG src={trashCan} alt="Delete Key" />
</button>
</td>
</tr>
);
const newKeyValue = v.token && (
<tr key={v.id}>
<td colSpan="4">
<NewTokenDisplay token={v.token} />
</td>
</tr>
);
return [keyRow, newKeyValue];
})}
</tbody>
</table>
);
}
class APIKeyForm extends React.Component { class APIKeyForm extends React.Component {
constructor(props) { constructor(props) {
@ -7,62 +64,61 @@ class APIKeyForm extends React.Component {
this.state = { keyLabel: '' }; this.state = { keyLabel: '' };
this.addKey = this.addKey.bind(this); this.addKey = this.addKey.bind(this);
this.removeKey = this.removeKey.bind(this);
this.renderApiKeys = this.renderApiKeys.bind(this);
} }
addKey(event) { addKey(event) {
event.preventDefault(); event.preventDefault();
document.getElementById('addKeyForm').reset(); const { keyLabel } = this.state;
this.props.createApiKey(this.state.keyLabel)
.then(newToken => this.setState({ newToken })); this.setState({
this.state.keyLabel = ''; keyLabel: ''
});
this.props.createApiKey(keyLabel);
return false; return false;
} }
removeKey(keyId) { removeKey(key) {
this.props.removeApiKey(keyId); const message = `Are you sure you want to delete "${key.label}"?`;
if (window.confirm(message)) {
this.props.removeApiKey(key.id);
}
}
renderApiKeys() {
const hasApiKeys = this.props.apiKeys && this.props.apiKeys.length > 0;
if (hasApiKeys) {
return (
<TokenMetadataList tokens={this.props.apiKeys} onRemove={this.removeKey} />
);
}
return <p>You have no API keys</p>;
} }
render() { render() {
const { newToken } = this.state; return (
const content = newToken ?
(
<div> <div>
<p>Here is your new key. Copy it somewhere, you won't be able to see it later !</p> <form id="addKeyForm" className="form" onSubmit={this.addKey}>
<input type="text" readOnly value={newToken} /> <label htmlFor="keyLabel" className="form__label">Name</label>
<button>Copy to clipboard</button>
</div>) :
(<form id="addKeyForm" className="form" onSubmit={this.addKey}>
<h2 className="form__label">Key label</h2>
<input <input
type="text" type="text"
className="form__input" className="form__input"
placeholder="A name you will be able to recognize" placeholder="What is this token for?"
id="keyLabel" id="keyLabel"
onChange={(event) => { this.setState({ keyLabel: event.target.value }); }} onChange={(event) => { this.setState({ keyLabel: event.target.value }); }}
/><br /> />
<input <input
type="submit" type="submit"
value="Create new Key" value="Create new Key"
disabled={this.state.keyLabel === ''} disabled={this.state.keyLabel === ''}
/> />
</form> </form>
); {this.renderApiKeys()}
return (
<div>
{content}
<table className="form__table">
<tbody id="form__table_new_key"></tbody>
<tbody>
{this.props.apiKeys && this.props.apiKeys.map(v => (
<tr key={v.id}>
<td><b>{v.label}</b><br />Created on: {v.createdAt}</td>
<td>Last used on:<br /> {v.lastUsedAt}</td>
<td><button className="form__table-button-remove" onClick={() => this.removeKey(v.id)}>Delete</button></td>
</tr>))}
</tbody>
</table>
</div> </div>
); );
} }

View file

@ -17,7 +17,9 @@ const apiKeySchema = new Schema({
}, { timestamps: true, _id: true }); }, { timestamps: true, _id: true });
apiKeySchema.virtual('publicFields').get(function publicFields() { apiKeySchema.virtual('publicFields').get(function publicFields() {
return { id: this.id, label: this.label, lastUsedAt: this.lastUsedAt }; return {
id: this.id, label: this.label, lastUsedAt: this.lastUsedAt, createdAt: this.createdAt
};
}); });
apiKeySchema.virtual('id').get(function getApiKeyId() { apiKeySchema.virtual('id').get(function getApiKeyId() {