diff options
author | Revanshu Paliwal <revanshu.paliwal@sonarsource.com> | 2023-08-10 15:52:49 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-08-14 20:02:57 +0000 |
commit | 18bc11f5f32d19837975c7cd1cf014e26a2cdafe (patch) | |
tree | 4ff016c1936e5d5956a0ef599c1dc9b79a228a3e /server | |
parent | ee27a13ee434d73973a16ae313d2d1447046d22a (diff) | |
download | sonarqube-18bc11f5f32d19837975c7cd1cf014e26a2cdafe.tar.gz sonarqube-18bc11f5f32d19837975c7cd1cf014e26a2cdafe.zip |
SONAR-20086 Migrating personal access token screen to adapt new designs
Diffstat (limited to 'server')
14 files changed, 727 insertions, 526 deletions
diff --git a/server/sonar-web/src/main/js/apps/create/project/Azure/AzurePersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/Azure/AzurePersonalAccessTokenForm.tsx index c3d621863ae..3f71739e892 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Azure/AzurePersonalAccessTokenForm.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Azure/AzurePersonalAccessTokenForm.tsx @@ -17,14 +17,18 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; +import { + ButtonPrimary, + DeferredSpinner, + FlagErrorIcon, + FlagMessage, + FormField, + InputField, + LightPrimary, + Link, +} from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../../components/common/Link'; -import ValidationInput from '../../../../components/controls/ValidationInput'; -import { SubmitButton } from '../../../../components/controls/buttons'; -import { Alert } from '../../../../components/ui/Alert'; -import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; import { translate } from '../../../../helpers/l10n'; import { AlmSettingsInstance } from '../../../../types/alm-settings'; @@ -42,7 +46,7 @@ function getAzurePatUrl(url: string) { export default function AzurePersonalAccessTokenForm(props: AzurePersonalAccessTokenFormProps) { const { - almSetting: { alm, url }, + almSetting: { url }, submitting = false, validationFailed, firstConnection, @@ -61,79 +65,92 @@ export default function AzurePersonalAccessTokenForm(props: AzurePersonalAccessT if (!token) { errorMessage = translate('onboarding.create_project.pat_form.pat_required'); } else if (isInvalid) { - errorMessage = translate('onboarding.create_project.pat_incorrect', alm); + errorMessage = translate('onboarding.create_project.pat_incorrect.azure'); } return ( - <div className="boxed-group abs-width-600"> - <div className="boxed-group-inner"> - <h2>{translate('onboarding.create_project.pat_form.title', alm)}</h2> + <form + className="sw-mt-3 sw-w-[50%]" + onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => { + e.preventDefault(); + props.onPersonalAccessTokenCreate(token); + }} + > + <LightPrimary as="h2" className="sw-heading-md"> + {translate('onboarding.create_project.pat_form.title')} + </LightPrimary> + <LightPrimary as="p" className="sw-mt-2 sw-mb-4 sw-body-sm"> + {translate('onboarding.create_project.pat_form.help.azure')} + </LightPrimary> - <div className="big-spacer-top big-spacer-bottom"> - <FormattedMessage - id="onboarding.create_project.pat_help.instructions" - defaultMessage={translate('onboarding.create_project.pat_help.instructions', alm)} - values={{ - link: url ? ( - <Link className="link-no-underline" to={getAzurePatUrl(url)} target="_blank"> - {translate('onboarding.create_project.pat_help.instructions.link', alm)} - </Link> - ) : ( - translate('onboarding.create_project.pat_help.instructions.link', alm) - ), - scope: ( - <strong> - <em>Code (Read & Write)</em> - </strong> - ), - }} - /> + {isInvalid && ( + <div> + <FlagMessage variant="error" className="sw-mb-4"> + <p>{errorMessage}</p> + </FlagMessage> </div> + )} - {!firstConnection && ( - <Alert className="big-spacer-right" variant="warning"> - <p>{translate('onboarding.create_project.pat.expired.info_message')}</p> - <p>{translate('onboarding.create_project.pat.expired.info_message_contact')}</p> - </Alert> - )} + {!firstConnection && ( + <FlagMessage variant="warning"> + <p> + {translate('onboarding.create_project.pat.expired.info_message')}{' '} + {translate('onboarding.create_project.pat.expired.info_message_contact')} + </p> + </FlagMessage> + )} - <form - onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => { - e.preventDefault(); - props.onPersonalAccessTokenCreate(token); - }} - > - <ValidationInput - error={errorMessage} - labelHtmlFor="personal_access_token" + <FormField + htmlFor="personal_access_token" + className="sw-mt-6 sw-mb-3" + label={translate('onboarding.create_project.enter_pat')} + required + > + <div> + <InputField + autoFocus + id="personal_access_token" + minLength={1} + name="personal_access_token" + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + setToken(e.target.value); + setTouched(true); + }} + type="text" + value={token} + size="large" isInvalid={isInvalid} - isValid={false} - label={translate('onboarding.create_project.enter_pat')} - required - > - <input - autoFocus - className={classNames('width-100 little-spacer-bottom', { - 'is-invalid': isInvalid, - })} - id="personal_access_token" - minLength={1} - name="personal_access_token" - onChange={(e: React.ChangeEvent<HTMLInputElement>) => { - setToken(e.target.value); - setTouched(true); + /> + {isInvalid && <FlagErrorIcon className="sw-ml-2" />} + </div> + </FormField> + + <div className="sw-mb-6"> + <FlagMessage variant="info"> + <p> + <FormattedMessage + id="onboarding.create_project.pat_help.instructions.azure" + defaultMessage={translate('onboarding.create_project.pat_help.instructions.azure')} + values={{ + link: url ? ( + <Link to={getAzurePatUrl(url)}> + {translate('onboarding.create_project.pat_help.instructions.link.azure')} + </Link> + ) : ( + translate('onboarding.create_project.pat_help.instructions.link.azure') + ), }} - type="text" - value={token} /> - </ValidationInput> + </p> + </FlagMessage> + </div> - <SubmitButton disabled={isInvalid || submitting || !touched}> - {translate('onboarding.create_project.pat_form.list_repositories')} - </SubmitButton> - <DeferredSpinner className="spacer-left" loading={submitting} /> - </form> + <div className="sw-flex sw-items-center sw-mb-6"> + <ButtonPrimary type="submit" disabled={isInvalid || submitting || !touched}> + {translate('save')} + </ButtonPrimary> + <DeferredSpinner className="sw-ml-2" loading={submitting} /> </div> - </div> + </form> ); } diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudPersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudPersonalAccessTokenForm.tsx new file mode 100644 index 00000000000..e703e78bef6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudPersonalAccessTokenForm.tsx @@ -0,0 +1,185 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { + ButtonPrimary, + DeferredSpinner, + FlagErrorIcon, + FlagMessage, + FormField, + InputField, + LightPrimary, + Link, +} from 'design-system'; +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { translate } from '../../../../helpers/l10n'; +import { AlmSettingsInstance } from '../../../../types/alm-settings'; +import { usePersonalAccessToken } from '../usePersonalAccessToken'; + +interface Props { + almSetting: AlmSettingsInstance; + resetPat: boolean; + onPersonalAccessTokenCreated: () => void; +} + +export default function BitbucketCloudPersonalAccessTokenForm({ + almSetting, + resetPat, + onPersonalAccessTokenCreated, +}: Props) { + const { + username, + password, + firstConnection, + validationFailed, + touched, + submitting, + validationErrorMessage, + checkingPat, + handlePasswordChange, + handleUsernameChange, + handleSubmit, + } = usePersonalAccessToken(almSetting, resetPat, onPersonalAccessTokenCreated); + + if (checkingPat) { + return <DeferredSpinner className="sw-ml-2" loading />; + } + + const isInvalid = validationFailed && !touched; + const canSubmit = Boolean(password) && Boolean(username); + const submitButtonDiabled = isInvalid || submitting || !canSubmit; + + const errorMessage = + validationErrorMessage ?? translate('onboarding.create_project.pat_incorrect.bitbucket_cloud'); + + return ( + <form className="sw-mt-3 sw-w-[50%]" onSubmit={handleSubmit}> + <LightPrimary as="h2" className="sw-heading-md"> + {translate('onboarding.create_project.pat_form.title')} + </LightPrimary> + <LightPrimary as="p" className="sw-mt-2 sw-mb-4 sw-body-sm"> + {translate('onboarding.create_project.pat_form.help.bitbucket_cloud')} + </LightPrimary> + + {isInvalid && ( + <div> + <FlagMessage variant="error" className="sw-mb-4"> + <p>{errorMessage}</p> + </FlagMessage> + </div> + )} + + {!firstConnection && ( + <FlagMessage variant="warning"> + <p> + {translate('onboarding.create_project.pat.expired.info_message')}{' '} + {translate('onboarding.create_project.pat.expired.info_message_contact')} + </p> + </FlagMessage> + )} + + <FormField + htmlFor="enter_username_validation" + className="sw-mt-6 sw-mb-3" + label={translate('onboarding.create_project.bitbucket_cloud.enter_username')} + required + > + <div> + <InputField + size="large" + id="enter_username_validation" + minLength={1} + value={username} + onChange={handleUsernameChange} + type="text" + isInvalid={isInvalid} + /> + {isInvalid && <FlagErrorIcon className="sw-ml-2" />} + </div> + </FormField> + + <div className="sw-mb-6"> + <FlagMessage variant="info"> + <p> + <FormattedMessage + id="onboarding.enter_username.instructions.bitbucket_cloud" + defaultMessage={translate('onboarding.enter_username.instructions.bitbucket_cloud')} + values={{ + link: ( + <Link to="https://bitbucket.org/account/settings/"> + {translate('onboarding.enter_username.instructions.bitbucket_cloud.link')} + </Link> + ), + }} + /> + </p> + </FlagMessage> + </div> + + <FormField + htmlFor="enter_password_validation" + className="sw-mt-6 sw-mb-3" + label={translate('onboarding.create_project.bitbucket_cloud.enter_password')} + required + > + <div> + <InputField + size="large" + id="enter_password_validation" + minLength={1} + value={password} + onChange={handlePasswordChange} + type="text" + isInvalid={isInvalid} + /> + {isInvalid && <FlagErrorIcon className="sw-ml-2" />} + </div> + </FormField> + + <div className="sw-mb-6"> + <FlagMessage variant="info"> + <p> + <FormattedMessage + id="onboarding.create_project.enter_password.instructions.bitbucket_cloud" + defaultMessage={translate( + 'onboarding.create_project.enter_password.instructions.bitbucket_cloud' + )} + values={{ + link: ( + <Link to="https://bitbucket.org/account/settings/app-passwords/new"> + {translate( + 'onboarding.create_project.enter_password.instructions.bitbucket_cloud.link' + )} + </Link> + ), + }} + /> + </p> + </FlagMessage> + </div> + + <ButtonPrimary type="submit" disabled={submitButtonDiabled} className="sw-mb-6"> + {translate('save')} + </ButtonPrimary> + <DeferredSpinner className="sw-ml-2" loading={submitting} /> + </form> + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx index 992f8046e4f..8cd8154af31 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx @@ -23,8 +23,8 @@ import { translate } from '../../../../helpers/l10n'; import { BitbucketCloudRepository } from '../../../../types/alm-integration'; import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown'; -import PersonalAccessTokenForm from '../components/PersonalAccessTokenForm'; import WrongBindingCountAlert from '../components/WrongBindingCountAlert'; +import BitbucketCloudPersonalAccessTokenForm from './BitbucketCloudPersonalAccessTokenForm'; import BitbucketCloudSearchForm from './BitbucketCloudSearchForm'; export interface BitbucketCloudProjectCreateRendererProps { @@ -90,7 +90,7 @@ export default function BitbucketCloudProjectCreateRenderer( {!loading && selectedAlmInstance && (showPersonalAccessTokenForm ? ( - <PersonalAccessTokenForm + <BitbucketCloudPersonalAccessTokenForm almSetting={selectedAlmInstance} resetPat={resetPat} onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated} diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectCreateRenderer.tsx index 0b435d15b5d..f6b4caeedd9 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectCreateRenderer.tsx @@ -27,9 +27,9 @@ import { } from '../../../../types/alm-integration'; import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown'; -import PersonalAccessTokenForm from '../components/PersonalAccessTokenForm'; import WrongBindingCountAlert from '../components/WrongBindingCountAlert'; import BitbucketImportRepositoryForm from './BitbucketImportRepositoryForm'; +import BitbucketServerPersonalAccessTokenForm from './BitbucketServerPersonalAccessTokenForm'; export interface BitbucketProjectCreateRendererProps { selectedAlmInstance?: AlmSettingsInstance; @@ -88,7 +88,7 @@ export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCr {selectedAlmInstance && (showPersonalAccessTokenForm ? ( - <PersonalAccessTokenForm + <BitbucketServerPersonalAccessTokenForm almSetting={selectedAlmInstance} onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated} resetPat={resetPat} diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketServerPersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketServerPersonalAccessTokenForm.tsx new file mode 100644 index 00000000000..914cee82adc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketServerPersonalAccessTokenForm.tsx @@ -0,0 +1,149 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { + ButtonPrimary, + DeferredSpinner, + FlagErrorIcon, + FlagMessage, + FormField, + InputField, + LightPrimary, + Link, +} from 'design-system'; +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { translate } from '../../../../helpers/l10n'; +import { AlmSettingsInstance } from '../../../../types/alm-settings'; +import { usePersonalAccessToken } from '../usePersonalAccessToken'; + +interface Props { + almSetting: AlmSettingsInstance; + resetPat: boolean; + onPersonalAccessTokenCreated: () => void; +} + +export default function BitbucketServerPersonalAccessTokenForm({ + almSetting, + resetPat, + onPersonalAccessTokenCreated, +}: Props) { + const { + password, + firstConnection, + validationFailed, + touched, + submitting, + validationErrorMessage, + checkingPat, + handlePasswordChange, + handleSubmit, + } = usePersonalAccessToken(almSetting, resetPat, onPersonalAccessTokenCreated); + + if (checkingPat) { + return <DeferredSpinner className="sw-ml-2" loading />; + } + + const { url } = almSetting; + const isInvalid = validationFailed && !touched; + const canSubmit = Boolean(password); + const submitButtonDiabled = isInvalid || submitting || !canSubmit; + + const errorMessage = + validationErrorMessage ?? translate('onboarding.create_project.pat_incorrect.bitbucket'); + + return ( + <form className="sw-mt-3 sw-w-[50%]" onSubmit={handleSubmit}> + <LightPrimary as="h2" className="sw-heading-md"> + {translate('onboarding.create_project.pat_form.title')} + </LightPrimary> + <LightPrimary as="p" className="sw-mt-2 sw-mb-4 sw-body-sm"> + {translate('onboarding.create_project.pat_form.help.bitbucket')} + </LightPrimary> + + {isInvalid && ( + <div> + <FlagMessage variant="error" className="sw-mb-4"> + <p>{errorMessage}</p> + </FlagMessage> + </div> + )} + + {!firstConnection && ( + <FlagMessage variant="warning"> + <p> + {translate('onboarding.create_project.pat.expired.info_message')}{' '} + {translate('onboarding.create_project.pat.expired.info_message_contact')} + </p> + </FlagMessage> + )} + + <FormField + htmlFor="personal_access_token_validation" + className="sw-mt-6 sw-mb-3" + label={translate('onboarding.create_project.enter_pat')} + required + > + <div> + <InputField + autoFocus + size="large" + id="personal_access_token_validation" + minLength={1} + value={password} + onChange={handlePasswordChange} + type="text" + isInvalid={isInvalid} + /> + {isInvalid && <FlagErrorIcon className="sw-ml-2" />} + </div> + </FormField> + + <div className="sw-mb-6"> + <FlagMessage variant="info"> + <p> + <FormattedMessage + id="onboarding.create_project.pat_help.instructions.bitbucket_server" + defaultMessage={translate( + 'onboarding.create_project.pat_help.instructions.bitbucket_server' + )} + values={{ + link: url ? ( + <Link to={`${url.replace(/\/$/, '')}/account`}> + {translate( + 'onboarding.create_project.pat_help.instructions.bitbucket_server.link' + )} + </Link> + ) : ( + translate('onboarding.create_project.pat_help.instructions.bitbucket_server.link') + ), + }} + /> + </p> + </FlagMessage> + </div> + + <ButtonPrimary type="submit" disabled={submitButtonDiabled} className="sw-mb-6"> + {translate('save')} + </ButtonPrimary> + <DeferredSpinner className="sw-ml-2" loading={submitting} /> + </form> + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/Gitlab/GItlabPersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GItlabPersonalAccessTokenForm.tsx new file mode 100644 index 00000000000..2b12662b9dd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GItlabPersonalAccessTokenForm.tsx @@ -0,0 +1,142 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { + ButtonPrimary, + DeferredSpinner, + FlagErrorIcon, + FlagMessage, + FormField, + InputField, + LightPrimary, + Link, +} from 'design-system'; +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { translate } from '../../../../helpers/l10n'; +import { AlmSettingsInstance } from '../../../../types/alm-settings'; +import { usePersonalAccessToken } from '../usePersonalAccessToken'; + +interface Props { + almSetting: AlmSettingsInstance; + resetPat: boolean; + onPersonalAccessTokenCreated: () => void; +} + +export default function GitlabPersonalAccessTokenForm({ + almSetting, + resetPat, + onPersonalAccessTokenCreated, +}: Props) { + const { + password, + firstConnection, + validationFailed, + touched, + submitting, + validationErrorMessage, + checkingPat, + handlePasswordChange, + handleSubmit, + } = usePersonalAccessToken(almSetting, resetPat, onPersonalAccessTokenCreated); + + if (checkingPat) { + return <DeferredSpinner className="sw-ml-2" loading />; + } + + const isInvalid = validationFailed && !touched; + const canSubmit = Boolean(password); + const submitButtonDiabled = isInvalid || submitting || !canSubmit; + + const errorMessage = + validationErrorMessage ?? translate('onboarding.create_project.pat_incorrect.gitlab'); + + return ( + <form className="sw-mt-3 sw-w-[50%]" onSubmit={handleSubmit}> + <LightPrimary as="h2" className="sw-heading-md"> + {translate('onboarding.create_project.pat_form.title')} + </LightPrimary> + <LightPrimary as="p" className="sw-mt-2 sw-mb-4 sw-body-sm"> + {translate('onboarding.create_project.pat_form.help.gitlab')} + </LightPrimary> + + {isInvalid && ( + <div> + <FlagMessage variant="error" className="sw-mb-4"> + <p>{errorMessage}</p> + </FlagMessage> + </div> + )} + + {!firstConnection && ( + <FlagMessage variant="warning"> + <p> + {translate('onboarding.create_project.pat.expired.info_message')}{' '} + {translate('onboarding.create_project.pat.expired.info_message_contact')} + </p> + </FlagMessage> + )} + + <FormField + htmlFor="personal_access_token_validation" + className="sw-mt-6 sw-mb-3" + label={translate('onboarding.create_project.enter_pat')} + required + > + <div> + <InputField + autoFocus + size="large" + id="personal_access_token_validation" + minLength={1} + value={password} + onChange={handlePasswordChange} + type="text" + isInvalid={isInvalid} + /> + {isInvalid && <FlagErrorIcon className="sw-ml-2" />} + </div> + </FormField> + + <div className="sw-mb-6"> + <FlagMessage variant="info"> + <p> + <FormattedMessage + id="onboarding.create_project.pat_help.instructions.gitlab" + defaultMessage={translate('onboarding.create_project.pat_help.instructions.gitlab')} + values={{ + link: ( + <Link to="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html"> + {translate('onboarding.create_project.pat_help.instructions.gitlab.link')} + </Link> + ), + }} + /> + </p> + </FlagMessage> + </div> + + <ButtonPrimary type="submit" disabled={submitButtonDiabled} className="sw-mb-6"> + {translate('save')} + </ButtonPrimary> + <DeferredSpinner className="sw-ml-2" loading={submitting} /> + </form> + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreateRenderer.tsx index 5bbf169a8e5..0b843afff80 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreateRenderer.tsx @@ -25,8 +25,8 @@ import { GitlabProject } from '../../../../types/alm-integration'; import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; import { Paging } from '../../../../types/types'; import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown'; -import PersonalAccessTokenForm from '../components/PersonalAccessTokenForm'; import WrongBindingCountAlert from '../components/WrongBindingCountAlert'; +import GitlabPersonalAccessTokenForm from './GItlabPersonalAccessTokenForm'; import GitlabProjectSelectionForm from './GitlabProjectSelectionForm'; export interface GitlabProjectCreateRendererProps { @@ -88,7 +88,7 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe {!loading && selectedAlmInstance && (showPersonalAccessTokenForm ? ( - <PersonalAccessTokenForm + <GitlabPersonalAccessTokenForm almSetting={selectedAlmInstance} resetPat={resetPat} onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx index 3460c7656d5..db9b1a3b2ba 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx @@ -74,16 +74,12 @@ it('should ask for PAT when it is not set yet and show the import project featur expect(screen.getByText('alm.configuration.selector.label.alm.azure.long')).toBeInTheDocument(); expect(screen.getByText('onboarding.create_project.enter_pat')).toBeInTheDocument(); - expect(screen.getByText('onboarding.create_project.pat_form.title.azure')).toBeInTheDocument(); - expect( - screen.getByRole('button', { name: 'onboarding.create_project.pat_form.list_repositories' }) - ).toBeInTheDocument(); + expect(screen.getByText('onboarding.create_project.pat_form.title')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'save' })).toBeInTheDocument(); await user.click(ui.personalAccessTokenInput.get()); await user.keyboard('secret'); - await user.click( - screen.getByRole('button', { name: 'onboarding.create_project.pat_form.list_repositories' }) - ); + await user.click(screen.getByRole('button', { name: 'save' })); expect(screen.getByText('Azure project')).toBeInTheDocument(); expect(screen.getByText('Azure project 2')).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx index 6b807c89485..8a9ace4db82 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx @@ -74,9 +74,7 @@ it('should ask for PAT when it is not set yet and show the import project featur expect(screen.getByText('onboarding.create_project.from_bbs')).toBeInTheDocument(); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - expect( - screen.getByText('onboarding.create_project.pat_form.title.bitbucket') - ).toBeInTheDocument(); + expect(screen.getByText('onboarding.create_project.pat_form.title')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'save' })).toBeDisabled(); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx index af4d30aa07c..9cd94893473 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx @@ -79,26 +79,23 @@ it('should ask for PAT when it is not set yet and show the import project featur expect(await ui.instanceSelector.find()).toBeInTheDocument(); expect( - screen.getByText('onboarding.create_project.enter_pat.bitbucketcloud') + screen.getByText('onboarding.create_project.bitbucket_cloud.enter_password') ).toBeInTheDocument(); expect( - screen.getByText( - 'onboarding.create_project.pat_help.instructions_username.bitbucketcloud.title' - ) + screen.getByText('onboarding.create_project.enter_password.instructions.bitbucket_cloud') ).toBeInTheDocument(); expect( - screen.getByText('onboarding.create_project.pat.expired.info_message') - ).toBeInTheDocument(); - expect( - screen.getByText('onboarding.create_project.pat.expired.info_message_contact') + screen.getByText( + 'onboarding.create_project.pat.expired.info_message onboarding.create_project.pat.expired.info_message_contact' + ) ).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'save' })).toBeDisabled(); await user.click( screen.getByRole('textbox', { - name: /onboarding.create_project.enter_username/, + name: /onboarding.create_project.bitbucket_cloud.enter_username/, }) ); @@ -106,7 +103,7 @@ it('should ask for PAT when it is not set yet and show the import project featur await user.click( screen.getByRole('textbox', { - name: /onboarding.create_project.enter_pat.bitbucketcloud/, + name: /onboarding.create_project.bitbucket_cloud.enter_password/, }) ); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx index cc549475982..1fa6e4ee751 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx @@ -76,7 +76,9 @@ it('should ask for PAT when it is not set yet and show the import project featur expect(ui.instanceSelector.get()).toBeInTheDocument(); expect(screen.getByText('onboarding.create_project.enter_pat')).toBeInTheDocument(); - expect(screen.getByText('onboarding.create_project.pat_help.title')).toBeInTheDocument(); + expect( + screen.getByText('onboarding.create_project.pat_help.instructions.gitlab') + ).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'save' })).toBeInTheDocument(); await act(async () => { await user.click(ui.personalAccessTokenInput.get()); diff --git a/server/sonar-web/src/main/js/apps/create/project/components/AlmRepoItem.tsx b/server/sonar-web/src/main/js/apps/create/project/components/AlmRepoItem.tsx index af61167b9d2..4b341da893b 100644 --- a/server/sonar-web/src/main/js/apps/create/project/components/AlmRepoItem.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/components/AlmRepoItem.tsx @@ -83,8 +83,8 @@ export default function AlmRepoItem({ </LightPrimary> )} </div> - <div className="sw-max-w-[50%] sw-min-w-0 sw-ml-2 sw-flex sw-items-center sw-truncate"> - <LightLabel className="sw-body-sm">{secondaryTextNode}</LightLabel> + <div className="sw-max-w-[50%] sw-min-w-0 sw-ml-2 sw-flex sw-items-center"> + <LightLabel className="sw-body-sm sw-truncate">{secondaryTextNode}</LightLabel> </div> </div> {almUrl !== undefined && ( diff --git a/server/sonar-web/src/main/js/apps/create/project/components/PersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/components/PersonalAccessTokenForm.tsx deleted file mode 100644 index c7ebeba3cfb..00000000000 --- a/server/sonar-web/src/main/js/apps/create/project/components/PersonalAccessTokenForm.tsx +++ /dev/null @@ -1,428 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { - checkPersonalAccessTokenIsValid, - setAlmPersonalAccessToken, -} from '../../../../api/alm-integrations'; -import ValidationInput from '../../../../components/controls/ValidationInput'; -import { SubmitButton } from '../../../../components/controls/buttons'; -import { Alert } from '../../../../components/ui/Alert'; -import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; -import { translate } from '../../../../helpers/l10n'; -import { getBaseUrl } from '../../../../helpers/system'; -import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; -import { tokenExistedBefore } from '../utils'; - -interface Props { - almSetting: AlmSettingsInstance; - resetPat: boolean; - onPersonalAccessTokenCreated: () => void; -} - -interface State { - validationFailed: boolean; - validationErrorMessage?: string; - touched: boolean; - password: string; - username?: string; - submitting: boolean; - checkingPat: boolean; - firstConnection: boolean; -} - -function getPatUrl(alm: AlmKeys, url = '') { - if (alm === AlmKeys.BitbucketServer) { - return `${url.replace(/\/$/, '')}/account`; - } else if (alm === AlmKeys.BitbucketCloud) { - return 'https://bitbucket.org/account/settings/app-passwords/new'; - } else if (alm === AlmKeys.GitLab) { - return 'https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html'; - } - - return ''; -} - -export default class PersonalAccessTokenForm extends React.PureComponent<Props, State> { - mounted = false; - - constructor(props: Props) { - super(props); - - this.state = { - checkingPat: false, - touched: false, - password: '', - submitting: false, - validationFailed: false, - firstConnection: false, - }; - } - - componentDidMount() { - this.mounted = true; - this.checkPATAndUpdateView(); - } - - componentDidUpdate(prevProps: Props) { - if (this.props.almSetting !== prevProps.almSetting) { - this.checkPATAndUpdateView(); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - checkPATAndUpdateView = async () => { - const { - almSetting: { key }, - resetPat, - } = this.props; - - // We don't need to check PAT if we want to reset - if (!resetPat) { - this.setState({ checkingPat: true }); - const { patIsValid, error } = await checkPersonalAccessTokenIsValid(key) - .then(({ status, error }) => ({ patIsValid: status, error })) - .catch(() => ({ patIsValid: status, error: translate('default_error_message') })); - if (patIsValid) { - this.props.onPersonalAccessTokenCreated(); - } - if (this.mounted) { - // This is the initial message when no token was provided - if (tokenExistedBefore(error)) { - this.setState({ - checkingPat: false, - firstConnection: true, - }); - } else { - this.setState({ - checkingPat: false, - validationFailed: true, - validationErrorMessage: error, - }); - } - } - } - }; - - handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => { - this.setState({ - touched: true, - username: event.target.value, - }); - }; - - handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => { - this.setState({ - touched: true, - password: event.target.value, - }); - }; - - handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => { - const { password, username } = this.state; - const { - almSetting: { key }, - } = this.props; - - e.preventDefault(); - if (password) { - this.setState({ submitting: true }); - - await setAlmPersonalAccessToken(key, password, username).catch(() => { - /* Set will not check pat validity. We need to check again so we will catch issue after */ - }); - - const { status, error } = await checkPersonalAccessTokenIsValid(key) - .then(({ status, error }) => ({ status, error })) - .catch(() => ({ status: false, error: translate('default_error_message') })); - - if (this.mounted && status) { - // Let's reset status, - this.setState({ - checkingPat: false, - touched: false, - password: '', - submitting: false, - username: '', - validationFailed: false, - }); - this.props.onPersonalAccessTokenCreated(); - } else if (this.mounted) { - this.setState({ - submitting: false, - touched: false, - validationFailed: true, - validationErrorMessage: error, - }); - } - } - }; - - renderHelpBox(suffixTranslationKey: string) { - const { - almSetting: { alm, url }, - } = this.props; - - return ( - <Alert className="big-spacer-left width-50" display="block" variant="info"> - {alm === AlmKeys.BitbucketCloud && ( - <> - <h3> - {translate( - 'onboarding.create_project.pat_help.instructions_username.bitbucketcloud.title' - )} - </h3> - <p className="big-spacer-top big-spacer-bottom"> - {translate('onboarding.create_project.pat_help.instructions_username.bitbucketcloud')} - </p> - - <div className="text-middle big-spacer-bottom"> - <img - alt="" // Should be ignored by screen readers - className="spacer-right" - height="16" - src={`${getBaseUrl()}/images/alm/${AlmKeys.BitbucketServer}.svg`} - /> - <a - href="https://bitbucket.org/account/settings/" - rel="noopener noreferrer" - target="_blank" - > - {translate( - 'onboarding.create_project.pat_help.instructions_username.bitbucketcloud.link' - )} - </a> - </div> - </> - )} - - <h3>{translate(`onboarding.create_project.pat_help${suffixTranslationKey}.title`)}</h3> - - <p className="big-spacer-top big-spacer-bottom"> - {alm === AlmKeys.BitbucketServer ? ( - <FormattedMessage - id="onboarding.create_project.pat_help.instructions" - defaultMessage={translate( - `onboarding.create_project.pat_help.bitbucket.instructions` - )} - values={{ - menu: ( - <strong> - {translate('onboarding.create_project.pat_help.bitbucket.instructions.menu')} - </strong> - ), - button: ( - <strong> - {translate('onboarding.create_project.pat_help.bitbucket.instructions.button')} - </strong> - ), - }} - /> - ) : ( - <FormattedMessage - id="onboarding.create_project.pat_help.instructions" - defaultMessage={translate( - `onboarding.create_project.pat_help${suffixTranslationKey}.instructions` - )} - values={{ - alm: translate('onboarding.alm', alm), - }} - /> - )} - </p> - - {(url || alm === AlmKeys.BitbucketCloud) && ( - <div className="text-middle"> - <img - alt="" // Should be ignored by screen readers - className="spacer-right" - height="16" - src={`${getBaseUrl()}/images/alm/${ - alm === AlmKeys.BitbucketCloud ? AlmKeys.BitbucketServer : alm - }.svg`} - /> - <a href={getPatUrl(alm, url)} rel="noopener noreferrer" target="_blank"> - {translate(`onboarding.create_project.pat_help${suffixTranslationKey}.link`)} - </a> - </div> - )} - - <p className="big-spacer-top big-spacer-bottom"> - {translate('onboarding.create_project.pat_help.instructions2', alm)} - </p> - - <ul> - {alm === AlmKeys.BitbucketServer && ( - <li> - <FormattedMessage - defaultMessage={translate( - 'onboarding.create_project.pat_help.bbs_permission_projects' - )} - id="onboarding.create_project.pat_help.bbs_permission_projects" - values={{ - perm: ( - <strong> - {translate('onboarding.create_project.pat_help.read_permission')} - </strong> - ), - }} - /> - </li> - )} - {(alm === AlmKeys.BitbucketServer || alm === AlmKeys.BitbucketCloud) && ( - <li> - <FormattedMessage - defaultMessage={translate( - 'onboarding.create_project.pat_help.bbs_permission_repos' - )} - id="onboarding.create_project.pat_help.bbs_permission_repos" - values={{ - perm: ( - <strong> - {translate('onboarding.create_project.pat_help.read_permission')} - </strong> - ), - }} - /> - </li> - )} - - {alm === AlmKeys.GitLab && ( - <li className="spacer-bottom"> - <strong> - {translate('onboarding.create_project.pat_help.gitlab.read_api_permission')} - </strong> - </li> - )} - </ul> - </Alert> - ); - } - - render() { - const { - almSetting: { alm }, - } = this.props; - const { - checkingPat, - submitting, - touched, - password, - username, - validationFailed, - validationErrorMessage, - firstConnection, - } = this.state; - - if (checkingPat) { - return <DeferredSpinner className="spacer-left" loading />; - } - - const suffixTranslationKey = alm === AlmKeys.BitbucketCloud ? '.bitbucketcloud' : ''; - - const isInvalid = validationFailed && !touched; - const canSubmit = Boolean(password) && (alm !== AlmKeys.BitbucketCloud || Boolean(username)); - const submitButtonDiabled = isInvalid || submitting || !canSubmit; - - const errorMessage = - validationErrorMessage ?? translate('onboarding.create_project.pat_incorrect', alm); - - return ( - <div className="display-flex-start"> - <form className="width-50" onSubmit={this.handleSubmit}> - <h2 className="big">{translate('onboarding.create_project.pat_form.title', alm)}</h2> - <p className="big-spacer-top big-spacer-bottom"> - {translate('onboarding.create_project.pat_form.help', alm)} - </p> - - {!firstConnection && ( - <Alert className="big-spacer-right" variant="warning"> - <p>{translate('onboarding.create_project.pat.expired.info_message')}</p> - <p>{translate('onboarding.create_project.pat.expired.info_message_contact')}</p> - </Alert> - )} - - {alm === AlmKeys.BitbucketCloud && ( - <ValidationInput - error={undefined} - labelHtmlFor="enter_username_validation" - isInvalid={false} - isValid={false} - label={translate('onboarding.create_project.enter_username')} - required - > - <input - autoFocus - className={classNames('input-super-large', { - 'is-invalid': isInvalid, - })} - id="enter_username_validation" - minLength={1} - name="username" - value={username} - onChange={this.handleUsernameChange} - type="text" - /> - </ValidationInput> - )} - - <ValidationInput - error={errorMessage} - labelHtmlFor="personal_access_token_validation" - isInvalid={false} - isValid={false} - label={translate(`onboarding.create_project.enter_pat${suffixTranslationKey}`)} - required - > - <input - autoFocus={alm !== AlmKeys.BitbucketCloud} - className={classNames('input-super-large', { - 'is-invalid': isInvalid, - })} - id="personal_access_token_validation" - minLength={1} - value={password} - onChange={this.handlePasswordChange} - type="text" - /> - </ValidationInput> - - <ValidationInput - error={errorMessage} - labelHtmlFor="personal_access_token_submit" - isInvalid={isInvalid} - isValid={false} - label={null} - > - <SubmitButton disabled={submitButtonDiabled}>{translate('save')}</SubmitButton> - <DeferredSpinner className="spacer-left" loading={submitting} /> - </ValidationInput> - </form> - - {this.renderHelpBox(suffixTranslationKey)} - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/project/usePersonalAccessToken.ts b/server/sonar-web/src/main/js/apps/create/project/usePersonalAccessToken.ts new file mode 100644 index 00000000000..ab4c5e2779d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/usePersonalAccessToken.ts @@ -0,0 +1,143 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { useEffect, useState } from 'react'; +import { + checkPersonalAccessTokenIsValid, + setAlmPersonalAccessToken, +} from '../../../api/alm-integrations'; +import { translate } from '../../../helpers/l10n'; +import { AlmSettingsInstance } from '../../../types/alm-settings'; +import { tokenExistedBefore } from './utils'; + +export interface PATType { + validationFailed: boolean; + validationErrorMessage?: string; + touched: boolean; + password: string; + username?: string; + submitting: boolean; + checkingPat: boolean; + firstConnection: boolean; + handleUsernameChange: (event: React.ChangeEvent<HTMLInputElement>) => void; + handlePasswordChange: (event: React.ChangeEvent<HTMLInputElement>) => void; + handleSubmit: (e: React.SyntheticEvent<HTMLFormElement>) => Promise<void>; +} + +export const usePersonalAccessToken = ( + almSetting: AlmSettingsInstance, + resetPat: boolean, + onPersonalAccessTokenCreated: () => void +): PATType => { + const [checkingPat, setCheckingPat] = useState(false); + const [touched, setTouched] = useState(false); + const [password, setPassword] = useState(''); + const [submitting, setSubmitting] = useState(false); + const [validationFailed, setValidationFailed] = useState(false); + const [validationErrorMessage, setValidationErrorMessage] = useState<string | undefined>(); + const [firstConnection, setFirstConnection] = useState(false); + const [username, setUsername] = useState(''); + + useEffect(() => { + const checkPATAndUpdateView = async () => { + const { key } = almSetting; + + // We don't need to check PAT if we want to reset + if (!resetPat) { + setCheckingPat(true); + const { patIsValid, error } = await checkPersonalAccessTokenIsValid(key) + .then(({ status, error }) => ({ patIsValid: status, error })) + .catch(() => ({ patIsValid: status, error: translate('default_error_message') })); + if (patIsValid) { + onPersonalAccessTokenCreated(); + return; + } + // This is the initial message when no token was provided + if (tokenExistedBefore(error)) { + setCheckingPat(false); + setFirstConnection(true); + } else { + setCheckingPat(false); + setValidationFailed(true); + setValidationErrorMessage(error); + } + } + }; + checkPATAndUpdateView(); + }, [almSetting, resetPat, onPersonalAccessTokenCreated]); + + const handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => { + setTouched(true); + setUsername(event.target.value); + }; + + const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => { + setTouched(true); + setPassword(event.target.value); + }; + + const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => { + const { key } = almSetting; + + e.preventDefault(); + if (password) { + setSubmitting(true); + + await setAlmPersonalAccessToken(key, password, username).catch(() => { + /* Set will not check pat validity. We need to check again so we will catch issue after */ + }); + + const { status, error } = await checkPersonalAccessTokenIsValid(key) + .then(({ status, error }) => ({ status, error })) + .catch(() => ({ status: false, error: translate('default_error_message') })); + + if (status) { + // Let's reset status, + setCheckingPat(false); + setTouched(false); + setPassword(''); + setSubmitting(false); + setUsername(''); + setValidationFailed(false); + + onPersonalAccessTokenCreated(); + } else { + setSubmitting(false); + setTouched(false); + setValidationFailed(true); + setValidationErrorMessage(error); + } + } + }; + + return { + username, + password, + firstConnection, + validationFailed, + touched, + submitting, + checkingPat, + validationErrorMessage, + handleUsernameChange, + handlePasswordChange, + handleSubmit, + }; +}; |