]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21384 - Convert header from security user to MIUI
authorKevin Silva <kevin.silva@sonarsource.com>
Fri, 29 Dec 2023 15:50:04 +0000 (16:50 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 4 Jan 2024 20:02:48 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/users/Header.tsx
server/sonar-web/src/main/js/apps/users/UsersApp.tsx
server/sonar-web/src/main/js/apps/users/components/UserForm.tsx
server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 4c62de4951d5a964a1a13bec3872e28369eda6a4..8feabce03dd0ca5c571e83dbaa30de913c725361 100644 (file)
  * 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';
 
@@ -30,41 +29,46 @@ interface Props {
 }
 
 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} />
       )}
index af4769e5e1f6e2a6965e8e09ba43d0b5f4c63922..22623f43c2d450926245e8450965c1d6689c1b3f 100644 (file)
@@ -18,6 +18,7 @@
  * 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';
@@ -26,8 +27,7 @@ import GitLabSynchronisationWarning from '../../app/components/GitLabSynchronisa
 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';
@@ -102,17 +102,21 @@ export default function UsersApp() {
           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)
@@ -131,7 +135,9 @@ export default function UsersApp() {
                 <p>{translate('users.activity_filter.helptext.sonarlint')}</p>
               </>
             }
-          />
+          >
+            <HelperHintIcon />
+          </HelpTooltip>
         </div>
       </div>
       <Spinner loading={isLoading}>
index 7395809260e0283de94f400000c8aeeb6bfc0969..3147ac5d9eb7b38c44dd39c5d78e83637750e03a 100644 (file)
  * 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';
@@ -43,8 +48,8 @@ const INTERNAL_SERVER_ERROR = 500;
 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 ?? '');
@@ -64,7 +69,18 @@ export default function UserForm(props: Props) {
     }
   };
 
-  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,
@@ -77,9 +93,9 @@ export default function UserForm(props: Props) {
     );
   };
 
-  const handleUpdateUser = () => {
+  const handleUpdateUser = (e: React.SyntheticEvent<HTMLFormElement>) => {
+    e.preventDefault();
     const { user } = props;
-
     updateUser(
       {
         id: user?.id!,
@@ -115,127 +131,130 @@ export default function UserForm(props: Props) {
   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')}
+    />
   );
 }
index 62c9da8430d43557d65b8e11af8494ca03d0385d..0b09928f069584b3aeff2f6c2c688d7aa199c498 100644 (file)
@@ -17,8 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { DestructiveIcon, InputField, TrashIcon } from 'design-system';
 import * as React from 'react';
-import { DeleteButton } from '../../../components/controls/buttons';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 
 export interface Props {
@@ -36,8 +36,10 @@ export default function UserScmAccountInput(props: Props) {
     : translate('users.create_user.scm_account_new');
 
   return (
-    <div className="js-scm-account display-flex-row spacer-bottom">
-      <input
+    <div className="it__scm-account sw-flex sw-mb-2">
+      <InputField
+        className="sw-mr-1"
+        size="full"
         maxLength={255}
         onChange={(event) => {
           props.onChange(idx, event.currentTarget.value);
@@ -46,11 +48,11 @@ export default function UserScmAccountInput(props: Props) {
         aria-label={inputAriaLabel}
         value={scmAccount}
       />
-      <DeleteButton
+      <DestructiveIcon
+        Icon={TrashIcon}
         aria-label={translateWithParameters('remove_x', inputAriaLabel)}
-        onClick={() => {
-          props.onRemove(idx);
-        }}
+        onClick={() => props.onRemove(idx)}
+        stopPropagation={false}
       />
     </div>
   );
index 5864c0816f19db43f90bc7cbe7f30493caeca8e2..12833ba60af27dc15186d58e4169f73dde5360a4 100644 (file)
@@ -5054,6 +5054,7 @@ users.change_admin_password.form.confirm=Confirm password for user 'admin'
 users.change_admin_password.form.cannot_use_default_password=You must choose a password that is different from the default password.
 users.change_admin_password.form.success=The admin user's password was successfully changed.
 users.change_admin_password.form.continue_to_app=Continue to SonarQube
+users.filter.by=Filter by
 
 #------------------------------------------------------------------------------
 #