* 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, FlagMessage, Link, Title } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import DocLink from '../../components/common/DocLink';
-import { Button } from '../../components/controls/buttons';
-import { Alert } from '../../components/ui/Alert';
+import { useDocUrl } from '../../helpers/docs';
import { translate } from '../../helpers/l10n';
import UserForm from './components/UserForm';
}
export default function Header(props: Props) {
+ const getDocUrl = useDocUrl();
const [openUserForm, setOpenUserForm] = React.useState(false);
const { manageProvider } = props;
return (
- <div className="page-header null-spacer-bottom">
- <h2 className="page-title">{translate('users.page')}</h2>
+ <div>
+ <div className="sw-flex sw-justify-between">
+ <Title>{translate('users.page')}</Title>
- <div className="page-actions">
- <Button
+ <ButtonPrimary
id="users-create"
disabled={manageProvider !== undefined}
onClick={() => setOpenUserForm(true)}
>
{translate('users.create_user')}
- </Button>
+ </ButtonPrimary>
+ </div>
+ <div>
+ {manageProvider === undefined ? (
+ <span>{translate('users.page.description')}</span>
+ ) : (
+ <FlagMessage className="sw-max-w-3/4" variant="info">
+ <span>
+ <FormattedMessage
+ defaultMessage={translate('users.page.managed_description')}
+ id="users.page.managed_description"
+ values={{
+ provider: manageProvider,
+ link: (
+ <Link to={getDocUrl('/instance-administration/authentication/overview/')}>
+ {translate('documentation')}
+ </Link>
+ ),
+ }}
+ />
+ </span>
+ </FlagMessage>
+ )}
</div>
- {manageProvider === undefined ? (
- <p className="page-description">{translate('users.page.description')}</p>
- ) : (
- <Alert className="page-description max-width-100" variant="info">
- <FormattedMessage
- defaultMessage={translate('users.page.managed_description')}
- id="users.page.managed_description"
- values={{
- provider: manageProvider,
- link: (
- <DocLink to="/instance-administration/authentication/overview/">
- {translate('documentation')}
- </DocLink>
- ),
- }}
- />
- </Alert>
- )}
{openUserForm && (
<UserForm onClose={() => setOpenUserForm(false)} isInstanceManaged={false} />
)}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { subDays, subSeconds } from 'date-fns';
+import { HelperHintIcon, InputSearch, InputSelect, StyledPageTitle } from 'design-system';
import React, { useEffect, useMemo, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { getIdentityProviders } from '../../api/users';
import HelpTooltip from '../../components/controls/HelpTooltip';
import ListFooter from '../../components/controls/ListFooter';
import { ManagedFilter } from '../../components/controls/ManagedFilter';
-import SearchBox from '../../components/controls/SearchBox';
-import Select, { LabelValueSelectOption } from '../../components/controls/Select';
+import { LabelValueSelectOption } from '../../components/controls/Select';
import Suggestions from '../../components/embed-docs-modal/Suggestions';
import Spinner from '../../components/ui/Spinner';
import { now, toISO8601WithOffsetString } from '../../helpers/dates';
managed={managed}
setManaged={(m) => setManaged(m)}
/>
- <SearchBox
+ <InputSearch
id="users-search"
minLength={2}
onChange={(search: string) => setSearch(search)}
placeholder={translate('search.search_by_login_or_name')}
value={search}
/>
- <div className="sw-ml-4">
- <Select
+ <div className="sw-flex sw-items-center sw-ml-4">
+ <StyledPageTitle as="label" className="sw-body-sm-highlight sw-mr-2">
+ {translate('users.filter.by')}
+ </StyledPageTitle>
+ <InputSelect
+ className="sw-body-sm"
+ size="medium"
id="users-activity-filter"
- className="input-large"
isDisabled={isLoading}
onChange={(userActivity: LabelValueSelectOption<UserActivity>) =>
setUsersActivity(userActivity.value)
<p>{translate('users.activity_filter.helptext.sonarlint')}</p>
</>
}
- />
+ >
+ <HelperHintIcon />
+ </HelpTooltip>
</div>
</div>
<Spinner loading={isLoading}>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { AxiosError, AxiosResponse } from 'axios';
+import {
+ ButtonPrimary,
+ ButtonSecondary,
+ FlagMessage,
+ FormField,
+ InputField,
+ Modal,
+ Spinner,
+} from 'design-system';
import * as React from 'react';
-import SimpleModal from '../../../components/controls/SimpleModal';
-import { Button, ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import { Alert } from '../../../components/ui/Alert';
-import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
import { addGlobalErrorMessage } from '../../../helpers/globalMessages';
import { translate, translateWithParameters } from '../../../helpers/l10n';
export default function UserForm(props: Props) {
const { user, isInstanceManaged } = props;
- const { mutate: createUser } = usePostUserMutation();
- const { mutate: updateUser } = useUpdateUserMutation();
+ const { mutate: createUser, isLoading: isLoadingCreate } = usePostUserMutation();
+ const { mutate: updateUser, isLoading: isLoadingUserUpdate } = useUpdateUserMutation();
const [email, setEmail] = React.useState<string>(user?.email ?? '');
const [login, setLogin] = React.useState<string>(user?.login ?? '');
}
};
- const handleCreateUser = () => {
+ React.useEffect(() => {
+ document.getElementById('it__error-message')?.scrollIntoView({
+ block: 'start',
+ });
+ }, [error]);
+
+ const handleClose = () => {
+ props.onClose();
+ };
+
+ const handleCreateUser = (e: React.SyntheticEvent<HTMLFormElement>) => {
+ e.preventDefault();
createUser(
{
email: email || undefined,
);
};
- const handleUpdateUser = () => {
+ const handleUpdateUser = (e: React.SyntheticEvent<HTMLFormElement>) => {
+ e.preventDefault();
const { user } = props;
-
updateUser(
{
id: user?.id!,
const header = user ? translate('users.update_user') : translate('users.create_user');
return (
- <SimpleModal
- header={header}
- onClose={props.onClose}
- onSubmit={user ? handleUpdateUser : handleCreateUser}
- size="small"
- >
- {({ onCloseClick, onFormSubmit, submitting }) => (
- <form autoComplete="off" id="user-form" onSubmit={onFormSubmit}>
- <header className="modal-head">
- <h2>{header}</h2>
- </header>
-
- <div className="modal-body modal-container">
- {error && <Alert variant="error">{error}</Alert>}
-
- {!error && user && !user.local && (
- <Alert variant="warning">{translate('users.cannot_update_delegated_user')}</Alert>
- )}
-
- <MandatoryFieldsExplanation className="modal-field" />
-
- {!user && (
- <div className="modal-field">
- <label htmlFor="create-user-login">
- {translate('login')}
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- autoFocus
- id="create-user-login"
- maxLength={255}
- minLength={3}
- name="login"
- onChange={(e) => setLogin(e.currentTarget.value)}
- required={!isInstanceManaged}
- type="text"
- value={login}
- />
- <p className="note">{translateWithParameters('users.minimum_x_characters', 3)}</p>
- </div>
- )}
- <div className="modal-field">
- <label htmlFor="create-user-name">
- {translate('name')}
- {!isInstanceManaged && <MandatoryFieldMarker />}
- </label>
- <input
+ <Modal
+ headerTitle={header}
+ onClose={handleClose}
+ body={
+ <form
+ autoComplete="off"
+ id="user-form"
+ onSubmit={user ? handleUpdateUser : handleCreateUser}
+ >
+ {error && (
+ <FlagMessage id="it__error-message" className="sw-mb-4" variant="error">
+ {error}
+ </FlagMessage>
+ )}
+
+ {!error && user && !user.local && (
+ <FlagMessage variant="warning">
+ {translate('users.cannot_update_delegated_user')}
+ </FlagMessage>
+ )}
+ <div className="sw-mb-4">
+ <MandatoryFieldsExplanation />
+ </div>
+
+ {!user && (
+ <FormField
+ description={translateWithParameters('users.minimum_x_characters', 3)}
+ label={translate('login')}
+ htmlFor="create-user-login"
+ required={!isInstanceManaged}
+ >
+ <InputField
autoComplete="off"
- autoFocus={!!user}
- disabled={(user && !user.local) || isInstanceManaged}
- id="create-user-name"
- maxLength={200}
- name="name"
- onChange={(e) => setName(e.currentTarget.value)}
- required={!isInstanceManaged}
+ maxLength={255}
+ minLength={3}
+ size="full"
+ id="create-user-login"
+ name="login"
+ onChange={(e) => setLogin(e.currentTarget.value)}
type="text"
- value={name}
+ value={login}
/>
- </div>
- <div className="modal-field">
- <label htmlFor="create-user-email">{translate('users.email')}</label>
- <input
+ </FormField>
+ )}
+
+ <FormField
+ label={translate('name')}
+ htmlFor="create-user-name"
+ required={!isInstanceManaged}
+ >
+ <InputField
+ autoComplete="off"
+ disabled={(user && !user.local) || isInstanceManaged}
+ size="full"
+ maxLength={200}
+ id="create-user-name"
+ name="name"
+ onChange={(e) => setName(e.currentTarget.value)}
+ type="text"
+ value={name}
+ />
+ </FormField>
+
+ <FormField label={translate('users.email')} htmlFor="create-user-email">
+ <InputField
+ autoComplete="off"
+ disabled={(user && !user.local) || isInstanceManaged}
+ size="full"
+ maxLength={100}
+ id="create-user-email"
+ name="email"
+ onChange={(e) => setEmail(e.currentTarget.value)}
+ type="email"
+ value={email}
+ />
+ </FormField>
+
+ {!user && (
+ <FormField required label={translate('password')} htmlFor="create-user-password">
+ <InputField
autoComplete="off"
- disabled={(user && !user.local) || isInstanceManaged}
- id="create-user-email"
- maxLength={100}
- name="email"
- onChange={(e) => setEmail(e.currentTarget.value)}
- type="email"
- value={email}
+ size="full"
+ id="create-user-password"
+ name="password"
+ onChange={(e) => setPassword(e.currentTarget.value)}
+ type="password"
+ value={password}
/>
+ </FormField>
+ )}
+ <FormField
+ description={translate('user.login_or_email_used_as_scm_account')}
+ label={translate('my_profile.scm_accounts')}
+ >
+ {scmAccounts.map((scm, idx) => (
+ <UserScmAccountInput
+ idx={idx}
+ key={idx}
+ onChange={handleUpdateScmAccount}
+ onRemove={handleRemoveScmAccount}
+ scmAccount={scm}
+ />
+ ))}
+ <div>
+ <ButtonSecondary className="it__scm-account-add" onClick={handleAddScmAccount}>
+ {translate('add_verb')}
+ </ButtonSecondary>
</div>
- {!user && (
- <div className="modal-field">
- <label htmlFor="create-user-password">
- {translate('password')}
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- id="create-user-password"
- name="password"
- onChange={(e) => setPassword(e.currentTarget.value)}
- required
- type="password"
- value={password}
- />
- </div>
- )}
- <div className="modal-field">
- <fieldset>
- <legend>{translate('my_profile.scm_accounts')}</legend>
- {scmAccounts.map((scm, idx) => (
- <UserScmAccountInput
- idx={idx}
- key={idx}
- onChange={handleUpdateScmAccount}
- onRemove={handleRemoveScmAccount}
- scmAccount={scm}
- />
- ))}
- <div className="spacer-bottom">
- <Button className="js-scm-account-add" onClick={handleAddScmAccount}>
- {translate('add_verb')}
- </Button>
- </div>
- </fieldset>
- <p className="note">{translate('user.login_or_email_used_as_scm_account')}</p>
- </div>
- </div>
-
- <footer className="modal-foot">
- {submitting && <i className="spinner spacer-right" />}
- <SubmitButton disabled={submitting}>
- {user ? translate('update_verb') : translate('create')}
- </SubmitButton>
- <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink>
- </footer>
+ </FormField>
</form>
- )}
- </SimpleModal>
+ }
+ primaryButton={
+ <>
+ <Spinner loading={isLoadingCreate || isLoadingUserUpdate} />
+ <ButtonPrimary
+ disabled={isLoadingCreate || isLoadingUserUpdate}
+ type="submit"
+ form="user-form"
+ >
+ {user ? translate('update_verb') : translate('create')}
+ </ButtonPrimary>
+ </>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ />
);
}