*/
import * as React from 'react';
import InstanceMessage from '../../../components/common/InstanceMessage';
-import TokenForm from '../../users/components/TokensForm';
+import TokensForm from '../../users/components/TokensForm';
import { translate } from '../../../helpers/l10n';
interface Props {
<InstanceMessage message={translate('my_account.tokens_description')} />
</div>
- <TokenForm login={login} />
+ <TokensForm deleteConfirmation="modal" login={login} />
</div>
</div>
);
/>
</div>
<TokensForm
+ deleteConfirmation="modal"
login="user"
/>
</div>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import TokensFormItem from './TokensFormItem';
+import TokensFormItem, { TokenDeleteConfirmation } from './TokensFormItem';
import TokensFormNewToken from './TokensFormNewToken';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import { SubmitButton } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
interface Props {
+ deleteConfirmation: TokenDeleteConfirmation;
login: string;
updateTokensCount?: (login: string, tokensCount: number) => void;
}
}
return tokens.map(token => (
<TokensFormItem
+ deleteConfirmation={this.props.deleteConfirmation}
key={token.name}
login={this.props.login}
onRevokeToken={this.handleRevokeToken}
{newToken && <TokensFormNewToken token={newToken} />}
- <table className="data zebra big-spacer-top ">
+ <table className="data zebra big-spacer-top">
<thead>
<tr>
<th>{translate('name')}</th>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { Button } from '../../../components/ui/buttons';
+import ConfirmButton from '../../../components/controls/ConfirmButton';
import DateFormatter from '../../../components/intl/DateFormatter';
import DateFromNowHourPrecision from '../../../components/intl/DateFromNowHourPrecision';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import Tooltip from '../../../components/controls/Tooltip';
-import { Button } from '../../../components/ui/buttons';
import { limitComponentName } from '../../../helpers/path';
import { revokeToken } from '../../../api/user-tokens';
import { translate } from '../../../helpers/l10n';
+export type TokenDeleteConfirmation = 'inline' | 'modal';
+
interface Props {
+ deleteConfirmation: TokenDeleteConfirmation;
login: string;
onRevokeToken: (token: T.UserToken) => void;
token: T.UserToken;
}
interface State {
- deleting: boolean;
loading: boolean;
+ showConfirmation: boolean;
}
export default class TokensFormItem extends React.PureComponent<Props, State> {
mounted = false;
- state: State = { deleting: false, loading: false };
+ state: State = { loading: false, showConfirmation: false };
componentDidMount() {
this.mounted = true;
this.mounted = false;
}
- handleRevoke = () => {
- if (this.state.deleting) {
- this.setState({ loading: true });
- revokeToken({ login: this.props.login, name: this.props.token.name }).then(
- () => this.props.onRevokeToken(this.props.token),
- () => {
- if (this.mounted) {
- this.setState({ loading: false, deleting: false });
- }
+ handleClick = () => {
+ if (this.state.showConfirmation) {
+ this.handleRevoke().then(() => {
+ if (this.mounted) {
+ this.setState({ showConfirmation: false });
}
- );
+ });
} else {
- this.setState({ deleting: true });
+ this.setState({ showConfirmation: true });
}
};
+ handleRevoke = () => {
+ this.setState({ loading: true });
+ return revokeToken({ login: this.props.login, name: this.props.token.name }).then(
+ () => this.props.onRevokeToken(this.props.token),
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
render() {
- const { token } = this.props;
- const { loading } = this.state;
+ const { deleteConfirmation, token } = this.props;
+ const { loading, showConfirmation } = this.state;
return (
<tr>
<td>
<DeferredSpinner loading={loading}>
<i className="spinner-placeholder" />
</DeferredSpinner>
- <Button
- className="button-red input-small spacer-left"
- disabled={loading}
- onClick={this.handleRevoke}>
- {this.state.deleting
- ? translate('users.tokens.sure')
- : translate('users.tokens.revoke')}
- </Button>
+ {deleteConfirmation === 'modal' ? (
+ <ConfirmButton
+ confirmButtonText={translate('users.tokens.revoke_token')}
+ isDestructive={true}
+ modalBody={
+ <FormattedMessage
+ defaultMessage={translate('users.tokens.sure_X')}
+ id="users.tokens.sure_X"
+ values={{ token: <strong>{token.name}</strong> }}
+ />
+ }
+ modalHeader={translate('users.tokens.revoke_token')}
+ onConfirm={this.handleRevoke}>
+ {({ onClick }) => (
+ <Button
+ className="spacer-left button-red input-small"
+ disabled={loading}
+ onClick={onClick}
+ title={translate('users.tokens.revoke_token')}>
+ {translate('users.tokens.revoke')}
+ </Button>
+ )}
+ </ConfirmButton>
+ ) : (
+ <Button
+ className="button-red input-small spacer-left"
+ disabled={loading}
+ onClick={this.handleClick}>
+ {showConfirmation ? translate('users.tokens.sure') : translate('users.tokens.revoke')}
+ </Button>
+ )}
</td>
</tr>
);
</h2>
</header>
<div className="modal-body modal-container">
- <TokensForm login={props.user.login} updateTokensCount={props.updateTokensCount} />
+ <TokensForm
+ deleteConfirmation="inline"
+ login={props.user.login}
+ updateTokensCount={props.updateTokensCount}
+ />
</div>
<footer className="modal-foot">
<ResetButtonLink onClick={props.onClose}>{translate('Done')}</ResetButtonLink>
});
function shallowRender(props: Partial<TokensForm['props']> = {}) {
- return shallow<TokensForm>(<TokensForm login="luke" updateTokensCount={jest.fn()} {...props} />);
+ return shallow<TokensForm>(
+ <TokensForm deleteConfirmation="inline" login="luke" updateTokensCount={jest.fn()} {...props} />
+ );
}
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
+ expect(shallowRender({ deleteConfirmation: 'modal' })).toMatchSnapshot();
});
-it('should revoke the token', async () => {
+it('should revoke the token using inline confirmation', async () => {
const onRevokeToken = jest.fn();
- const wrapper = shallowRender({ onRevokeToken });
+ const wrapper = shallowRender({ deleteConfirmation: 'inline', onRevokeToken });
expect(wrapper.find('Button')).toMatchSnapshot();
click(wrapper.find('Button'));
expect(wrapper.find('Button')).toMatchSnapshot();
expect(onRevokeToken).toHaveBeenCalledWith(userToken);
});
+it('should revoke the token using modal confirmation', async () => {
+ const onRevokeToken = jest.fn();
+ const wrapper = shallowRender({ deleteConfirmation: 'modal', onRevokeToken });
+ wrapper.find('ConfirmButton').prop<Function>('onConfirm')();
+ expect(revokeToken).toHaveBeenCalledWith({ login: 'luke', name: 'foo' });
+ await waitAndUpdate(wrapper);
+ expect(onRevokeToken).toHaveBeenCalledWith(userToken);
+});
+
function shallowRender(props: Partial<TokensFormItem['props']> = {}) {
return shallow(
- <TokensFormItem login="luke" onRevokeToken={jest.fn()} token={userToken} {...props} />
+ <TokensFormItem
+ deleteConfirmation="inline"
+ login="luke"
+ onRevokeToken={jest.fn()}
+ token={userToken}
+ {...props}
+ />
);
}
</SubmitButton>
</form>
<table
- className="data zebra big-spacer-top "
+ className="data zebra big-spacer-top"
>
<thead>
<tr>
</SubmitButton>
</form>
<table
- className="data zebra big-spacer-top "
+ className="data zebra big-spacer-top"
>
<thead>
<tr>
timeout={100}
>
<TokensFormItem
+ deleteConfirmation="inline"
key="foo"
login="luke"
onRevokeToken={[Function]}
}
/>
<TokensFormItem
+ deleteConfirmation="inline"
key="bar"
login="luke"
onRevokeToken={[Function]}
</tr>
`;
-exports[`should revoke the token 1`] = `
+exports[`should render correctly 2`] = `
+<tr>
+ <td>
+ <Tooltip
+ overlay="foo"
+ >
+ <span>
+ foo
+ </span>
+ </Tooltip>
+ </td>
+ <td
+ className="nowrap"
+ >
+ <DateFromNowHourPrecision
+ date="2019-01-18T15:06:33+0100"
+ />
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <DateFormatter
+ date="2019-01-15T15:06:33+0100"
+ long={true}
+ />
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <DeferredSpinner
+ loading={false}
+ timeout={100}
+ >
+ <i
+ className="spinner-placeholder"
+ />
+ </DeferredSpinner>
+ <ConfirmButton
+ confirmButtonText="users.tokens.revoke_token"
+ isDestructive={true}
+ modalBody={
+ <FormattedMessage
+ defaultMessage="users.tokens.sure_X"
+ id="users.tokens.sure_X"
+ values={
+ Object {
+ "token": <strong>
+ foo
+ </strong>,
+ }
+ }
+ />
+ }
+ modalHeader="users.tokens.revoke_token"
+ onConfirm={[Function]}
+ >
+ <Component />
+ </ConfirmButton>
+ </td>
+</tr>
+`;
+
+exports[`should revoke the token using inline confirmation 1`] = `
<Button
className="button-red input-small spacer-left"
disabled={false}
</Button>
`;
-exports[`should revoke the token 2`] = `
+exports[`should revoke the token using inline confirmation 2`] = `
<Button
className="button-red input-small spacer-left"
disabled={false}
className="modal-body modal-container"
>
<TokensForm
+ deleteConfirmation="inline"
login="john.doe"
updateTokensCount={[MockFunction]}
/>
users.search_description=Search users by login or name
users.update=Update users
users.tokens=Tokens
+users.user_X_tokens=Tokens of {user}
users.tokens.sure=Sure?
+users.tokens.sure_X=Are you sure you want to revoke token {token}?
users.tokens.revoke=Revoke
-users.user_X_tokens=Tokens of {user}
+users.tokens.revoke_token=Revoke token
users.no_tokens=No tokens
users.generate=Generate
users.generate_tokens=Generate Tokens