Просмотр исходного кода

fix non-auto fixable biome errors

Futa Arai 3 месяцев назад
Родитель
Сommit
f24b5207f5
31 измененных файлов с 318 добавлено и 198 удалено
  1. 15 7
      apps/app/src/client/components/Admin/App/AppSetting.jsx
  2. 28 11
      apps/app/src/client/components/Admin/App/AwsSetting.tsx
  3. 14 6
      apps/app/src/client/components/Admin/App/AzureSetting.tsx
  4. 4 2
      apps/app/src/client/components/Admin/App/FileUploadSetting.tsx
  5. 10 6
      apps/app/src/client/components/Admin/App/GcsSetting.tsx
  6. 7 3
      apps/app/src/client/components/Admin/App/MailSetting.tsx
  7. 8 2
      apps/app/src/client/components/Admin/App/MaskedInput.tsx
  8. 8 2
      apps/app/src/client/components/Admin/App/PageBulkExportSettings.tsx
  9. 10 2
      apps/app/src/client/components/Admin/App/SesSetting.tsx
  10. 3 0
      apps/app/src/client/components/Admin/App/SiteUrlSetting.tsx
  11. 24 5
      apps/app/src/client/components/Admin/App/SmtpSetting.tsx
  12. 4 4
      apps/app/src/client/components/Admin/SlackIntegration/BotTypeCard.tsx
  13. 6 4
      apps/app/src/client/components/Admin/SlackIntegration/Bridge.tsx
  14. 5 1
      apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx
  15. 2 0
      apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx
  16. 8 2
      apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx
  17. 1 0
      apps/app/src/client/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.tsx
  18. 61 57
      apps/app/src/client/components/Admin/SlackIntegration/ManageCommandsProcess.jsx
  19. 1 9
      apps/app/src/client/components/Admin/SlackIntegration/OfficialBotSettings.jsx
  20. 6 0
      apps/app/src/client/components/Admin/SlackIntegration/SlackIntegration.tsx
  21. 26 4
      apps/app/src/client/components/Admin/SlackIntegration/WithProxyAccordions.jsx
  22. 4 7
      apps/app/src/client/components/Admin/UserGroup/UserGroupDeleteModal.tsx
  23. 1 1
      apps/app/src/client/components/Admin/UserGroup/UserGroupDropdown.tsx
  24. 2 2
      apps/app/src/client/components/Admin/UserGroup/UserGroupForm.tsx
  25. 7 10
      apps/app/src/client/components/Admin/UserGroup/UserGroupPage.tsx
  26. 0 3
      apps/app/src/client/components/Admin/UserGroup/UserGroupTable.tsx
  27. 16 27
      apps/app/src/client/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx
  28. 6 6
      apps/app/src/client/components/Admin/Users/ExternalAccountTable.tsx
  29. 12 4
      apps/app/src/client/components/Admin/Users/PasswordResetModal.jsx
  30. 12 8
      apps/app/src/client/components/Admin/Users/SortIcons.tsx
  31. 7 3
      apps/app/src/client/components/Admin/Users/UserInviteModal.jsx

+ 15 - 7
apps/app/src/client/components/Admin/App/AppSetting.jsx

