@@ -172,7 +172,7 @@ export default class Onboarding extends React.PureComponent { | |||
</div> | |||
<div className="page-description"> | |||
{translateWithParameters( | |||
'onboarding.header.description_x', | |||
'onboarding.header.description', | |||
organizationsEnabled ? 3 : 2 | |||
)} | |||
</div> |
@@ -19,6 +19,7 @@ | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import classNames from 'classnames'; | |||
import Step from './Step'; | |||
import CloseIcon from '../../../components/icons-components/CloseIcon'; | |||
import { generateToken, revokeToken } from '../../../api/user-tokens'; | |||
@@ -36,7 +37,9 @@ type Props = {| | |||
/*:: | |||
type State = { | |||
existingToken:string, | |||
loading: boolean, | |||
selection: string, | |||
tokenName?: string, | |||
token?: string | |||
}; | |||
@@ -46,7 +49,9 @@ export default class TokenStep extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { | |||
loading: false | |||
existingToken: '', | |||
loading: false, | |||
selection: 'generate' | |||
}; | |||
componentDidMount() { | |||
@@ -57,6 +62,9 @@ export default class TokenStep extends React.PureComponent { | |||
this.mounted = false; | |||
} | |||
getToken = () => | |||
this.state.selection === 'generate' ? this.state.token : this.state.existingToken; | |||
handleTokenNameChange = (event /*: { target: HTMLInputElement } */) => { | |||
this.setState({ tokenName: event.target.value }); | |||
}; | |||
@@ -103,13 +111,95 @@ export default class TokenStep extends React.PureComponent { | |||
handleContinueClick = (event /*: Event */) => { | |||
event.preventDefault(); | |||
if (this.state.token) { | |||
this.props.onContinue(this.state.token); | |||
const token = this.getToken(); | |||
if (token) { | |||
this.props.onContinue(token); | |||
} | |||
}; | |||
handleGenerateClick = (event /*: Event */) => { | |||
event.preventDefault(); | |||
this.setState({ selection: 'generate' }); | |||
}; | |||
handleUseExistingClick = (event /*: Event */) => { | |||
event.preventDefault(); | |||
this.setState({ selection: 'use-existing' }); | |||
}; | |||
handleExisingTokenChange = (event /*: { currentTarget: HTMLInputElement } */) => { | |||
this.setState({ existingToken: event.currentTarget.value }); | |||
}; | |||
renderGenerateOption = () => ( | |||
<div> | |||
<a | |||
className="js-new link-base-color link-no-underline" | |||
href="#" | |||
onClick={this.handleGenerateClick}> | |||
<i | |||
className={classNames('icon-radio', 'spacer-right', { | |||
'is-checked': this.state.selection === 'generate' | |||
})} | |||
/> | |||
{translate('onboading.token.generate_token')} | |||
</a> | |||
{this.state.selection === 'generate' && ( | |||
<div className="big-spacer-top"> | |||
<form onSubmit={this.handleTokenGenerate}> | |||
<input | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={this.handleTokenNameChange} | |||
placeholder={translate('onboading.token.generate_token.placeholder')} | |||
required={true} | |||
type="text" | |||
value={this.state.tokenName || ''} | |||
/> | |||
{this.state.loading ? ( | |||
<i className="spinner text-middle" /> | |||
) : ( | |||
<button className="text-middle" disabled={!this.state.tokenName}> | |||
{translate('onboarding.token.generate')} | |||
</button> | |||
)} | |||
</form> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
renderUseExistingOption = () => ( | |||
<div className="big-spacer-top"> | |||
<a | |||
className="js-new link-base-color link-no-underline" | |||
href="#" | |||
onClick={this.handleUseExistingClick}> | |||
<i | |||
className={classNames('icon-radio', 'spacer-right', { | |||
'is-checked': this.state.selection === 'use-existing' | |||
})} | |||
/> | |||
{translate('onboarding.token.use_existing_token')} | |||
</a> | |||
{this.state.selection === 'use-existing' && ( | |||
<div className="big-spacer-top"> | |||
<input | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={this.handleExisingTokenChange} | |||
placeholder={translate('onboarding.token.use_existing_token.placeholder')} | |||
required={true} | |||
type="text" | |||
value={this.state.existingToken} | |||
/> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
renderForm = () => { | |||
const { loading, token, tokenName } = this.state; | |||
const { existingToken, loading, selection, token, tokenName } = this.state; | |||
return ( | |||
<div className="boxed-group-inner"> | |||
@@ -129,27 +219,16 @@ export default class TokenStep extends React.PureComponent { | |||
)} | |||
</form> | |||
) : ( | |||
<form onSubmit={this.handleTokenGenerate}> | |||
<input | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={this.handleTokenNameChange} | |||
placeholder={translate('onboarding.token.placeholder')} | |||
required={true} | |||
type="text" | |||
value={tokenName || ''} | |||
/> | |||
{loading ? ( | |||
<i className="spinner text-middle" /> | |||
) : ( | |||
<button className="text-middle">{translate('onboarding.token.generate')}</button> | |||
)} | |||
</form> | |||
<div> | |||
{this.renderGenerateOption()} | |||
{this.renderUseExistingOption()} | |||
</div> | |||
)} | |||
<div className="note big-spacer-top width-50">{translate('onboarding.token.text')}</div> | |||
{token != null && ( | |||
{((selection === 'generate' && token != null) || | |||
(selection === 'use-existing' && existingToken)) && ( | |||
<div className="big-spacer-top"> | |||
<button className="js-continue" onClick={this.handleContinueClick}> | |||
{translate('continue')} | |||
@@ -161,7 +240,8 @@ export default class TokenStep extends React.PureComponent { | |||
}; | |||
renderResult = () => { | |||
const { token, tokenName } = this.state; | |||
const { selection, tokenName } = this.state; | |||
const token = this.getToken(); | |||
if (!token) { | |||
return null; | |||
@@ -170,8 +250,7 @@ export default class TokenStep extends React.PureComponent { | |||
return ( | |||
<div className="boxed-group-actions"> | |||
<i className="icon-check spacer-right" /> | |||
{tokenName} | |||
{': '} | |||
{selection === 'generate' && tokenName && `${tokenName}: `} | |||
<strong>{token}</strong> | |||
</div> | |||
); |
@@ -77,3 +77,19 @@ it('continues', () => { | |||
click(wrapper.find('.js-continue')); | |||
expect(onContinue).toBeCalledWith('abcd1234'); | |||
}); | |||
it('uses existing token', () => { | |||
const onContinue = jest.fn(); | |||
const wrapper = mount( | |||
<TokenStep | |||
finished={false} | |||
open={true} | |||
onContinue={onContinue} | |||
onOpen={jest.fn()} | |||
stepNumber={1} | |||
/> | |||
); | |||
wrapper.setState({ existingToken: 'abcd1234', selection: 'use-existing' }); | |||
click(wrapper.find('.js-continue')); | |||
expect(onContinue).toBeCalledWith('abcd1234'); | |||
}); |
@@ -39,7 +39,7 @@ exports[`guides for on-premise 1`] = ` | |||
<div | |||
className="page-description" | |||
> | |||
onboarding.header.description_x.2 | |||
onboarding.header.description.2 | |||
</div> | |||
</header> | |||
<TokenStep | |||
@@ -99,7 +99,7 @@ exports[`guides for on-premise 2`] = ` | |||
<div | |||
className="page-description" | |||
> | |||
onboarding.header.description_x.2 | |||
onboarding.header.description.2 | |||
</div> | |||
</header> | |||
<TokenStep | |||
@@ -160,7 +160,7 @@ exports[`guides for sonarcloud 1`] = ` | |||
<div | |||
className="page-description" | |||
> | |||
onboarding.header.description_x.3 | |||
onboarding.header.description.3 | |||
</div> | |||
</header> | |||
<OrganizationStep | |||
@@ -233,7 +233,7 @@ exports[`guides for sonarcloud 2`] = ` | |||
<div | |||
className="page-description" | |||
> | |||
onboarding.header.description_x.3 | |||
onboarding.header.description.3 | |||
</div> | |||
</header> | |||
<OrganizationStep | |||
@@ -307,7 +307,7 @@ exports[`guides for sonarcloud 3`] = ` | |||
<div | |||
className="page-description" | |||
> | |||
onboarding.header.description_x.3 | |||
onboarding.header.description.3 | |||
</div> | |||
</header> | |||
<OrganizationStep |
@@ -35,24 +35,57 @@ exports[`generates token 1`] = ` | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={[Function]} | |||
placeholder="onboarding.token.placeholder" | |||
required={true} | |||
type="text" | |||
value="" | |||
/> | |||
<button | |||
className="text-middle" | |||
<div> | |||
<div> | |||
<a | |||
className="js-new link-base-color link-no-underline" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<i | |||
className="icon-radio spacer-right is-checked" | |||
/> | |||
onboading.token.generate_token | |||
</a> | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={[Function]} | |||
placeholder="onboading.token.generate_token.placeholder" | |||
required={true} | |||
type="text" | |||
value="" | |||
/> | |||
<button | |||
className="text-middle" | |||
disabled={true} | |||
> | |||
onboarding.token.generate | |||
</button> | |||
</form> | |||
</div> | |||
</div> | |||
<div | |||
className="big-spacer-top" | |||
> | |||
onboarding.token.generate | |||
</button> | |||
</form> | |||
<a | |||
className="js-new link-base-color link-no-underline" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<i | |||
className="icon-radio spacer-right" | |||
/> | |||
onboarding.token.use_existing_token | |||
</a> | |||
</div> | |||
</div> | |||
<div | |||
className="note big-spacer-top width-50" | |||
> | |||
@@ -99,22 +132,54 @@ exports[`generates token 2`] = ` | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={[Function]} | |||
placeholder="onboarding.token.placeholder" | |||
required={true} | |||
type="text" | |||
value="my token" | |||
/> | |||
<i | |||
className="spinner text-middle" | |||
/> | |||
</form> | |||
<div> | |||
<div> | |||
<a | |||
className="js-new link-base-color link-no-underline" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<i | |||
className="icon-radio spacer-right is-checked" | |||
/> | |||
onboading.token.generate_token | |||
</a> | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={[Function]} | |||
placeholder="onboading.token.generate_token.placeholder" | |||
required={true} | |||
type="text" | |||
value="my token" | |||
/> | |||
<i | |||
className="spinner text-middle" | |||
/> | |||
</form> | |||
</div> | |||
</div> | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<a | |||
className="js-new link-base-color link-no-underline" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<i | |||
className="icon-radio spacer-right" | |||
/> | |||
onboarding.token.use_existing_token | |||
</a> | |||
</div> | |||
</div> | |||
<div | |||
className="note big-spacer-top width-50" | |||
> | |||
@@ -419,24 +484,57 @@ exports[`revokes token 3`] = ` | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={[Function]} | |||
placeholder="onboarding.token.placeholder" | |||
required={true} | |||
type="text" | |||
value="" | |||
/> | |||
<button | |||
className="text-middle" | |||
<div> | |||
<div> | |||
<a | |||
className="js-new link-base-color link-no-underline" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<i | |||
className="icon-radio spacer-right is-checked" | |||
/> | |||
onboading.token.generate_token | |||
</a> | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-large spacer-right text-middle" | |||
onChange={[Function]} | |||
placeholder="onboading.token.generate_token.placeholder" | |||
required={true} | |||
type="text" | |||
value="" | |||
/> | |||
<button | |||
className="text-middle" | |||
disabled={true} | |||
> | |||
onboarding.token.generate | |||
</button> | |||
</form> | |||
</div> | |||
</div> | |||
<div | |||
className="big-spacer-top" | |||
> | |||
onboarding.token.generate | |||
</button> | |||
</form> | |||
<a | |||
className="js-new link-base-color link-no-underline" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<i | |||
className="icon-radio spacer-right" | |||
/> | |||
onboarding.token.use_existing_token | |||
</a> | |||
</div> | |||
</div> | |||
<div | |||
className="note big-spacer-top width-50" | |||
> |
@@ -2470,10 +2470,14 @@ onboarding.header=Welcome to SonarQube! | |||
onboarding.header.sonarcloud=Welcome to SonarCloud! | |||
onboarding.header.description=Want to quickly analyze a first project? Follow these {0} easy steps. | |||
onboarding.token.header=Generate a token | |||
onboarding.token.header=Provide a token | |||
onboarding.token.text=The token is used to identify you when an analysis is performed. If it has been compromised, you can revoke it at any point of time in your user account. | |||
onboarding.token.generate=Generate | |||
onboarding.token.placeholder=Enter a name for your token | |||
onboading.token.generate_token=Generate a token | |||
onboading.token.generate_token.placeholder=Enter a name for your token | |||
onboarding.token.use_existing_token=Use existing token | |||
onboarding.token.use_existing_token.placeholder=Enter your existing token | |||
onboarding.organization.header=Choose an organization for your project | |||
onboarding.organization.text=Organizations are where your projects belong. You can add your team members to your organization later to allow them to contribute to your projects. |