@@ -73,7 +73,10 @@ const AppSetting = (props) => {
   return (
     <form onSubmit={handleSubmit(onSubmit)}>
       <div className="row">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <label
+          className="text-start text-md-end col-md-3 col-form-label"
+          htmlFor="admin-app-setting-site-name"
+        >
           {t('admin:app_setting.site_name')}
         </label>
         <div className="col-md-6">
@@ -81,6 +84,7 @@ const AppSetting = (props) => {
             className="form-control"
             type="text"
             placeholder="GROWI"
+            id="admin-app-setting-site-name"
             {...register('title')}
           />
           <p className="form-text text-muted">
@@ -90,7 +94,10 @@ const AppSetting = (props) => {
       </div>
 
       <div className="row mb-5">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <label
+          className="text-start text-md-end col-md-3 col-form-label"
+          htmlFor="admin-app-setting-confidential-name"
+        >
           {t('admin:app_setting.confidential_name')}
         </label>
         <div className="col-md-6">
@@ -98,6 +105,7 @@ const AppSetting = (props) => {
             className="form-control"
             type="text"
             placeholder={t('admin:app_setting.confidential_example')}
+            id="admin-app-setting-confidential-name"
             {...register('confidential')}
           />
           <p className="form-text text-muted">
@@ -107,13 +115,13 @@ const AppSetting = (props) => {
       </div>
 
       <div className="row mb-5">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <span className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.default_language')}
-        </label>
+        </span>
         <div className="col-md-6 py-2">
           {i18nConfig.locales.map((locale) => {
             if (i18n == null) {
-              return;
+              return null;
             }
             const fixedT = i18n.getFixedT(locale, 'admin');
 
@@ -139,9 +147,9 @@ const AppSetting = (props) => {
       </div>
 
       <div className="row mb-5">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <span className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.default_mail_visibility')}
-        </label>
+        </span>
         <div className="col-md-6 py-2">
           <div className="form-check form-check-inline">
             <input

+ 28 - 11
apps/app/src/client/components/Admin/App/AwsSetting.tsx

@@ -18,9 +18,9 @@ export const AwsSettingMolecule = (
   return (
     <>
       <div className="row my-3">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <span className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.file_delivery_method')}
-        </label>
+        </span>
 
         <div className="col-md-6">
           <div className="dropdown">
@@ -37,10 +37,7 @@ export const AwsSettingMolecule = (
               {!props.s3ReferenceFileWithRelayMode &&
                 t('admin:app_setting.file_delivery_method_redirect')}
             </button>
-            <div
-              className="dropdown-menu"
-              aria-labelledby="ddS3ReferenceFileWithRelayMode"
-            >
+            <div className="dropdown-menu">
               <button
                 className="dropdown-item"
                 type="button"
@@ -71,20 +68,27 @@ export const AwsSettingMolecule = (
       </div>
 
       <div className="row">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <label
+          className="text-start text-md-end col-md-3 col-form-label"
+          htmlFor="admin-aws-setting-region"
+        >
           {t('admin:app_setting.region')}
         </label>
         <div className="col-md-6">
           <input
             className="form-control"
             placeholder={`${t('eg')} ap-northeast-1`}
+            id="admin-aws-setting-region"
             {...props.register('s3Region')}
           />
         </div>
       </div>
 
       <div className="row">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <label
+          className="text-start text-md-end col-md-3 col-form-label"
+          htmlFor="admin-aws-setting-custom-endpoint"
+        >
           {t('admin:app_setting.custom_endpoint')}
         </label>
         <div className="col-md-6">
@@ -92,6 +96,7 @@ export const AwsSettingMolecule = (
             className="form-control"
             type="text"
             placeholder={`${t('eg')} http://localhost:9000`}
+            id="admin-aws-setting-custom-endpoint"
             {...props.register('s3CustomEndpoint')}
           />
           <p className="form-text text-muted">
@@ -101,7 +106,10 @@ export const AwsSettingMolecule = (
       </div>
 
       <div className="row">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <label
+          className="text-start text-md-end col-md-3 col-form-label"
+          htmlFor="admin-aws-setting-bucket-name"
+        >
           {t('admin:app_setting.bucket_name')}
         </label>
         <div className="col-md-6">
@@ -109,32 +117,41 @@ export const AwsSettingMolecule = (
             className="form-control"
             type="text"
             placeholder={`${t('eg')} crowi`}
+            id="admin-aws-setting-bucket-name"
             {...props.register('s3Bucket')}
           />
         </div>
       </div>
 
       <div className="row">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <label
+          className="text-start text-md-end col-md-3 col-form-label"
+          htmlFor="admin-aws-setting-access-key-id"
+        >
           Access key ID
         </label>
         <div className="col-md-6">
           <input
             className="form-control"
             type="text"
+            id="admin-aws-setting-access-key-id"
             {...props.register('s3AccessKeyId')}
           />
         </div>
       </div>
 
       <div className="row">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <label
+          className="text-start text-md-end col-md-3 col-form-label"
+          htmlFor="admin-aws-setting-secret-access-key"
+        >
           Secret access key
         </label>
         <div className="col-md-6">
           <input
             className="form-control"
             type="text"
+            id="admin-aws-setting-secret-access-key"
             {...props.register('s3SecretAccessKey')}
           />
           <p className="form-text text-muted">

+ 14 - 6
apps/app/src/client/components/Admin/App/AzureSetting.tsx

@@ -35,9 +35,9 @@ export const AzureSettingMolecule = (
   return (
     <>
       <div className="row form-group my-3">
-        <label className="text-left text-md-right col-md-3 col-form-label">
+        <span className="text-left text-md-right col-md-3 col-form-label">
           {t('admin:app_setting.file_delivery_method')}
-        </label>
+        </span>
 
         <div className="col-md-6">
           <div className="dropdown">
@@ -54,10 +54,7 @@ export const AzureSettingMolecule = (
               {!azureReferenceFileWithRelayMode &&
                 t('admin:app_setting.file_delivery_method_redirect')}
             </button>
-            <div
-              className="dropdown-menu"
-              aria-labelledby="ddAzureReferenceFileWithRelayMode"
-            >
+            <div className="dropdown-menu">
               <button
                 className="dropdown-item"
                 type="button"
@@ -91,6 +88,7 @@ export const AzureSettingMolecule = (
         <p
           className="alert alert-info"
           // eslint-disable-next-line react/no-danger
+          // biome-ignore lint/security/noDangerouslySetInnerHtml: includes <br> and <code> from i18n strings
           dangerouslySetInnerHTML={{
             __html: t('admin:app_setting.azure_note_for_the_only_env_option', {
               env: 'AZURE_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS',
@@ -133,6 +131,8 @@ export const AzureSettingMolecule = (
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small
+                  // eslint-disable-next-line react/no-danger
+                  // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                   dangerouslySetInnerHTML={{
                     __html: t('admin:app_setting.use_env_var_if_empty', {
                       variable: 'AZURE_TENANT_ID',
@@ -161,6 +161,8 @@ export const AzureSettingMolecule = (
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small
+                  // eslint-disable-next-line react/no-danger
+                  // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                   dangerouslySetInnerHTML={{
                     __html: t('admin:app_setting.use_env_var_if_empty', {
                       variable: 'AZURE_CLIENT_ID',
@@ -189,6 +191,8 @@ export const AzureSettingMolecule = (
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small
+                  // eslint-disable-next-line react/no-danger
+                  // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                   dangerouslySetInnerHTML={{
                     __html: t('admin:app_setting.use_env_var_if_empty', {
                       variable: 'AZURE_CLIENT_SECRET',
@@ -219,6 +223,8 @@ export const AzureSettingMolecule = (
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small
+                  // eslint-disable-next-line react/no-danger
+                  // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                   dangerouslySetInnerHTML={{
                     __html: t('admin:app_setting.use_env_var_if_empty', {
                       variable: 'AZURE_STORAGE_ACCOUNT_NAME',
@@ -249,6 +255,8 @@ export const AzureSettingMolecule = (
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small
+                  // eslint-disable-next-line react/no-danger
+                  // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                   dangerouslySetInnerHTML={{
                     __html: t('admin:app_setting.use_env_var_if_empty', {
                       variable: 'AZURE_STORAGE_CONTAINER_NAME',

+ 4 - 2
apps/app/src/client/components/Admin/App/FileUploadSetting.tsx

@@ -103,9 +103,9 @@ const FileUploadSetting = (): JSX.Element => {
       </p>
 
       <div className="row mb-3">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <span className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.file_upload_method')}
-        </label>
+        </span>
 
         <div className="col-md-6 py-2">
           {Object.values(FileUploadType).map((type) => {
@@ -137,6 +137,8 @@ const FileUploadSetting = (): JSX.Element => {
             <br />
             {/* eslint-disable-next-line react/no-danger */}
             <b
+              // eslint-disable-next-line react/no-danger
+              // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
               dangerouslySetInnerHTML={{
                 __html: t('admin:app_setting.fixed_by_env_var', {
                   envKey: 'FILE_UPLOAD',

+ 10 - 6
apps/app/src/client/components/Admin/App/GcsSetting.tsx

@@ -30,9 +30,9 @@ export const GcsSettingMolecule = (
   return (
     <>
       <div className="row my-3">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <span className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.file_delivery_method')}
-        </label>
+        </span>
 
         <div className="col-md-6">
           <div className="dropdown">
@@ -49,10 +49,7 @@ export const GcsSettingMolecule = (
               {!gcsReferenceFileWithRelayMode &&
                 t('admin:app_setting.file_delivery_method_redirect')}
             </button>
-            <div
-              className="dropdown-menu"
-              aria-labelledby="ddGcsReferenceFileWithRelayMode"
-            >
+            <div className="dropdown-menu">
               <button
                 className="dropdown-item"
                 type="button"
@@ -86,6 +83,7 @@ export const GcsSettingMolecule = (
         <p
           className="alert alert-info"
           // eslint-disable-next-line react/no-danger
+          // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
           dangerouslySetInnerHTML={{
             __html: t('admin:app_setting.note_for_the_only_env_option', {
               env: 'GCS_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS',
@@ -130,6 +128,8 @@ export const GcsSettingMolecule = (
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small
+                  // eslint-disable-next-line react/no-danger
+                  // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                   dangerouslySetInnerHTML={{
                     __html: t('admin:app_setting.use_env_var_if_empty', {
                       variable: 'GCS_API_KEY_JSON_PATH',
@@ -160,6 +160,8 @@ export const GcsSettingMolecule = (
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small
+                  // eslint-disable-next-line react/no-danger
+                  // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                   dangerouslySetInnerHTML={{
                     __html: t('admin:app_setting.use_env_var_if_empty', {
                       variable: 'GCS_BUCKET',
@@ -190,6 +192,8 @@ export const GcsSettingMolecule = (
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small
+                  // eslint-disable-next-line react/no-danger
+                  // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                   dangerouslySetInnerHTML={{
                     __html: t('admin:app_setting.use_env_var_if_empty', {
                       variable: 'GCS_UPLOAD_NAMESPACE',

+ 7 - 3
apps/app/src/client/components/Admin/App/MailSetting.tsx

@@ -99,7 +99,10 @@ const MailSetting = (props: Props) => {
         </div>
       )}
       <div className="row mb-4">
-        <label className="col-md-3 col-form-label text-end">
+        <label
+          className="col-md-3 col-form-label text-end"
+          htmlFor="admin-mail-setting-from-address"
+        >
           {t('admin:app_setting.from_e-mail_address')}
         </label>
         <div className="col-md-6">
@@ -107,15 +110,16 @@ const MailSetting = (props: Props) => {
             className="form-control"
             type="text"
             placeholder={`${t('eg')} mail@growi.org`}
+            id="admin-mail-setting-from-address"
             {...register('fromAddress')}
           />
         </div>
       </div>
 
       <div className="row mb-2">
-        <label className="form-label text-start text-md-end col-md-3 col-form-label">
+        <span className="form-label text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.transmission_method')}
-        </label>
+        </span>
         <div className="col-md-6 py-2">
           {transmissionMethods.map((method) => {
             return (

+ 8 - 2
apps/app/src/client/components/Admin/App/MaskedInput.tsx

@@ -43,13 +43,19 @@ export default function MaskedInput(props: Props): JSX.Element {
         tabIndex={tabIndex}
         {...inputProps}
       />
-      <span onClick={togglePassword} className={styles.PasswordReveal}>
+      <button
+        type="button"
+        onClick={togglePassword}
+        className={styles.PasswordReveal}
+        aria-pressed={passwordShown}
+        aria-label="Toggle password visibility"
+      >
         {passwordShown ? (
           <span className="material-symbols-outlined">visibility</span>
         ) : (
           <span className="material-symbols-outlined">visibility_off</span>
         )}
-      </span>
+      </button>
     </div>
   );
 }

+ 8 - 2
apps/app/src/client/components/Admin/App/PageBulkExportSettings.tsx

@@ -82,7 +82,7 @@ const PageBulkExportSettings = (): JSX.Element => {
           </p>
 
           <div className="my-4 row">
-            <label className="text-start text-md-end col-md-3 col-form-label"></label>
+            <div className="text-start text-md-end col-md-3 col-form-label"></div>
 
             <div className="col-md-6">
               <div className="form-check form-switch form-check-info">
@@ -107,6 +107,8 @@ const PageBulkExportSettings = (): JSX.Element => {
                 <p className="form-text text-muted">
                   {/* eslint-disable-next-line react/no-danger */}
                   <b
+                    // eslint-disable-next-line react/no-danger
+                    // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                     dangerouslySetInnerHTML={{
                       __html: t('admin:app_setting.fixed_by_env_var', {
                         envKey: 'BULK_EXPORT_PAGES_ENABLED',
@@ -121,13 +123,17 @@ const PageBulkExportSettings = (): JSX.Element => {
 
           <div className="mb-4">
             <div className="row">
-              <label className="text-start text-md-end col-md-3 col-form-label">
+              <label
+                className="text-start text-md-end col-md-3 col-form-label"
+                htmlFor="admin-page-bulk-export-expiration"
+              >
                 {t('app_setting.page_bulk_export_storage_period')}
               </label>
 
               <div className="col-md-2">
                 <select
                   className="form-select"
+                  id="admin-page-bulk-export-expiration"
                   value={
                     (bulkExportDownloadExpirationSeconds ?? 0) / (24 * 60 * 60)
                   }

+ 10 - 2
apps/app/src/client/components/Admin/App/SesSetting.tsx

@@ -18,26 +18,34 @@ const SesSetting = (props: Props): JSX.Element => {
     <React.Fragment>
       <div id="mail-ses" className="tab-pane active">
         <div className="row">
-          <label className="text-start text-md-end col-md-3 col-form-label">
+          <label
+            className="text-start text-md-end col-md-3 col-form-label"
+            htmlFor="admin-ses-access-key-id"
+          >
             Access key ID
           </label>
           <div className="col-md-6">
             <input
               className="form-control"
               type="text"
+              id="admin-ses-access-key-id"
               {...register('sesAccessKeyId')}
             />
           </div>
         </div>
 
         <div className="row">
-          <label className="text-start text-md-end col-md-3 col-form-label">
+          <label
+            className="text-start text-md-end col-md-3 col-form-label"
+            htmlFor="admin-ses-secret-access-key"
+          >
             Secret access key
           </label>
           <div className="col-md-6">
             <input
               className="form-control"
               type="text"
+              id="admin-ses-secret-access-key"
               {...register('sesSecretAccessKey')}
             />
           </div>

+ 3 - 0
apps/app/src/client/components/Admin/App/SiteUrlSetting.tsx

@@ -61,6 +61,7 @@ const SiteUrlSetting = (props: Props) => {
           <p
             className="alert alert-info"
             // eslint-disable-next-line react/no-danger
+            // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
             dangerouslySetInnerHTML={{
               __html: t('site_url.note_for_the_only_env_option', {
                 env: 'APP_SITE_URL_USES_ONLY_ENV_VARS',
@@ -97,6 +98,7 @@ const SiteUrlSetting = (props: Props) => {
                 <p className="form-text text-muted">
                   {/* eslint-disable-next-line react/no-danger */}
                   <span
+                    // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                     dangerouslySetInnerHTML={{ __html: t('site_url.help') }}
                   />
                 </p>
@@ -111,6 +113,7 @@ const SiteUrlSetting = (props: Props) => {
                 <p className="form-text text-muted">
                   {/* eslint-disable-next-line react/no-danger */}
                   <span
+                    // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
                     dangerouslySetInnerHTML={{
                       __html: t('use_env_var_if_empty', {
                         variable: 'APP_SITE_URL',

+ 24 - 5
apps/app/src/client/components/Admin/App/SmtpSetting.tsx

@@ -20,48 +20,67 @@ const SmtpSetting = (props: Props): JSX.Element => {
     <React.Fragment>
       <div id="mail-smtp" className="tab-pane active">
         <div className="row">
-          <label className="text-start text-md-end col-md-3 col-form-label">
+          <label
+            className="text-start text-md-end col-md-3 col-form-label"
+            htmlFor="admin-smtp-host"
+          >
             {t('admin:app_setting.host')}
           </label>
           <div className="col-md-6">
             <input
               className="form-control"
               type="text"
+              id="admin-smtp-host"
               {...register('smtpHost')}
             />
           </div>
         </div>
 
         <div className="row">
-          <label className="text-start text-md-end col-md-3 col-form-label">
+          <label
+            className="text-start text-md-end col-md-3 col-form-label"
+            htmlFor="admin-smtp-port"
+          >
             {t('admin:app_setting.port')}
           </label>
           <div className="col-md-6">
-            <input className="form-control" {...register('smtpPort')} />
+            <input
+              className="form-control"
+              id="admin-smtp-port"
+              {...register('smtpPort')}
+            />
           </div>
         </div>
 
         <div className="row">
-          <label className="text-start text-md-end col-md-3 col-form-label">
+          <label
+            className="text-start text-md-end col-md-3 col-form-label"
+            htmlFor="admin-smtp-user"
+          >
             {t('admin:app_setting.user')}
           </label>
           <div className="col-md-6">
             <input
               className="form-control"
               type="text"
+              id="admin-smtp-user"
               {...register('smtpUser')}
             />
           </div>
         </div>
 
         <div className="row">
-          <label className="text-start text-md-end col-md-3 col-form-label">
+          <label
+            className="text-start text-md-end col-md-3 col-form-label"
+            htmlFor="admin-smtp-password"
+          >
             {t('Password')}
           </label>
           <div className="col-md-6">
             <input
               className="form-control"
               type="password"
+              id="admin-smtp-password"
               {...register('smtpPassword')}
             />
           </div>

+ 4 - 4
apps/app/src/client/components/Admin/SlackIntegration/BotTypeCard.tsx

@@ -43,11 +43,11 @@ export const BotTypeCard = (props: BotTypeCardProps): JSX.Element => {
   const isBotTypeOfficial = botType === SlackbotType.OFFICIAL;
 
   return (
-    <div
+    <button
+      type="button"
       className={`card admin-bot-card rounded border-radius-sm shadow ${isActive ? 'border-primary' : ''}`}
       onClick={() => onBotTypeSelectHandler(botDetails[botType].botType)}
-      role="button"
-      key={botType}
+      aria-pressed={isActive}
     >
       <div>
         <h3
@@ -121,7 +121,7 @@ export const BotTypeCard = (props: BotTypeCardProps): JSX.Element => {
           </div>
         </div>
       </div>
-    </div>
+    </button>
   );
 };
 

+ 6 - 4
apps/app/src/client/components/Admin/SlackIntegration/Bridge.tsx

@@ -36,6 +36,7 @@ const BridgeCore = (props: BridgeCoreProps): JSX.Element => {
           <small
             className="ms-2 d-none d-lg-inline"
             // eslint-disable-next-line react/no-danger
+            // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
             dangerouslySetInnerHTML={{ __html: description }}
           />
         </p>
@@ -52,6 +53,7 @@ const BridgeCore = (props: BridgeCoreProps): JSX.Element => {
       >
         <small
           // eslint-disable-next-line react/no-danger
+          // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
           dangerouslySetInnerHTML={{ __html: description }}
         />
       </UncontrolledTooltip>
@@ -68,10 +70,10 @@ export const Bridge = (props: BridgeProps): JSX.Element => {
   const { t } = useTranslation();
   const { errorCount, totalCount, withProxy } = props;
 
-  let description;
-  let iconClass;
-  let iconName;
-  let hrClass;
+  let description = '';
+  let iconClass = '';
+  let iconName = '';
+  let hrClass = '';
 
   // empty or all failed
   if (totalCount === 0 || errorCount === totalCount) {

+ 5 - 1
apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -126,7 +126,10 @@ const CustomBotWithProxySettings = (props) => {
           />
 
           <div className="row my-4">
-            <label className="text-start text-md-end col-md-3 col-form-label mt-3">
+            <label
+              className="text-start text-md-end col-md-3 col-form-label mt-3"
+              htmlFor="admin-slack-proxy-url"
+            >
               Proxy URL
             </label>
             <div className="col-md-6 mt-3">
@@ -134,6 +137,7 @@ const CustomBotWithProxySettings = (props) => {
                 className="form-control"
                 type="text"
                 name="settingForm[proxyUrl]"
+                id="admin-slack-proxy-url"
                 value={newProxyServerUri}
                 onChange={(e) => {
                   setNewProxyServerUri(e.target.value);

+ 2 - 0
apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx

@@ -82,6 +82,7 @@ const CustomBotWithoutProxySecretTokenSection = (props) => {
           <p className="form-text text-muted">
             {/* eslint-disable-next-line max-len, react/no-danger */}
             <small
+              // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
               dangerouslySetInnerHTML={{
                 __html: t('admin:slack_integration.use_env_var_if_empty', {
                   variable: 'SLACKBOT_WITHOUT_PROXY_SIGNING_SECRET',
@@ -115,6 +116,7 @@ const CustomBotWithoutProxySecretTokenSection = (props) => {
           <p className="form-text text-muted">
             {/* eslint-disable-next-line react/no-danger */}
             <small
+              // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
               dangerouslySetInnerHTML={{
                 __html: t('admin:slack_integration.use_env_var_if_empty', {
                   variable: 'SLACKBOT_WITHOUT_PROXY_BOT_TOKEN',

+ 8 - 2
apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx

@@ -132,6 +132,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
           <img
             src="/images/slack-integration/slack-bot-install-your-app-introduction.png"
             className="border border-light img-fluid mb-5"
+            alt=""
           />
           <p>
             2.{' '}
@@ -140,11 +141,13 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
           <img
             src="/images/slack-integration/slack-bot-install-to-workspace.png"
             className="border border-light img-fluid mb-5"
+            alt=""
           />
           <p>3. {t('admin:slack_integration.accordion.click_allow')}</p>
           <img
             src="/images/slack-integration/slack-bot-install-your-app-transition-destination.png"
             className="border border-light img-fluid mb-5"
+            alt=""
           />
           <p>
             4.{' '}
@@ -153,6 +156,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
           <img
             src="/images/slack-integration/slack-bot-install-your-app-complete.png"
             className="border border-light img-fluid mb-5"
+            alt=""
           />
           <p>
             5. {t('admin:slack_integration.accordion.invite_bot_to_channel')}
@@ -160,10 +164,12 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
           <img
             src="/images/slack-integration/slack-bot-install-to-workspace-joined-bot.png"
             className="border border-light img-fluid mb-1"
+            alt=""
           />
           <img
             src="/images/slack-integration/slack-bot-install-your-app-introduction-to-channel.png"
             className="border border-light img-fluid"
+            alt=""
           />
         </div>
       </Accordion>
@@ -271,11 +277,11 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
         <form>
           <div className="row my-3 justify-content-center">
             <div className="slack-connection-log col-md-4">
-              <label className="form-label mb-1">
+              <div className="form-label mb-1">
                 <p className="border-info slack-connection-log-title ps-2 m-0">
                   Logs
                 </p>
-              </label>
+              </div>
               <textarea
                 className="form-control card border-info slack-connection-log-body rounded-3"
                 rows="5"

+ 1 - 0
apps/app/src/client/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.tsx

@@ -49,6 +49,7 @@ export const DeleteSlackBotSettingsModal = React.memo(
       return (
         <span
           // eslint-disable-next-line react/no-danger
+          // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
           dangerouslySetInnerHTML={{ __html: htmlContent }}
         />
       );

+ 61 - 57
apps/app/src/client/components/Admin/SlackIntegration/ManageCommandsProcess.jsx

@@ -181,6 +181,61 @@ PermissionSettingForEachPermissionTypeComponent.propTypes = {
   permissionSettings: PropTypes.object,
 };
 
+const PermissionSettingsForEachCategoryComponent = ({
+  currentPermissionTypes,
+  usageType,
+  menuItem,
+  permissionSettings,
+}) => {
+  const {
+    title,
+    description,
+    defaultCommandsName,
+    singleCommandDescription,
+    updatePermissionsHandler,
+    updateChannelsHandler,
+    allowedChannelsDescription,
+  } = menuItem;
+
+  return (
+    <>
+      {(title || description) && (
+        <div className="row">
+          <div className="col-md-7 offset-md-2">
+            {title && <p className="fw-bold mb-1">{title}</p>}
+            {description && <p className="text-muted">{description}</p>}
+          </div>
+        </div>
+      )}
+
+      <div className="form-check">
+        <div className="row mb-5 d-block">
+          {defaultCommandsName.map((keyName) => (
+            <PermissionSettingForEachPermissionTypeComponent
+              key={`${keyName}-component`}
+              keyName={keyName}
+              usageType={usageType}
+              permissionSettings={permissionSettings}
+              currentPermissionType={currentPermissionTypes[keyName]}
+              singleCommandDescription={singleCommandDescription}
+              onUpdatePermissions={updatePermissionsHandler}
+              onUpdateChannels={updateChannelsHandler}
+              allowedChannelsDescription={allowedChannelsDescription}
+            />
+          ))}
+        </div>
+      </div>
+    </>
+  );
+};
+
+PermissionSettingsForEachCategoryComponent.propTypes = {
+  currentPermissionTypes: PropTypes.object,
+  usageType: PropTypes.string,
+  menuItem: PropTypes.object,
+  permissionSettings: PropTypes.object,
+};
+
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
 const ManageCommandsProcess = ({
   slackAppIntegrationId,
@@ -307,63 +362,10 @@ const ManageCommandsProcess = ({
     }
   };
 
-  const PermissionSettingsForEachCategoryComponent = ({
-    currentPermissionTypes,
-    usageType,
-    menuItem,
-  }) => {
-    const permissionMap = {
-      broadcastUse: permissionsForBroadcastUseCommandsState,
-      singleUse: permissionsForSingleUseCommandsState,
-      linkSharing: permissionsForEventsState,
-    };
-
-    const {
-      title,
-      description,
-      defaultCommandsName,
-      singleCommandDescription,
-      updatePermissionsHandler,
-      updateChannelsHandler,
-      allowedChannelsDescription,
-    } = menuItem;
-
-    return (
-      <>
-        {(title || description) && (
-          <div className="row">
-            <div className="col-md-7 offset-md-2">
-              {title && <p className="fw-bold mb-1">{title}</p>}
-              {description && <p className="text-muted">{description}</p>}
-            </div>
-          </div>
-        )}
-
-        <div className="form-check">
-          <div className="row mb-5 d-block">
-            {defaultCommandsName.map((keyName) => (
-              <PermissionSettingForEachPermissionTypeComponent
-                key={`${keyName}-component`}
-                keyName={keyName}
-                usageType={usageType}
-                permissionSettings={permissionMap[usageType]}
-                currentPermissionType={currentPermissionTypes[keyName]}
-                singleCommandDescription={singleCommandDescription}
-                onUpdatePermissions={updatePermissionsHandler}
-                onUpdateChannels={updateChannelsHandler}
-                allowedChannelsDescription={allowedChannelsDescription}
-              />
-            ))}
-          </div>
-        </div>
-      </>
-    );
-  };
-
-  PermissionSettingsForEachCategoryComponent.propTypes = {
-    currentPermissionTypes: PropTypes.object,
-    usageType: PropTypes.string,
-    menuItem: PropTypes.object,
+  const permissionMap = {
+    broadcastUse: permissionsForBroadcastUseCommandsState,
+    singleUse: permissionsForSingleUseCommandsState,
+    linkSharing: permissionsForEventsState,
   };
 
   // Using i18n in allowedChannelsDescription will cause interpolation error
@@ -413,6 +415,7 @@ const ManageCommandsProcess = ({
               currentPermissionTypes={currentPermissionTypes}
               usageType={commandUsageType}
               menuItem={menuMap[commandUsageType]}
+              permissionSettings={permissionMap[commandUsageType]}
             />
           ))}
         </div>
@@ -427,6 +430,7 @@ const ManageCommandsProcess = ({
               currentPermissionTypes={currentPermissionTypes}
               usageType={EventType}
               menuItem={menuMap[EventType]}
+              permissionSettings={permissionMap[EventType]}
             />
           ))}
         </div>

+ 1 - 9
apps/app/src/client/components/Admin/SlackIntegration/OfficialBotSettings.jsx

@@ -90,15 +90,7 @@ const OfficialBotSettings = (props) => {
           target="_blank"
           rel="noopener noreferrer"
         >
-          <span
-            className="growi-custom-icons btn-link ms-2"
-            onClick={() =>
-              window.open(
-                `${t('admin:slack_integration.docs_url.official_bot')}`,
-                '_blank',
-              )
-            }
-          >
+          <span className="growi-custom-icons btn-link ms-2">
             external_link
           </span>
         </a>

+ 6 - 0
apps/app/src/client/components/Admin/SlackIntegration/SlackIntegration.tsx

@@ -218,7 +218,13 @@ export const SlackIntegration = (): JSX.Element => {
             href={t('admin:slack_integration.docs_url.slack_integration')}
             target="_blank"
             rel="noopener noreferrer"
+            aria-label={t(
+              'admin:slack_integration.selecting_bot_types.slack_bot',
+            )}
           >
+            <span className="visually-hidden">
+              {t('admin:slack_integration.selecting_bot_types.slack_bot')}
+            </span>
             <span className="material-symbols-outlined ms-1" aria-hidden="true">
               help
             </span>

+ 26 - 4
apps/app/src/client/components/Admin/SlackIntegration/WithProxyAccordions.jsx

@@ -92,6 +92,7 @@ const BotInstallProcessForCustomBotWithProxy = () => {
       <img
         src="/images/slack-integration/activate-public-dist.png"
         className="border border-light img-fluid mb-5"
+        alt=""
       />
       <p>
         3. {t('admin:slack_integration.accordion.click-add-to-slack-button')}
@@ -99,11 +100,13 @@ const BotInstallProcessForCustomBotWithProxy = () => {
       <img
         src="/images/slack-integration/click-add-to-slack.png"
         className="border border-light img-fluid mb-5"
+        alt=""
       />
       <p>4. {t('admin:slack_integration.accordion.click_allow')}</p>
       <img
         src="/images/slack-integration/slack-bot-install-your-app-transition-destination.png"
         className="border border-light img-fluid mb-5"
+        alt=""
       />
       <p>
         5. {t('admin:slack_integration.accordion.install_complete_if_checked')}
@@ -111,15 +114,18 @@ const BotInstallProcessForCustomBotWithProxy = () => {
       <img
         src="/images/slack-integration/basicinfo-all-checked.png"
         className="border border-light img-fluid mb-5"
+        alt=""
       />
       <p>6. {t('admin:slack_integration.accordion.invite_bot_to_channel')}</p>
       <img
         src="/images/slack-integration/slack-bot-install-to-workspace-joined-bot.png"
         className="border border-light img-fluid mb-1"
+        alt=""
       />
       <img
         src="/images/slack-integration/slack-bot-install-your-app-introduction-to-channel.png"
         className="border border-light img-fluid"
+        alt=""
       />
     </div>
   );
@@ -133,6 +139,7 @@ const RegisteringProxyUrlProcess = () => {
         <li>
           <p
             // eslint-disable-next-line react/no-danger
+            // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
             dangerouslySetInnerHTML={{
               __html: t('admin:slack_integration.accordion.copy_proxy_url'),
             }}
@@ -141,12 +148,14 @@ const RegisteringProxyUrlProcess = () => {
             <img
               className="border border-light img-fluid"
               src="/images/slack-integration/growi-register-sentence.png"
+              alt=""
             />
           </p>
         </li>
         <li>
           <p
             // eslint-disable-next-line react/no-danger
+            // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
             dangerouslySetInnerHTML={{
               __html: t(
                 'admin:slack_integration.accordion.enter_proxy_url_and_update',
@@ -157,6 +166,7 @@ const RegisteringProxyUrlProcess = () => {
             <img
               className="border border-light img-fluid"
               src="/images/slack-integration/growi-set-proxy-url.png"
+              alt=""
             />
           </p>
           <p className="text-danger">
@@ -195,7 +205,10 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = (props) => {
         1. {t('admin:slack_integration.accordion.generate_access_token')}
       </p>
       <div className="row">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <label
+          className="text-start text-md-end col-md-3 col-form-label"
+          htmlFor="admin-slack-access-token-ptog"
+        >
           Access Token Proxy to GROWI
         </label>
         <div className="col-md-6">
@@ -204,6 +217,7 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = (props) => {
               className="form-control"
               type="text"
               value={props.tokenPtoG || ''}
+              id="admin-slack-access-token-ptog"
               readOnly
             />
             <CustomCopyToClipBoard
@@ -214,7 +228,10 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = (props) => {
         </div>
       </div>
       <div className="row">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <label
+          className="text-start text-md-end col-md-3 col-form-label"
+          htmlFor="admin-slack-access-token-gtop"
+        >
           Access Token GROWI to Proxy
         </label>
         <div className="col-md-6">
@@ -223,6 +240,7 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = (props) => {
               className="form-control"
               type="text"
               value={props.tokenGtoP || ''}
+              id="admin-slack-access-token-gtop"
               readOnly
             />
             <CustomCopyToClipBoard
@@ -254,6 +272,7 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = (props) => {
             <p
               className="ms-2"
               // eslint-disable-next-line react/no-danger
+              // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
               dangerouslySetInnerHTML={{
                 __html: t(
                   'admin:slack_integration.accordion.enter_growi_register_on_slack',
@@ -267,6 +286,7 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = (props) => {
               // TODO: Add dynamic link
               // TODO: Add logo
               // eslint-disable-next-line react/no-danger
+              // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
               dangerouslySetInnerHTML={{
                 __html: t('admin:slack_integration.accordion.paste_growi_url'),
               }}
@@ -290,6 +310,7 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = (props) => {
             <p
               className="ms-2"
               // eslint-disable-next-line react/no-danger
+              // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
               dangerouslySetInnerHTML={{
                 __html: t(
                   'admin:slack_integration.accordion.enter_access_token_for_growi_and_proxy',
@@ -302,6 +323,7 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = (props) => {
           className="mb-3 border border-light img-fluid"
           width={500}
           src="/images/slack-integration/growi-register-modal.png"
+          alt=""
         />
       </div>
     </div>
@@ -390,11 +412,11 @@ const TestProcess = ({
       <form>
         <div className="row my-3 justify-content-center">
           <div className="slack-connection-log col-md-4">
-            <label className="form-label mb-1">
+            <div className="form-label mb-1">
               <p className="border-info slack-connection-log-title ps-2 m-0">
                 Logs
               </p>
-            </label>
+            </div>
             <textarea
               className="form-control card border-info slack-connection-log-body rounded-3"
               rows="5"

+ 4 - 7
apps/app/src/client/components/Admin/UserGroup/UserGroupDeleteModal.tsx

@@ -87,13 +87,10 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
     onHide();
   }, [onHide, resetStates]);
 
-  const handleActionChange = useCallback(
-    (e) => {
-      const actionName = e.target.value;
-      setActionName(actionName);
-    },
-    [setActionName],
-  );
+  const handleActionChange = useCallback((e) => {
+    const actionName = e.target.value;
+    setActionName(actionName);
+  }, []);
 
   const handleGroupChange = useCallback(
     (e) => {

+ 1 - 1
apps/app/src/client/components/Admin/UserGroup/UserGroupDropdown.tsx

@@ -45,7 +45,7 @@ export const UserGroupDropdown: FC<Props> = (props: Props) => {
           {t('admin:user_group_management.add_child_group')}
         </button>
 
-        <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+        <div className="dropdown-menu">
           {selectableUserGroups != null && selectableUserGroups.length > 0 && (
             <>
               {selectableUserGroups.map((userGroup) => (

+ 2 - 2
apps/app/src/client/components/Admin/UserGroup/UserGroupForm.tsx

@@ -54,7 +54,7 @@ export const UserGroupForm: FC<Props> = (props: Props) => {
         setSelectedParent(userGroup);
       }
     },
-    [selectedParent, setSelectedParent],
+    [selectedParent],
   );
 
   useEffect(() => {
@@ -146,7 +146,7 @@ export const UserGroupForm: FC<Props> = (props: Props) => {
             >
               {selectedParent?.name ?? messageAtReleaseParentGroup}
             </button>
-            <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+            <div className="dropdown-menu">
               {isSelectableParentUserGroups && (
                 <>
                   {selectableParentUserGroups.map((userGroup) => (

+ 7 - 10
apps/app/src/client/components/Admin/UserGroup/UserGroupPage.tsx

@@ -84,24 +84,21 @@ export const UserGroupPage: FC = () => {
    */
   const showCreateModal = useCallback(() => {
     setCreateModalShown(true);
-  }, [setCreateModalShown]);
+  }, []);
 
   const hideCreateModal = useCallback(() => {
     setCreateModalShown(false);
-  }, [setCreateModalShown]);
+  }, []);
 
-  const showUpdateModal = useCallback(
-    (group: IUserGroupHasId) => {
-      setUpdateModalShown(true);
-      setSelectedUserGroup(group);
-    },
-    [setUpdateModalShown],
-  );
+  const showUpdateModal = useCallback((group: IUserGroupHasId) => {
+    setUpdateModalShown(true);
+    setSelectedUserGroup(group);
+  }, []);
 
   const hideUpdateModal = useCallback(() => {
     setUpdateModalShown(false);
     setSelectedUserGroup(undefined);
-  }, [setUpdateModalShown]);
+  }, []);
 
   const syncUserGroupAndRelations = useCallback(async () => {
     try {

+ 0 - 3
apps/app/src/client/components/Admin/UserGroup/UserGroupTable.tsx

@@ -271,7 +271,6 @@ export const UserGroupTable: FC<Props> = ({
                         <button
                           className="dropdown-item"
                           type="button"
-                          role="button"
                           onClick={onClickEdit}
                           data-user-group-id={group._id}
                         >
@@ -284,7 +283,6 @@ export const UserGroupTable: FC<Props> = ({
                           <button
                             className="dropdown-item"
                             type="button"
-                            role="button"
                             onClick={onClickRemove}
                             data-user-group-id={group._id}
                           >
@@ -299,7 +297,6 @@ export const UserGroupTable: FC<Props> = ({
                         <button
                           className="dropdown-item"
                           type="button"
-                          role="button"
                           onClick={onClickDelete}
                           data-user-group-id={group._id}
                         >

+ 16 - 27
apps/app/src/client/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx

@@ -130,7 +130,7 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
     ) {
       router.push('/admin/user-groups');
     }
-  }, [currentUserGroup, currentUserGroupId, notExistsUerGroup, router]);
+  }, [currentUserGroupId, notExistsUerGroup, router]);
 
   /*
    * Fetch
@@ -348,10 +348,10 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
       }
     },
     [
-      currentUserGroup?.name,
       currentUserGroupId,
       mutateUserGroupRelationList,
       mutateUserGroupRelations,
+      currentUserGroup?.name,
     ],
   );
 
@@ -372,21 +372,18 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
         );
       }
     },
-    [currentUserGroup?.name, currentUserGroupId, mutateUserGroupRelationList],
+    [currentUserGroupId, mutateUserGroupRelationList, currentUserGroup?.name],
   );
 
-  const showUpdateModal = useCallback(
-    (group: IUserGroupHasId) => {
-      setUpdateModalShown(true);
-      setSelectedUserGroup(group);
-    },
-    [setUpdateModalShown],
-  );
+  const showUpdateModal = useCallback((group: IUserGroupHasId) => {
+    setUpdateModalShown(true);
+    setSelectedUserGroup(group);
+  }, []);
 
   const hideUpdateModal = useCallback(() => {
     setUpdateModalShown(false);
     setSelectedUserGroup(undefined);
-  }, [setUpdateModalShown]);
+  }, []);
 
   const updateChildUserGroup = useCallback(
     async (userGroupData: IUserGroupHasId) => {
@@ -424,11 +421,11 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
 
   const showCreateModal = useCallback(() => {
     setCreateModalShown(true);
-  }, [setCreateModalShown]);
+  }, []);
 
   const hideCreateModal = useCallback(() => {
     setCreateModalShown(false);
-  }, [setCreateModalShown]);
+  }, []);
 
   const createChildUserGroup = useCallback(
     async (userGroupData: IUserGroup) => {
@@ -466,18 +463,15 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
     ],
   );
 
-  const showDeleteModal = useCallback(
-    async (group: IUserGroupHasId) => {
-      setSelectedUserGroup(group);
-      setDeleteModalShown(true);
-    },
-    [setSelectedUserGroup, setDeleteModalShown],
-  );
+  const showDeleteModal = useCallback(async (group: IUserGroupHasId) => {
+    setSelectedUserGroup(group);
+    setDeleteModalShown(true);
+  }, []);
 
   const hideDeleteModal = useCallback(() => {
     setSelectedUserGroup(undefined);
     setDeleteModalShown(false);
-  }, [setSelectedUserGroup, setDeleteModalShown]);
+  }, []);
 
   const deleteChildUserGroupById = useCallback(
     async (
@@ -512,12 +506,7 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
         toastError(new Error('Unable to delete the groups'));
       }
     },
-    [
-      mutateChildUserGroups,
-      setSelectedUserGroup,
-      setDeleteModalShown,
-      isExternalGroup,
-    ],
+    [mutateChildUserGroups, isExternalGroup],
   );
 
   const removeChildUserGroup = useCallback(

+ 6 - 6
apps/app/src/client/components/Admin/Users/ExternalAccountTable.tsx

@@ -63,14 +63,15 @@ const ExternalAccountTable = (
             <th style={{ width: '100px' }}>
               <div className="d-flex align-items-center">
                 {t('user_management.password_setting')}
-                <span
-                  role="button"
-                  className="text-muted mx-2"
+                <button
+                  type="button"
+                  className="text-muted mx-2 btn btn-link p-0"
                   data-bs-toggle="popper"
                   data-placement="top"
                   data-trigger="hover"
                   data-html="true"
                   title={t('user_management.password_setting_help')}
+                  aria-label={t('user_management.password_setting_help')}
                 >
                   <small>
                     <span
@@ -80,7 +81,7 @@ const ExternalAccountTable = (
                       help
                     </span>
                   </small>
-                </span>
+                </button>
               </div>
             </th>
             <th style={{ width: '100px' }}>
@@ -127,14 +128,13 @@ const ExternalAccountTable = (
                         </span>{' '}
                         <span className="caret"></span>
                       </button>
-                      <ul className="dropdown-menu" role="menu">
+                      <ul className="dropdown-menu">
                         <li className="dropdown-header">
                           {t('user_management.user_table.edit_menu')}
                         </li>
                         <button
                           className="dropdown-item"
                           type="button"
-                          role="button"
                           onClick={() => removeExtenalAccount(ea._id)}
                         >
                           <span className="material-symbols-outlined text-danger">

+ 12 - 4
apps/app/src/client/components/Admin/Users/PasswordResetModal.jsx

@@ -83,8 +83,10 @@ class PasswordResetModal extends React.Component {
     return (
       <div className="d-flex col text-start ms-1 ps-0">
         {!isMailerSetup ? (
-          <label
-            className="form-label form-text text-muted"
+          <p
+            className="form-label form-text text-muted mb-0"
+            // eslint-disable-next-line react/no-danger
+            // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
             dangerouslySetInnerHTML={{
               __html: t('admin:mailer_setup_required'),
             }}
@@ -142,12 +144,18 @@ class PasswordResetModal extends React.Component {
         <p>
           {t('user_management.reset_password_modal.new_password')}:{' '}
           <code>
-            <span
+            <button
+              type="button"
+              className="btn btn-link p-0 align-baseline"
               onMouseEnter={() => this.setState({ showPassword: true })}
               onMouseLeave={() => this.setState({ showPassword: false })}
+              aria-pressed={showPassword}
+              aria-label={t(
+                'user_management.reset_password_modal.new_password',
+              )}
             >
               {showPassword ? temporaryPassword : maskedPassword}
-            </span>
+            </button>
           </code>
           <CopyToClipboard
             text={temporaryPassword}

+ 12 - 8
apps/app/src/client/components/Admin/Users/SortIcons.tsx

@@ -11,20 +11,24 @@ export const SortIcons = (props: SortIconsProps): JSX.Element => {
 
   return (
     <div className="d-flex flex-column text-center">
-      <a
-        className={`${isSelected && isAsc ? 'text-primary' : 'text-muted'}`}
-        aria-hidden="true"
+      <button
+        type="button"
+        className={`${isSelected && isAsc ? 'text-primary' : 'text-muted'} btn btn-link p-0`}
         onClick={() => onClick('asc')}
+        aria-pressed={isSelected && isAsc}
+        aria-label="Sort ascending"
       >
         <span className="material-symbols-outlined">expand_less</span>
-      </a>
-      <a
-        className={`${isSelected && !isAsc ? 'text-primary' : 'text-muted'}`}
-        aria-hidden="true"
+      </button>
+      <button
+        type="button"
+        className={`${isSelected && !isAsc ? 'text-primary' : 'text-muted'} btn btn-link p-0`}
         onClick={() => onClick('desc')}
+        aria-pressed={isSelected && !isAsc}
+        aria-label="Sort descending"
       >
         <span className="material-symbols-outlined">expand_more</span>
-      </a>
+      </button>
     </div>
   );
 };

+ 7 - 3
apps/app/src/client/components/Admin/Users/UserInviteModal.jsx

@@ -63,7 +63,7 @@ class UserInviteModal extends React.Component {
 
     return (
       <>
-        <label className="form-label">
+        <label className="form-label" htmlFor="admin-invite-emails">
           {t('admin:user_management.invite_modal.emails')}
         </label>
         <p>
@@ -73,6 +73,7 @@ class UserInviteModal extends React.Component {
         </p>
         <textarea
           className="form-control"
+          id="admin-invite-emails"
           placeholder="e.g.&#13;&#10;user1@growi.org&#13;&#10;user2@growi.org"
           style={{ height: '200px' }}
           value={this.state.emailInputValue}
@@ -128,6 +129,8 @@ class UserInviteModal extends React.Component {
             // eslint-disable-next-line react/no-danger
             <p
               className="form-text text-muted"
+              // eslint-disable-next-line react/no-danger
+              // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
               dangerouslySetInnerHTML={{
                 __html: t(
                   'admin:user_management.invite_modal.mail_setting_link',
@@ -138,6 +141,7 @@ class UserInviteModal extends React.Component {
             // eslint-disable-next-line react/no-danger
             <p
               className="form-text text-muted"
+              // biome-ignore lint/security/noDangerouslySetInnerHtml: includes markup from i18n strings
               dangerouslySetInnerHTML={{
                 __html: t('admin:mailer_setup_required'),
               }}
@@ -171,12 +175,12 @@ class UserInviteModal extends React.Component {
 
     return (
       <>
-        <label className="form-label me-3 text-start" style={{ flex: 1 }}>
+        <div className="form-label me-3 text-start" style={{ flex: 1 }}>
           <text className="text-danger">
             {t('admin:user_management.invite_modal.send_temporary_password')}
           </text>
           <text>{t('admin:user_management.invite_modal.send_email')}</text>
-        </label>
+        </div>
         <button
           type="button"
           className="btn btn-outline-secondary"