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

Merge branch 'support/apply-bootstrap4' into dev/4.0.x

Yuki Takei 6 лет назад
Родитель
Сommit
5bc2de24a9
100 измененных файлов с 1945 добавлено и 1613 удалено
  1. 1 0
      .eslintrc.js
  2. 5 0
      CHANGES.md
  3. 15 0
      babel.config.js
  4. 1 0
      config/logger/config.dev.js
  5. 10 12
      config/webpack.common.js
  6. 49 0
      config/webpack.dev.dll.js
  7. 9 3
      package.json
  8. 2 1
      resource/cdn-manifests.js
  9. 3 3
      resource/locales/en-US/translation.json
  10. 3 3
      resource/locales/en-US/welcome.md
  11. 3 3
      resource/locales/ja/translation.json
  12. 3 3
      resource/locales/ja/welcome.md
  13. 5 0
      src/client/js/app.jsx
  14. 4 1
      src/client/js/bootstrap.jsx
  15. 2 2
      src/client/js/components/Admin/AdminHome/AdminHome.jsx
  16. 1 1
      src/client/js/components/Admin/AdminHome/SystemInfomationTable.jsx
  17. 24 21
      src/client/js/components/Admin/App/AppSetting.jsx
  18. 5 5
      src/client/js/components/Admin/App/AppSettingsPage.jsx
  19. 17 17
      src/client/js/components/Admin/App/AwsSetting.jsx
  20. 11 11
      src/client/js/components/Admin/App/MailSetting.jsx
  21. 6 5
      src/client/js/components/Admin/App/PluginSetting.jsx
  22. 40 42
      src/client/js/components/Admin/App/SiteUrlSetting.jsx
  23. 40 35
      src/client/js/components/Admin/Common/AdminNavigation.jsx
  24. 9 13
      src/client/js/components/Admin/Common/AdminUpdateButtonRow.jsx
  25. 5 5
      src/client/js/components/Admin/Common/ProgressBar.jsx
  26. 9 3
      src/client/js/components/Admin/Customize/CustomizeBehaviorOption.jsx
  27. 39 35
      src/client/js/components/Admin/Customize/CustomizeBehaviorSetting.jsx
  28. 24 19
      src/client/js/components/Admin/Customize/CustomizeCssSetting.jsx
  29. 3 2
      src/client/js/components/Admin/Customize/CustomizeFunctionOption.jsx
  30. 122 104
      src/client/js/components/Admin/Customize/CustomizeFunctionSetting.jsx
  31. 33 29
      src/client/js/components/Admin/Customize/CustomizeHeaderSetting.jsx
  32. 63 44
      src/client/js/components/Admin/Customize/CustomizeHighlightSetting.jsx
  33. 9 3
      src/client/js/components/Admin/Customize/CustomizeLayoutOption.jsx
  34. 3 3
      src/client/js/components/Admin/Customize/CustomizeLayoutOptions.jsx
  35. 14 6
      src/client/js/components/Admin/Customize/CustomizeLayoutSetting.jsx
  36. 57 42
      src/client/js/components/Admin/Customize/CustomizeScriptSetting.jsx
  37. 0 2
      src/client/js/components/Admin/Customize/CustomizeThemeOptions.jsx
  38. 34 20
      src/client/js/components/Admin/Customize/CustomizeTitle.jsx
  39. 7 7
      src/client/js/components/Admin/ElasticsearchManagement/ElasticsearchManagement.jsx
  40. 1 1
      src/client/js/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.jsx
  41. 2 2
      src/client/js/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx
  42. 1 1
      src/client/js/components/Admin/ElasticsearchManagement/ReconnectControls.jsx
  43. 23 27
      src/client/js/components/Admin/ElasticsearchManagement/StatusTable.jsx
  44. 1 1
      src/client/js/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.jsx
  45. 46 42
      src/client/js/components/Admin/ExportArchiveData/SelectCollectionsModal.jsx
  46. 1 1
      src/client/js/components/Admin/ExportArchiveDataPage.jsx
  47. 7 7
      src/client/js/components/Admin/ImportData/GrowiArchive/ErrorViewer.jsx
  48. 46 31
      src/client/js/components/Admin/ImportData/GrowiArchive/ImportCollectionConfigurationModal.jsx
  49. 17 19
      src/client/js/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx
  50. 17 15
      src/client/js/components/Admin/ImportData/GrowiArchive/ImportForm.jsx
  51. 6 6
      src/client/js/components/Admin/ImportData/GrowiArchive/UploadForm.jsx
  52. 32 32
      src/client/js/components/Admin/ImportDataPage.jsx
  53. 18 24
      src/client/js/components/Admin/MarkdownSetting/LineBreakForm.jsx
  54. 16 15
      src/client/js/components/Admin/MarkdownSetting/MarkDownSetting.jsx
  55. 58 51
      src/client/js/components/Admin/MarkdownSetting/PresentationForm.jsx
  56. 4 4
      src/client/js/components/Admin/MarkdownSetting/WhiteListInput.jsx
  57. 85 75
      src/client/js/components/Admin/MarkdownSetting/XssForm.jsx
  58. 11 10
      src/client/js/components/Admin/Notification/GlobalNotification.jsx
  59. 33 26
      src/client/js/components/Admin/Notification/GlobalNotificationList.jsx
  60. 46 36
      src/client/js/components/Admin/Notification/ManageGlobalNotification.jsx
  61. 13 15
      src/client/js/components/Admin/Notification/NotificationDeleteModal.jsx
  62. 59 24
      src/client/js/components/Admin/Notification/NotificationSetting.jsx
  63. 32 34
      src/client/js/components/Admin/Notification/SlackAppConfiguration.jsx
  64. 4 3
      src/client/js/components/Admin/Notification/TriggerEventCheckBox.jsx
  65. 1 1
      src/client/js/components/Admin/Notification/UserNotificationRow.jsx
  66. 1 1
      src/client/js/components/Admin/Notification/UserTriggerNotification.jsx
  67. 10 10
      src/client/js/components/Admin/Security/BasicSecuritySetting.jsx
  68. 16 16
      src/client/js/components/Admin/Security/GitHubSecuritySetting.jsx
  69. 16 16
      src/client/js/components/Admin/Security/GoogleSecuritySetting.jsx
  70. 13 10
      src/client/js/components/Admin/Security/LdapAuthTestModal.jsx
  71. 76 53
      src/client/js/components/Admin/Security/LdapSecuritySetting.jsx
  72. 57 70
      src/client/js/components/Admin/Security/LocalSecuritySetting.jsx
  73. 34 32
      src/client/js/components/Admin/Security/OidcSecuritySetting.jsx
  74. 18 16
      src/client/js/components/Admin/Security/SamlSecuritySetting.jsx
  75. 121 62
      src/client/js/components/Admin/Security/SecurityManagement.jsx
  76. 114 143
      src/client/js/components/Admin/Security/SecuritySetting.jsx
  77. 16 16
      src/client/js/components/Admin/Security/TwitterSecuritySetting.jsx
  78. 1 1
      src/client/js/components/Admin/UserGroup/UserGroupCreateForm.jsx
  79. 14 15
      src/client/js/components/Admin/UserGroup/UserGroupDeleteModal.jsx
  80. 16 17
      src/client/js/components/Admin/UserGroup/UserGroupTable.jsx
  81. 3 3
      src/client/js/components/Admin/UserGroupDetail/CheckBoxForSerchUserOption.jsx
  82. 3 3
      src/client/js/components/Admin/UserGroupDetail/RadioButtonForSerchUserOption.jsx
  83. 4 4
      src/client/js/components/Admin/UserGroupDetail/UserGroupDetailPage.jsx
  84. 15 11
      src/client/js/components/Admin/UserGroupDetail/UserGroupEditForm.jsx
  85. 1 1
      src/client/js/components/Admin/UserGroupDetail/UserGroupPageList.jsx
  86. 3 3
      src/client/js/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx
  87. 11 9
      src/client/js/components/Admin/UserGroupDetail/UserGroupUserModal.jsx
  88. 19 11
      src/client/js/components/Admin/UserGroupDetail/UserGroupUserTable.jsx
  89. 23 18
      src/client/js/components/Admin/UserManagement.jsx
  90. 1 1
      src/client/js/components/Admin/Users/ExternalAccountTable.jsx
  91. 1 1
      src/client/js/components/Admin/Users/GiveAdminButton.jsx
  92. 1 1
      src/client/js/components/Admin/Users/InviteUserControl.jsx
  93. 11 12
      src/client/js/components/Admin/Users/PasswordResetModal.jsx
  94. 1 1
      src/client/js/components/Admin/Users/RemoveAdminButton.jsx
  95. 1 1
      src/client/js/components/Admin/Users/StatusActivateButton.jsx
  96. 1 1
      src/client/js/components/Admin/Users/StatusSuspendedButton.jsx
  97. 34 28
      src/client/js/components/Admin/Users/UserInviteModal.jsx
  98. 10 9
      src/client/js/components/Admin/Users/UserMenu.jsx
  99. 1 1
      src/client/js/components/Admin/Users/UserRemoveButton.jsx
  100. 8 8
      src/client/js/components/Admin/Users/UserTable.jsx

+ 1 - 0
.eslintrc.js

@@ -13,6 +13,7 @@ module.exports = {
     jquery: true,
     emojione: true,
     hljs: true,
+    ScrollPosStyler: true,
     window: true,
   },
   plugins: [

+ 5 - 0
CHANGES.md

@@ -1,5 +1,10 @@
 # CHANGES
 
+## v4.0.0-RC
+
+* Support: Upgrade libs
+    * bootstrap
+
 ## v3.7.3-RC
 
 *

+ 15 - 0
babel.config.js

@@ -14,6 +14,21 @@ module.exports = function(api) {
   ];
   const plugins = [
     'lodash',
+    // transform
+    //  from `import { Button } from 'reactstrap';`
+    //  to   `import Row from 'reactstrap/Button';`
+    [
+      'transform-imports', {
+        reactstrap: {
+          // eslint-disable-next-line no-template-curly-in-string
+          transform: 'reactstrap/es/${member}',
+          preventFullImport: true,
+        },
+      },
+    ],
+    [
+      '@babel/plugin-proposal-class-properties', { loose: true },
+    ],
   ];
 
   return {

+ 1 - 0
config/logger/config.dev.js

@@ -33,4 +33,5 @@ module.exports = {
   'growi:app': 'debug',
   'growi:services:*': 'debug',
   // 'growi:StaffCredit': 'debug',
+  // 'growi:TableOfContents': 'debug',
 };

+ 10 - 12
config/webpack.common.js

@@ -34,18 +34,16 @@ module.exports = (options) => {
       'styles/style-presentation':    './src/client/styles/scss/style-presentation.scss',
       // themes
       'styles/theme-default':         './src/client/styles/scss/theme/default.scss',
-      'styles/theme-default-dark':    './src/client/styles/scss/theme/default-dark.scss',
-      'styles/theme-nature':          './src/client/styles/scss/theme/nature.scss',
-      'styles/theme-mono-blue':       './src/client/styles/scss/theme/mono-blue.scss',
-      'styles/theme-future':          './src/client/styles/scss/theme/future.scss',
-      'styles/theme-blue-night':      './src/client/styles/scss/theme/blue-night.scss',
-      'styles/theme-kibela':          './src/client/styles/scss/theme/kibela.scss',
-      'styles/theme-halloween':       './src/client/styles/scss/theme/halloween.scss',
-      'styles/theme-wood':          './src/client/styles/scss/theme/wood.scss',
-      'styles/theme-christmas':          './src/client/styles/scss/theme/christmas.scss',
-      'styles/theme-island':      './src/client/styles/scss/theme/island.scss',
-      'styles/theme-spring':      './src/client/styles/scss/theme/spring.scss',
-      'styles/theme-antarctic':      './src/client/styles/scss/theme/antarctic.scss',
+      // 'styles/theme-nature':          './src/client/styles/scss/theme/nature.scss',
+      // 'styles/theme-mono-blue':       './src/client/styles/scss/theme/mono-blue.scss',
+      // 'styles/theme-future':          './src/client/styles/scss/theme/future.scss',
+      // 'styles/theme-blue-night':      './src/client/styles/scss/theme/blue-night.scss',
+      // 'styles/theme-kibela':          './src/client/styles/scss/theme/kibela.scss',
+      // 'styles/theme-halloween':       './src/client/styles/scss/theme/halloween.scss',
+      // 'styles/theme-wood':          './src/client/styles/scss/theme/wood.scss',
+      // 'styles/theme-christmas':          './src/client/styles/scss/theme/christmas.scss',
+      // 'styles/theme-island':      './src/client/styles/scss/theme/island.scss',
+      // 'styles/theme-antarctic':      './src/client/styles/scss/theme/antarctic.scss',
       // styles for external services
       'styles/style-hackmd':          './src/client/styles/hackmd/style.scss',
     }, options.entry || {}), // Merge with env dependent settings

+ 49 - 0
config/webpack.dev.dll.js

@@ -0,0 +1,49 @@
+/**
+ * @author: Yuki Takei <yuki@weseek.co.jp>
+ */
+const webpack = require('webpack');
+const helpers = require('../src/lib/util/helpers');
+
+
+module.exports = {
+  mode: 'development',
+  entry: {
+    dlls: [
+      // Libraries
+      'axios',
+      'browser-bunyan', 'bunyan-format',
+      'codemirror', 'react-codemirror2',
+      'date-fns',
+      'diff2html',
+      'debug',
+      'entities',
+      'growi-commons',
+      'i18next', 'i18next-browser-languagedetector',
+      'jquery-slimscroll',
+      'lodash', 'pako',
+      'markdown-it', 'csv-to-markdown-table',
+      'react', 'react-dom',
+      'reactstrap', 'react-bootstrap-typeahead',
+      'react-i18next', 'react-dropzone', 'react-hotkeys', 'react-copy-to-clipboard', 'react-waypoint',
+      'socket.io-client',
+      'toastr',
+      'unstated',
+      'xss',
+    ],
+  },
+  output: {
+    path: helpers.root('public/dll'),
+    filename: 'dll.js',
+    library: 'growi_dlls',
+  },
+  resolve: {
+    extensions: ['.js', '.json'],
+    modules: [helpers.root('src'), helpers.root('node_modules')],
+  },
+  plugins: [
+    new webpack.DllPlugin({
+      path: helpers.root('public/dll/manifest.json'),
+      name: 'growi_dlls',
+    }),
+  ],
+};

+ 9 - 3
package.json

@@ -154,7 +154,11 @@
       "handsontable: v7.0.0 or above is no loger MIT lisence."
     ],
     "@alienfast/i18next-loader": "^1.0.16",
+    "@atlaskit/drawer": "^5.3.5",
+    "@atlaskit/logo": "^12.3.3",
+    "@atlaskit/navigation-next": "^8.0.2",
     "@babel/core": "^7.4.5",
+    "@babel/plugin-proposal-class-properties": "^7.8.3",
     "@babel/polyfill": "^7.4.4",
     "@babel/preset-env": "^7.4.5",
     "@babel/preset-react": "^7.0.0",
@@ -163,8 +167,8 @@
     "babel-eslint": "^10.0.1",
     "babel-loader": "^8.0.6",
     "babel-plugin-lodash": "^3.3.4",
-    "bootstrap-sass": "^3.4.1",
-    "bootstrap-select": "^1.12.4",
+    "babel-plugin-transform-imports": "^2.0.0",
+    "bootstrap": "^4.4.1",
     "browser-bunyan": "^1.3.0",
     "browser-sync": "^2.26.3",
     "bunyan-debug": "^2.0.0",
@@ -218,7 +222,6 @@
     "postcss-loader": "^3.0.0",
     "prettier": "^1.19.1",
     "react": "^16.8.3",
-    "react-bootstrap": "^0.32.1",
     "react-bootstrap-typeahead": "^3.4.7",
     "react-codemirror2": "^6.0.0",
     "react-copy-to-clipboard": "^5.0.1",
@@ -228,12 +231,15 @@
     "react-hotkeys": "^2.0.0",
     "react-i18next": "^11.1.0",
     "react-waypoint": "^9.0.0",
+    "reactstrap": "^8.0.1",
     "replacestream": "^4.0.3",
     "reveal.js": "^3.5.0",
     "sass-loader": "^8.0.0",
     "simple-load-script": "^1.0.2",
     "socket.io-client": "^2.0.3",
+    "sticky-events": "^3.1.3",
     "style-loader": "^1.0.0",
+    "styled-components": "^5.0.1",
     "stylelint": "^13.2.0",
     "stylelint-config-recess-order": "^2.0.1",
     "swagger-jsdoc": "^3.4.0",

+ 2 - 1
resource/cdn-manifests.js

@@ -2,7 +2,8 @@ module.exports = {
   js: [
     {
       name: 'basis',
-      url: 'https://cdn.jsdelivr.net/combine/npm/emojione@3.1.2,npm/jquery@3.4.0,npm/bootstrap@3.4.1/dist/js/bootstrap.min.js',
+      // eslint-disable-next-line max-len
+      url: 'https://cdn.jsdelivr.net/combine/npm/emojione@3.1.2,npm/jquery@3.4.0,npm/popper.js@1.15.0,npm/bootstrap@4.4.1/dist/js/bootstrap.min.js,npm/scrollpos-styler@0.7.1,npm/jquery-slimscroll@1.3.8/jquery.slimscroll.min.js',
       groups: ['basis'],
       args: {
         integrity: '',

+ 3 - 3
resource/locales/en-US/translation.json

@@ -61,7 +61,7 @@
   "No diff": "No diff",
   "Shrink versions that have no diffs": "Shrink versions that have no diffs",
   "User ID": "User ID",
-  "Home": "Home",
+  "User's Home": "User's Home",
   "User Settings": "User Settings",
   "User Information": "User Information",
   "Basic Info": "Basic Info",
@@ -325,7 +325,7 @@
   "template": {
     "modal_label": {
       "Create/Edit Template Page": "Create/Edit Template Page",
-      "Create template under": "Create template page under:<br /><code><small>%s</small></code>"
+      "Create template under": "Create template page under:<br /><code>%s</code>"
     },
     "option_label": {
       "create/edit": "Create/Edit Template page..",
@@ -642,7 +642,7 @@
     "export_collections": "Export Collections",
     "check_all": "Check All",
     "uncheck_all": "Uncheck All",
-    "desc_password_seed": "DO NOT FORGET to set current <code>PASSWORD_SEED</code> to your new GROWI system when restoring user data, or users will NOT be able to login with their password.<br><br><strong>HINT:</strong><br>The current <code>PASSWORD_SEED</code> will be stored in <code>meta.json</code> in exported ZIP.",
+    "desc_password_seed": "<p>DO NOT FORGET to set current <code>PASSWORD_SEED</code> to your new GROWI system when restoring user data, or users will NOT be able to login with their password.</p><strong>HINT:</strong><p>The current <code>PASSWORD_SEED</code> will be stored in <code>meta.json</code> in exported ZIP.</p>",
     "create_new_archive_data": "Create New Archive Data",
     "export": "Export",
     "cancel": "Cancel",

+ 3 - 3
resource/locales/en-US/welcome.md

@@ -3,9 +3,9 @@
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
 [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
 
-<div class="panel panel-primary">
-  <div class="panel-heading">Tips</div>
-  <div class="panel-body"><ul>
+<div class="card">
+  <div class="card-header">Tips</div>
+  <div class="card-body"><ul>
     <li>Ctrl(⌘)-/ to show quick help</li>
     <li>You can write HTML with <a href="https://getbootstrap.com/docs/3.3/css/">Bootstrap 3</a>.</li>
   </ul></div>

+ 3 - 3
resource/locales/ja/translation.json

@@ -61,7 +61,7 @@
   "No diff": "差分なし",
   "Shrink versions that have no diffs": "差分のないバージョンをコンパクトに表示する",
   "User ID": "ユーザーID",
-  "Home": "ホーム",
+  "User's Home": "ユーザーホーム",
   "User Settings": "ユーザー設定",
   "User Information": "ユーザー情報",
   "Basic Info": "ユーザーの基本情報",
@@ -323,7 +323,7 @@
   "template": {
     "modal_label": {
       "Create/Edit Template Page": "テンプレートページの作成/編集",
-      "Create template under": "<code><small>%s</small></code><br />にテンプレートページを作成"
+      "Create template under": "<code>%s</code><br />にテンプレートページを作成"
     },
     "option_label": {
       "select": "テンプレートタイプを選択してください",
@@ -631,7 +631,7 @@
     "export_collections": "コレクションのエクスポート",
     "check_all": "全てにチェックを付ける",
     "uncheck_all": "全てからチェックを外す",
-    "desc_password_seed": "ユーザーデータをバックアップ/リストアする場合、現在の <code>PASSWORD_SEED</code> を新しい GROWI システムにセットすることを忘れないでください。さもなくば、ユーザーがパスワードでログインできなくなります。<br><br><strong>ヒント:</strong><br>現在の <code>PASSWORD_SEED</code> は、エクスポートされる ZIP 中の <code>meta.json</code> に保存されます。",
+    "desc_password_seed": "<p>ユーザーデータをバックアップ/リストアする場合、現在の <code>PASSWORD_SEED</code> を新しい GROWI システムにセットすることを忘れないでください。さもなくば、ユーザーがパスワードでログインできなくなります。</p><strong>ヒント:</strong><p>現在の <code>PASSWORD_SEED</code> は、エクスポートされる ZIP 中の <code>meta.json</code> に保存されます。</p>",
     "create_new_archive_data": "アーカイブデータの新規作成",
     "export": "エクスポート",
     "cancel": "キャンセル",

+ 3 - 3
resource/locales/ja/welcome.md

@@ -3,9 +3,9 @@
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
 [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
 
-<div class="panel panel-primary">
-  <div class="panel-heading">Tips</div>
-  <div class="panel-body"><ul>
+<div class="card">
+  <div class="card-header">Tips</div>
+  <div class="card-body"><ul>
     <li>Ctrl(⌘)-/ でショートカットヘルプを表示します</li>
       <li>HTML/CSS の記述時は、<a href="https://getbootstrap.com/docs/3.3/css/">Bootstrap 3</a> を利用できます</li>
   </ul></div>

+ 5 - 0
src/client/js/app.jsx

@@ -36,6 +36,7 @@ import PageContainer from './services/PageContainer';
 import CommentContainer from './services/CommentContainer';
 import EditorContainer from './services/EditorContainer';
 import TagContainer from './services/TagContainer';
+import GrowiSubNavigation from './components/Navbar/GrowiSubNavigation';
 import PersonalContainer from './services/PersonalContainer';
 
 import { appContainer, componentMappings } from './bootstrap';
@@ -104,6 +105,7 @@ if (pageContainer.state.path != null) {
     'page': <Page />,
     'revision-path': <RevisionPath behaviorType={appContainer.config.behaviorType} pageId={pageContainer.state.pageId} pagePath={pageContainer.state.path} />,
     'tag-label': <TagLabels />,
+    'grw-subnav': <GrowiSubNavigation />,
   });
 }
 
@@ -129,3 +131,6 @@ $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
     </I18nextProvider>, document.getElementById('revision-history'),
   );
 });
+
+// initialize scrollpos-styler
+ScrollPosStyler.init();

+ 4 - 1
src/client/js/bootstrap.jsx

@@ -5,6 +5,7 @@ import Xss from '@commons/service/xss';
 
 import HeaderSearchBox from './components/HeaderSearchBox';
 import PersonalDropdown from './components/Navbar/PersonalDropdown';
+import Sidebar from './components/Sidebar';
 import StaffCredit from './components/StaffCredit/StaffCredit';
 
 import AppContainer from './services/AppContainer';
@@ -27,7 +28,7 @@ const websocketContainer = new WebsocketContainer(appContainer);
 
 logger.info('unstated containers have been initialized');
 
-appContainer.initPlugins();
+appContainer.init();
 appContainer.injectToWindow();
 
 /**
@@ -40,6 +41,8 @@ const componentMappings = {
   'search-sidebar': <HeaderSearchBox crowi={appContainer} />,
   'personal-dropdown': <PersonalDropdown />,
 
+  'grw-sidebar': <Sidebar />,
+
   'staff-credit': <StaffCredit />,
 };
 

+ 2 - 2
src/client/js/components/Admin/AdminHome/AdminHome.jsx

@@ -41,14 +41,14 @@ class AdminHome extends React.Component {
         </p>
 
         <div className="row mb-5">
-          <div className="col-md-12">
+          <div className="col-lg-12">
             <h2 className="admin-setting-header">{t('admin:admin_top.system_information')}</h2>
             <SystemInfomationTable />
           </div>
         </div>
 
         <div className="row mb-5">
-          <div className="col-md-12">
+          <div className="col-lg-12">
             <h2 className="admin-setting-header">{t('admin:admin_top.list_of_installed_plugins')}</h2>
             <InstalledPluginTable />
           </div>

+ 1 - 1
src/client/js/components/Admin/AdminHome/SystemInfomationTable.jsx

@@ -15,7 +15,7 @@ class SystemInformationTable extends React.Component {
       <table className="table table-bordered">
         <tbody>
           <tr>
-            <th className="col-sm-4">GROWI</th>
+            <th>GROWI</th>
             <td>{ adminHomeContainer.state.growiVersion }</td>
           </tr>
           <tr>

+ 24 - 21
src/client/js/components/Admin/App/AppSetting.jsx

@@ -38,9 +38,9 @@ class AppSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">{t('admin:app_setting.site_name')}</label>
-          <div className="col-xs-6">
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">{t('admin:app_setting.site_name')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -48,13 +48,13 @@ class AppSetting extends React.Component {
               onChange={(e) => { adminAppContainer.changeTitle(e.target.value) }}
               placeholder="GROWI"
             />
-            <p className="help-block">{t('admin:app_setting.sitename_change')}</p>
+            <p className="form-text text-muted">{t('admin:app_setting.sitename_change')}</p>
           </div>
         </div>
 
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">{t('admin:app_setting.confidential_name')}</label>
-          <div className="col-xs-6">
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">{t('admin:app_setting.confidential_name')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -62,53 +62,56 @@ class AppSetting extends React.Component {
               onChange={(e) => { adminAppContainer.changeConfidential(e.target.value) }}
               placeholder={t('admin:app_setting.confidential_example')}
             />
-            <p className="help-block">{t('admin:app_setting.header_content')}</p>
+            <p className="form-text text-muted">{t('admin:app_setting.header_content')}</p>
           </div>
         </div>
 
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">{t('admin:app_setting.default_language')}</label>
-          <div className="col-xs-6">
-            <div className="radio radio-primary radio-inline">
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">{t('admin:app_setting.default_language')}</label>
+          <div className="col-6">
+            <div className="custom-control custom-radio custom-control-inline">
               <input
                 type="radio"
                 id="radioLangEn"
+                className="custom-control-input"
                 name="globalLang"
                 value="en-US"
                 checked={adminAppContainer.state.globalLang === 'en-US'}
                 onChange={(e) => { adminAppContainer.changeGlobalLang(e.target.value) }}
               />
-              <label htmlFor="radioLangEn">{t('English')}</label>
+              <label className="custom-control-label" htmlFor="radioLangEn">{t('English')}</label>
             </div>
-            <div className="radio radio-primary radio-inline">
+            <div className="custom-control custom-radio custom-control-inline">
               <input
                 type="radio"
                 id="radioLangJa"
+                className="custom-control-input"
                 name="globalLang"
                 value="ja"
                 checked={adminAppContainer.state.globalLang === 'ja'}
                 onChange={(e) => { adminAppContainer.changeGlobalLang(e.target.value) }}
               />
-              <label htmlFor="radioLangJa">{t('Japanese')}</label>
+              <label className="custom-control-label" htmlFor="radioLangJa">{t('Japanese')}</label>
             </div>
           </div>
         </div>
 
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">{t('admin:app_setting.file_uploading')}</label>
-          <div className="col-xs-6">
-            <div className="checkbox checkbox-info">
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">{t('admin:app_setting.file_uploading')}</label>
+          <div className="col-6">
+            <div className="custom-control custom-checkbox custom-checkbox-info">
               <input
                 type="checkbox"
                 id="cbFileUpload"
+                className="custom-control-input"
                 name="fileUpload"
                 checked={adminAppContainer.state.fileUpload}
                 onChange={(e) => { adminAppContainer.changeFileUpload(e.target.checked) }}
               />
-              <label htmlFor="cbFileUpload">{t('admin:app_setting.enable_files_except_image')}</label>
+              <label className="custom-control-label" htmlFor="cbFileUpload">{t('admin:app_setting.enable_files_except_image')}</label>
             </div>
 
-            <p className="help-block">
+            <p className="form-text text-muted">
               {t('admin:app_setting.enable_files_except_image')}
               <br />
               {t('admin:app_setting.attach_enable')}

+ 5 - 5
src/client/js/components/Admin/App/AppSettingsPage.jsx

@@ -38,35 +38,35 @@ class AppSettingsPage extends React.Component {
     return (
       <Fragment>
         <div className="row">
-          <div className="col-md-12">
+          <div className="col-lg-12">
             <h2 className="admin-setting-header">{t('App Settings')}</h2>
             <AppSetting />
           </div>
         </div>
 
         <div className="row mt-5">
-          <div className="col-md-12">
+          <div className="col-lg-12">
             <h2 className="admin-setting-header">{t('Site URL settings')}</h2>
             <SiteUrlSetting />
           </div>
         </div>
 
         <div className="row mt-5">
-          <div className="col-md-12">
+          <div className="col-lg-12">
             <h2 className="admin-setting-header">{t('admin:app_setting.mail_settings')}</h2>
             <MailSetting />
           </div>
         </div>
 
         <div className="row mt-5">
-          <div className="col-md-12">
+          <div className="col-lg-12">
             <h2 className="admin-setting-header">{t('admin:app_setting.aws_settings')}</h2>
             <AwsSetting />
           </div>
         </div>
 
         <div className="row mt-5">
-          <div className="col-md-12">
+          <div className="col-lg-12">
             <h2 className="admin-setting-header">{t('admin:app_setting.plugin_settings')}</h2>
             <PluginSetting />
           </div>

+ 17 - 17
src/client/js/components/Admin/App/AwsSetting.jsx

@@ -38,7 +38,7 @@ class AwsSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <p className="well">
+        <p className="card well">
           {t('admin:app_setting.aws_access')}
           <br />
           {t('admin:app_setting.no_smtp_setting')}
@@ -50,11 +50,11 @@ class AwsSetting extends React.Component {
           </span>
         </p>
 
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">
             {t('admin:app_setting.region')}
           </label>
-          <div className="col-xs-6">
+          <div className="col-6">
             <input
               className="form-control"
               placeholder={`${t('eg')} ap-northeast-1`}
@@ -66,11 +66,11 @@ class AwsSetting extends React.Component {
           </div>
         </div>
 
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">
             {t('admin:app_setting.custom_endpoint')}
           </label>
-          <div className="col-xs-6">
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -80,15 +80,15 @@ class AwsSetting extends React.Component {
                 adminAppContainer.changeCustomEndpoint(e.target.value);
               }}
             />
-            <p className="help-block">{t('admin:app_setting.custom_endpoint_change')}</p>
+            <p className="form-text text-muted">{t('admin:app_setting.custom_endpoint_change')}</p>
           </div>
         </div>
 
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">
             {t('admin:app_setting.bucket_name')}
           </label>
-          <div className="col-xs-6">
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -101,11 +101,11 @@ class AwsSetting extends React.Component {
           </div>
         </div>
 
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">
             Access Key ID
           </label>
-          <div className="col-xs-6">
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -117,11 +117,11 @@ class AwsSetting extends React.Component {
           </div>
         </div>
 
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">
             Secret Access Key
           </label>
-          <div className="col-xs-6">
+          <div className="col-6">
             <input
               className="form-control"
               type="text"

+ 11 - 11
src/client/js/components/Admin/App/MailSetting.jsx

@@ -38,10 +38,10 @@ class MailSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <p className="well">{t('admin:app_setting.smtp_used')} {t('admin:app_setting.smtp_but_aws')}<br />{t('admin:app_setting.neihter_of')}</p>
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">{t('admin:app_setting.from_e-mail_address')}</label>
-          <div className="col-xs-6">
+        <p className="card well">{t('admin:app_setting.smtp_used')} {t('admin:app_setting.smtp_but_aws')}<br />{t('admin:app_setting.neihter_of')}</p>
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">{t('admin:app_setting.from_e-mail_address')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -52,9 +52,9 @@ class MailSetting extends React.Component {
           </div>
         </div>
 
-        <div className="row mb-5">
-          <label className="col-xs-3 control-label">{t('admin:app_setting.smtp_settings')}</label>
-          <div className="col-xs-4">
+        <div className="row form-group mb-5">
+          <label className="col-3 col-form-label">{t('admin:app_setting.smtp_settings')}</label>
+          <div className="col-4">
             <label>{t('admin:app_setting.host')}</label>
             <input
               className="form-control"
@@ -63,7 +63,7 @@ class MailSetting extends React.Component {
               onChange={(e) => { adminAppContainer.changeSmtpHost(e.target.value) }}
             />
           </div>
-          <div className="col-xs-2">
+          <div className="col-2">
             <label>{t('admin:app_setting.port')}</label>
             <input
               className="form-control"
@@ -73,8 +73,8 @@ class MailSetting extends React.Component {
           </div>
         </div>
 
-        <div className="row mb-5">
-          <div className="col-xs-3 col-xs-offset-3">
+        <div className="row form-group mb-5">
+          <div className="col-3 offset-3">
             <label>{t('admin:app_setting.user')}</label>
             <input
               className="form-control"
@@ -83,7 +83,7 @@ class MailSetting extends React.Component {
               onChange={(e) => { adminAppContainer.changeSmtpUser(e.target.value) }}
             />
           </div>
-          <div className="col-xs-3">
+          <div className="col-3">
             <label>{t('Password')}</label>
             <input
               className="form-control"

+ 6 - 5
src/client/js/components/Admin/App/PluginSetting.jsx

@@ -39,20 +39,21 @@ class PluginSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <p className="well">{t('admin:app_setting.enable_plugin_loading')}</p>
+        <p className="card well">{t('admin:app_setting.enable_plugin_loading')}</p>
 
-        <div className="row mb-5">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+        <div className="row form-group mb-5">
+          <div className="offset-3 col-6 text-left">
+            <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isEnabledPlugins"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminAppContainer.state.isEnabledPlugins}
                 onChange={(e) => {
                   adminAppContainer.changeIsEnabledPlugins(e.target.checked);
                 }}
               />
-              <label htmlFor="isEnabledPlugins">{t('admin:app_setting.load_plugins')}</label>
+              <label className="custom-control-label" htmlFor="isEnabledPlugins">{t('admin:app_setting.load_plugins')}</label>
             </div>
           </div>
         </div>

+ 40 - 42
src/client/js/components/Admin/App/SiteUrlSetting.jsx

@@ -38,51 +38,49 @@ class SiteUrlSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <p className="well">{t('admin:app_setting.site_url_desc')}</p>
+        <p className="card well">{t('admin:app_setting.site_url_desc')}</p>
         {!adminAppContainer.state.isSetSiteUrl
           && (<p className="alert alert-danger"><i className="icon-exclamation"></i> {t('admin:app_setting.site_url_warn')}</p>)}
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="col-xs-offset-3">
-              <table className="table settings-table">
-                <colgroup>
-                  <col className="from-db" />
-                  <col className="from-env-vars" />
-                </colgroup>
-                <thead>
-                  <tr>
-                    <th>Database</th>
-                    <th>Environment variables</th>
-                  </tr>
-                </thead>
-                <tbody>
-                  <tr>
-                    <td>
-                      <input
-                        className="form-control"
-                        type="text"
-                        name="settingForm[app:siteUrl]"
-                        defaultValue={adminAppContainer.state.siteUrl || ''}
-                        onChange={(e) => { adminAppContainer.changeSiteUrl(e.target.value) }}
-                        placeholder="e.g. https://my.growi.org"
-                      />
-                      <p className="help-block">
-                        {/* eslint-disable-next-line react/no-danger */}
-                        <span dangerouslySetInnerHTML={{ __html: t('admin:app_setting.siteurl_help') }} />
-                      </p>
-                    </td>
-                    <td>
-                      <input className="form-control" type="text" value={adminAppContainer.state.envSiteUrl || ''} readOnly />
-                      <p className="help-block">
-                        {/* eslint-disable-next-line react/no-danger */}
-                        <span dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'APP_SITE_URL' }) }} />
-                      </p>
-                    </td>
-                  </tr>
-                </tbody>
-              </table>
-            </div>
+        <div className="row form-group">
+          <div className="col-9 offset-3">
+            <table className="table settings-table">
+              <colgroup>
+                <col className="from-db" />
+                <col className="from-env-vars" />
+              </colgroup>
+              <thead>
+                <tr>
+                  <th>Database</th>
+                  <th>Environment variables</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td>
+                    <input
+                      className="form-control"
+                      type="text"
+                      name="settingForm[app:siteUrl]"
+                      defaultValue={adminAppContainer.state.siteUrl || ''}
+                      onChange={(e) => { adminAppContainer.changeSiteUrl(e.target.value) }}
+                      placeholder="e.g. https://my.growi.org"
+                    />
+                    <p className="form-text text-muted">
+                      {/* eslint-disable-next-line react/no-danger */}
+                      <span dangerouslySetInnerHTML={{ __html: t('admin:app_setting.siteurl_help') }} />
+                    </p>
+                  </td>
+                  <td>
+                    <input className="form-control" type="text" value={adminAppContainer.state.envSiteUrl || ''} readOnly />
+                    <p className="form-text text-muted">
+                      {/* eslint-disable-next-line react/no-danger */}
+                      <span dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'APP_SITE_URL' }) }} />
+                    </p>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
           </div>
         </div>
 

+ 40 - 35
src/client/js/components/Admin/Common/AdminNavigation.jsx

@@ -12,41 +12,46 @@ const AdminNavigation = (props) => {
   };
 
   return (
-    <ul className="nav nav-pills nav-stacked">
-      <li className={`${pathname === '/admin' && 'active'}`}>
-        <a href="/admin"><i className="icon-fw icon-home"></i> { t('Management Wiki Home') }</a>
-      </li>
-      <li className={`${isActiveMenu('/app') && 'active'}`}>
-        <a href="/admin/app"><i className="icon-fw icon-settings"></i> { t('App Settings') }</a>
-      </li>
-      <li className={`${isActiveMenu('/security') && 'active'}`}>
-        <a href="/admin/security"><i className="icon-fw icon-shield"></i> { t('security_settings') }</a>
-      </li>
-      <li className={`${isActiveMenu('/markdown') && 'active'}`}>
-        <a href="/admin/markdown"><i className="icon-fw icon-note"></i> { t('Markdown Settings') }</a>
-      </li>
-      <li className={`${isActiveMenu('/customize') && 'active'}`}>
-        <a href="/admin/customize"><i className="icon-fw icon-wrench"></i> { t('Customize') }</a>
-      </li>
-      <li className={`${isActiveMenu('/importer') && 'active'}`}>
-        <a href="/admin/importer"><i className="icon-fw icon-cloud-upload"></i> { t('Import Data') }</a>
-      </li>
-      <li className={`${isActiveMenu('/export') && 'active'}`}>
-        <a href="/admin/export"><i className="icon-fw icon-cloud-download"></i> { t('Export Archive Data') }</a>
-      </li>
-      <li className={`${(isActiveMenu('/notification') || isActiveMenu('/global-notification')) && 'active'}`}>
-        <a href="/admin/notification"><i className="icon-fw icon-bell"></i> { t('Notification Settings') }</a>
-      </li>
-      <li className={`${(isActiveMenu('/users')) && 'active'}`}>
-        <a href="/admin/users"><i className="icon-fw icon-user"></i> { t('User_Management') }</a>
-      </li>
-      <li className={`${isActiveMenu('/user-group') && 'active'}`}>
-        <a href="/admin/user-groups"><i className="icon-fw icon-people"></i> { t('UserGroup Management') }</a>
-      </li>
-      <li className={`${isActiveMenu('/search') && 'active'}`}>
-        <a href="/admin/search"><i className="icon-fw icon-magnifier"></i> { t('Full Text Search Management') }</a>
-      </li>
-    </ul>
+    <div className="list-group admin-navigation">
+      <a href="/admin" className={`list-group-item list-group-item-action border-0 round-corner ${pathname === '/admin' && 'active'}`}>
+        <i className="icon-fw icon-home"></i> {t('Management Wiki Home')}
+      </a>
+      <a href="/admin/app" className={`list-group-item list-group-item-action border-0 round-corner ${isActiveMenu('/app') && 'active'}`}>
+        <i className="icon-fw icon-settings"></i> {t('App Settings')}
+      </a>
+      <a href="/admin/security" className={`list-group-item list-group-item-action border-0 round-corner  ${isActiveMenu('/security') && 'active'}`}>
+        <i className="icon-fw icon-shield"></i> {t('security_settings')}
+      </a>
+      <a href="/admin/markdown" className={`list-group-item list-group-item-action border-0 round-corner ${isActiveMenu('/markdown') && 'active'}`}>
+        <i className="icon-fw icon-note"></i> {t('Markdown Settings')}
+      </a>
+      <a href="/admin/customize" className={`list-group-item list-group-item-action border-0 round-corner ${isActiveMenu('/customize') && 'active'}`}>
+        <i className="icon-fw icon-wrench"></i> {t('Customize')}
+      </a>
+      <a href="/admin/importer" className={`list-group-item list-group-item-action border-0 round-corner ${isActiveMenu('/importer') && 'active'}`}>
+        <i className="icon-fw icon-cloud-upload"></i> {t('Import Data')}
+      </a>
+      <a href="/admin/export" className={`list-group-item list-group-item-action border-0 round-corner ${isActiveMenu('/export') && 'active'}`}>
+        <i className="icon-fw icon-cloud-download"></i> {t('Export Archive Data')}
+      </a>
+      <a
+        href="/admin/notification"
+        className={
+          `list-group-item list-group-item-action border-0 round-corner ${(isActiveMenu('/notification') || isActiveMenu('/global-notification')) && 'active'}`
+        }
+      >
+        <i className="icon-fw icon-bell"></i> {t('Notification Settings')}
+      </a>
+      <a href="/admin/users" className={`list-group-item list-group-item-action border-0 round-corner ${(isActiveMenu('/users')) && 'active'}`}>
+        <i className="icon-fw icon-user"></i> {t('User_Management')}
+      </a>
+      <a href="/admin/user-groups" className={`list-group-item list-group-item-action border-0 round-corner ${isActiveMenu('/user-group') && 'active'}`}>
+        <i className="icon-fw icon-people"></i> {t('UserGroup Management')}
+      </a>
+      <a href="/admin/search" className={`list-group-item list-group-item-action border-0 round-corner ${isActiveMenu('/search') && 'active'}`}>
+        <i className="icon-fw icon-magnifier"></i> {t('Full Text Search Management')}
+      </a>
+    </div>
   );
 };
 

+ 9 - 13
src/client/js/components/Admin/Common/AdminUpdateButtonRow.jsx

@@ -2,21 +2,17 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
-class AdminUpdateButtonRow extends React.PureComponent {
+const AdminUpdateButtonRow = (props) => {
+  const { t } = props;
 
-  render() {
-    const { t } = this.props;
-
-    return (
-      <div className="row my-3">
-        <div className="col-xs-offset-4 col-xs-5">
-          <button type="button" className="btn btn-primary" onClick={this.props.onClick} disabled={this.props.disabled}>{ t('Update') }</button>
-        </div>
+  return (
+    <div className="row my-3">
+      <div className="offset-4 col-5">
+        <button type="button" className="btn btn-primary" onClick={props.onClick} disabled={props.disabled}>{ t('Update') }</button>
       </div>
-    );
-  }
-
-}
+    </div>
+  );
+};
 
 AdminUpdateButtonRow.propTypes = {
   t: PropTypes.func.isRequired, // i18next

+ 5 - 5
src/client/js/components/Admin/Common/ProgressBar.jsx

@@ -17,13 +17,13 @@ class ProgressBar extends React.Component {
 
     return (
       <>
-        <h5 className="my-1">
+        <h6 className="my-1">
           {header}
-          <div className="pull-right">{currentCount} / {totalCount}</div>
-        </h5>
-        <div className="progress progress-sm">
+          <div className="float-right">{currentCount} / {totalCount}</div>
+        </h6>
+        <div className="progress">
           <div
-            className={`progress-bar ${isActive ? 'progress-bar-info progress-bar-striped active' : 'progress-bar-success'}`}
+            className={`progress-bar ${isActive ? 'bg-info progress-bar-striped active' : 'bg-success'}`}
             style={{ width: `${percentage}%` }}
           >
             <span className="sr-only">{percentage.toFixed(0)}% Complete</span>

+ 9 - 3
src/client/js/components/Admin/Customize/CustomizeBehaviorOption.jsx

@@ -9,9 +9,15 @@ class CustomizeBehaviorOption extends React.PureComponent {
     return (
       <React.Fragment>
         <h4>
-          <div className="radio radio-primary">
-            <input type="radio" id={`radioBehavior${this.props.behaviorType}`} checked={this.props.isSelected} onChange={this.props.onSelected} />
-            <label htmlFor={`radioBehavior${this.props.behaviorType}`}>
+          <div className="custom-control custom-radio">
+            <input
+              type="radio"
+              className="custom-control-input"
+              id={`radioBehavior${this.props.behaviorType}`}
+              checked={this.props.isSelected}
+              onChange={this.props.onSelected}
+            />
+            <label className="custom-control-label" htmlFor={`radioBehavior${this.props.behaviorType}`}>
               {/* eslint-disable-next-line react/no-danger */}
               <span dangerouslySetInnerHTML={{ __html: this.props.labelHtml }} />
             </label>

+ 39 - 35
src/client/js/components/Admin/Customize/CustomizeBehaviorSetting.jsx

@@ -38,44 +38,48 @@ class CustomizeBehaviorSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.behavior')}</h2>
         <div className="row">
-          <div className="col-xs-6">
-            <CustomizeBehaviorOption
-              behaviorType="growi"
-              isSelected={adminCustomizeContainer.state.currentBehavior === 'growi'}
-              onSelected={() => adminCustomizeContainer.switchBehaviorType('growi')}
-              labelHtml={`GROWI Simplified Behavior <small class="text-success">${t('admin:customize_setting.recommended')}</small>`}
-            >
-              <ul>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text1') }} /></li>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text2') }} /></li>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text3') }} /></li>
-              </ul>
-            </CustomizeBehaviorOption>
-          </div>
-
-          <div className="col-xs-6">
-            <CustomizeBehaviorOption
-              behaviorType="crowi-plus"
-              isSelected={adminCustomizeContainer.state.currentBehavior === 'crowi-plus'}
-              onSelected={() => adminCustomizeContainer.switchBehaviorType('crowi-plus')}
-              labelHtml="Crowi Classic Behavior"
-            >
-              <ul>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text1') }} /></li>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text2') }} /></li>
-                <ul>
-                  <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text3') }} /></li>
-                </ul>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text4') }} /></li>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text5') }} /></li>
-              </ul>
-            </CustomizeBehaviorOption>
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.behavior')}</h2>
+            <div className="row">
+              <div className="col-6">
+                <CustomizeBehaviorOption
+                  behaviorType="growi"
+                  isSelected={adminCustomizeContainer.state.currentBehavior === 'growi'}
+                  onSelected={() => adminCustomizeContainer.switchBehaviorType('growi')}
+                  labelHtml={`GROWI Simplified Behavior <small class="text-success">${t('admin:customize_setting.recommended')}</small>`}
+                >
+                  <ul>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text1') }} /></li>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text2') }} /></li>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text3') }} /></li>
+                  </ul>
+                </CustomizeBehaviorOption>
+              </div>
+
+              <div className="col-6">
+                <CustomizeBehaviorOption
+                  behaviorType="crowi-plus"
+                  isSelected={adminCustomizeContainer.state.currentBehavior === 'crowi-plus'}
+                  onSelected={() => adminCustomizeContainer.switchBehaviorType('crowi-plus')}
+                  labelHtml="Crowi Classic Behavior"
+                >
+                  <ul>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text1') }} /></li>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text2') }} /></li>
+                    <ul>
+                      <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text3') }} /></li>
+                    </ul>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text4') }} /></li>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text5') }} /></li>
+                  </ul>
+                </CustomizeBehaviorOption>
+              </div>
+            </div>
+
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           </div>
         </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 24 - 19
src/client/js/components/Admin/Customize/CustomizeCssSetting.jsx

@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { Card, CardBody } from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -36,27 +37,31 @@ class CustomizeCssSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.custom_css')}</h2>
-        <p className="well">
-          {t('admin:customize_setting.write_css')}<br />
-          {t('admin:customize_setting.reflect_change')}
-        </p>
-        <div className="form-group">
-          <div className="col-xs-12">
-            <CustomCssEditor
-              value={adminCustomizeContainer.state.currentCustomizeCss || ''}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeCss(inputValue) }}
-            />
-          </div>
-          <div className="col-xs-12">
-            <p className="help-block text-right">
-              <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
-              {t('admin:customize_setting.ctrl_space')}
-            </p>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.custom_css')}</h2>
+
+            <Card className="card well my-3">
+              <CardBody className="px-0 py-2">
+                { t('admin:customize_setting.write_css') }<br />
+                { t('admin:customize_setting.reflect_change') }
+              </CardBody>
+            </Card>
+
+            <div className="form-group">
+              <CustomCssEditor
+                value={adminCustomizeContainer.state.currentCustomizeCss || ''}
+                onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeCss(inputValue) }}
+              />
+              <p className="form-text text-muted text-right">
+                <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
+                {t('admin:customize_setting.ctrl_space')}
+              </p>
+            </div>
+
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           </div>
         </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 3 - 2
src/client/js/components/Admin/Customize/CustomizeFunctionOption.jsx

@@ -7,14 +7,15 @@ class CustomizeFunctionOption extends React.PureComponent {
   render() {
     return (
       <React.Fragment>
-        <div className="checkbox checkbox-success">
+        <div className="custom-control custom-switch custom-checkbox-success">
           <input
+            className="custom-control-input"
             type="checkbox"
             id={this.props.optionId}
             checked={this.props.isChecked}
             onChange={this.props.onChecked}
           />
-          <label htmlFor={this.props.optionId}>
+          <label className="custom-control-label" htmlFor={this.props.optionId}>
             <strong>{this.props.label}</strong>
           </label>
         </div>

+ 122 - 104
src/client/js/components/Admin/Customize/CustomizeFunctionSetting.jsx

@@ -1,6 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import {
+  Card, CardBody,
+  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
+} from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -16,9 +20,18 @@ class CustomizeBehaviorSetting extends React.Component {
   constructor(props) {
     super(props);
 
+    this.state = {
+      isDropdownOpen: false,
+    };
+
+    this.onToggleDropdown = this.onToggleDropdown.bind(this);
     this.onClickSubmit = this.onClickSubmit.bind(this);
   }
 
+  onToggleDropdown() {
+    this.setState({ isDropdownOpen: !this.state.isDropdownOpen });
+  }
+
   async onClickSubmit() {
     const { t, adminCustomizeContainer } = this.props;
 
@@ -36,119 +49,124 @@ class CustomizeBehaviorSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.function')}</h2>
-        <p className="well">{t('admin:customize_setting.function_desc')}</p>
-
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <CustomizeFunctionOption
-              optionId="isEnabledTimeline"
-              label={t('admin:customize_setting.function_options.timeline')}
-              isChecked={adminCustomizeContainer.state.isEnabledTimeline}
-              onChecked={() => { adminCustomizeContainer.switchEnableTimeline() }}
-            >
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.timeline_desc1')}<br />
-                {t('admin:customize_setting.function_options.timeline_desc2')}<br />
-                {t('admin:customize_setting.function_options.timeline_desc3')}
-              </p>
-            </CustomizeFunctionOption>
-          </div>
-        </div>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.function')}</h2>
+            <Card className="card well my-3">
+              <CardBody className="px-0 py-2">
+                {t('admin:customize_setting.function_desc')}
+              </CardBody>
+            </Card>
+
+
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <CustomizeFunctionOption
+                  optionId="isEnabledTimeline"
+                  label={t('admin:customize_setting.function_options.timeline')}
+                  isChecked={adminCustomizeContainer.state.isEnabledTimeline}
+                  onChecked={() => { adminCustomizeContainer.switchEnableTimeline() }}
+                >
+                  <p className="form-text text-muted">
+                    {t('admin:customize_setting.function_options.timeline_desc1')}<br />
+                    {t('admin:customize_setting.function_options.timeline_desc2')}<br />
+                    {t('admin:customize_setting.function_options.timeline_desc3')}
+                  </p>
+                </CustomizeFunctionOption>
+              </div>
+            </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <CustomizeFunctionOption
-              optionId="isSavedStatesOfTabChanges"
-              label={t('admin:customize_setting.function_options.tab_switch')}
-              isChecked={adminCustomizeContainer.state.isSavedStatesOfTabChanges}
-              onChecked={() => { adminCustomizeContainer.switchSavedStatesOfTabChanges() }}
-            >
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.tab_switch_desc1')}<br />
-                {t('admin:customize_setting.function_options.tab_switch_desc2')}
-              </p>
-            </CustomizeFunctionOption>
-          </div>
-        </div>
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <CustomizeFunctionOption
+                  optionId="isSavedStatesOfTabChanges"
+                  label={t('admin:customize_setting.function_options.tab_switch')}
+                  isChecked={adminCustomizeContainer.state.isSavedStatesOfTabChanges}
+                  onChecked={() => { adminCustomizeContainer.switchSavedStatesOfTabChanges() }}
+                >
+                  <p className="form-text text-muted">
+                    {t('admin:customize_setting.function_options.tab_switch_desc1')}<br />
+                    {t('admin:customize_setting.function_options.tab_switch_desc2')}
+                  </p>
+                </CustomizeFunctionOption>
+              </div>
+            </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <CustomizeFunctionOption
-              optionId="isEnabledAttachTitleHeader"
-              label={t('admin:customize_setting.function_options.attach_title_header')}
-              isChecked={adminCustomizeContainer.state.isEnabledAttachTitleHeader}
-              onChecked={() => { adminCustomizeContainer.switchEnabledAttachTitleHeader() }}
-            >
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.attach_title_header_desc')}
-              </p>
-            </CustomizeFunctionOption>
-          </div>
-        </div>
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <CustomizeFunctionOption
+                  optionId="isEnabledAttachTitleHeader"
+                  label={t('admin:customize_setting.function_options.attach_title_header')}
+                  isChecked={adminCustomizeContainer.state.isEnabledAttachTitleHeader}
+                  onChecked={() => { adminCustomizeContainer.switchEnabledAttachTitleHeader() }}
+                >
+                  <p className="form-text text-muted">
+                    {t('admin:customize_setting.function_options.attach_title_header_desc')}
+                  </p>
+                </CustomizeFunctionOption>
+              </div>
+            </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <div className="my-0 btn-group">
-              <label>{t('admin:customize_setting.function_options.recent_created__n_draft_num_desc')}</label>
-              <div className="dropdown">
-                <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                  <span className="pull-left">{adminCustomizeContainer.state.currentRecentCreatedLimit}</span>
-                  <span className="bs-caret pull-right">
-                    <span className="caret" />
-                  </span>
-                </button>
-                {/* TODO adjust dropdown after BS4 */}
-                <ul className="dropdown-menu" role="menu">
-                  <li key={10} role="presentation" type="button" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(10) }}>
-                    <a role="menuitem">10</a>
-                  </li>
-                  <li key={30} role="presentation" type="button" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(30) }}>
-                    <a role="menuitem">30</a>
-                  </li>
-                  <li key={50} role="presentation" type="button" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(50) }}>
-                    <a role="menuitem">50</a>
-                  </li>
-                </ul>
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <div className="my-0 w-100">
+                  <label>{t('admin:customize_setting.function_options.recent_created__n_draft_num_desc')}</label>
+                </div>
+                <Dropdown isOpen={this.state.isDropdownOpen} toggle={this.onToggleDropdown}>
+                  <DropdownToggle className="text-right col-6" caret>
+                    <span className="float-left">{adminCustomizeContainer.state.currentRecentCreatedLimit}</span>
+                  </DropdownToggle>
+                  <DropdownMenu className="dropdown-menu" role="menu">
+                    <DropdownItem key={10} role="presentation" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(10) }}>
+                      <a role="menuitem">10</a>
+                    </DropdownItem>
+                    <DropdownItem key={30} role="presentation" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(30) }}>
+                      <a role="menuitem">30</a>
+                    </DropdownItem>
+                    <DropdownItem key={50} role="presentation" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(50) }}>
+                      <a role="menuitem">50</a>
+                    </DropdownItem>
+                  </DropdownMenu>
+                </Dropdown>
+                <p className="form-text text-muted">
+                  {t('admin:customize_setting.function_options.recently_created_n_draft_num_desc')}
+                </p>
               </div>
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.recently_created_n_draft_num_desc')}
-              </p>
             </div>
-          </div>
-        </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <CustomizeFunctionOption
-              optionId="isEnabledStaleNotification"
-              label={t('admin:customize_setting.function_options.stale_notification')}
-              isChecked={adminCustomizeContainer.state.isEnabledStaleNotification}
-              onChecked={() => { adminCustomizeContainer.switchEnableStaleNotification() }}
-            >
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.stale_notification_desc')}
-              </p>
-            </CustomizeFunctionOption>
-          </div>
-        </div>
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <CustomizeFunctionOption
+                  optionId="isEnabledStaleNotification"
+                  label={t('admin:customize_setting.function_options.stale_notification')}
+                  isChecked={adminCustomizeContainer.state.isEnabledStaleNotification}
+                  onChecked={() => { adminCustomizeContainer.switchEnableStaleNotification() }}
+                >
+                  <p className="form-text text-muted">
+                    {t('admin:customize_setting.function_options.stale_notification_desc')}
+                  </p>
+                </CustomizeFunctionOption>
+              </div>
+            </div>
+
+            <div className="form-group row">
+              <div className="col-xs-offset-3 col-xs-6 text-left">
+                <CustomizeFunctionOption
+                  optionId="isAllReplyShown"
+                  label={t('admin:customize_setting.function_options.show_all_reply_comments')}
+                  isChecked={adminCustomizeContainer.state.isAllReplyShown || false}
+                  onChecked={() => { adminCustomizeContainer.switchIsAllReplyShown() }}
+                >
+                  <p className="help-block">
+                    {t('admin:customize_setting.function_options.show_all_reply_comments_desc')}
+                  </p>
+                </CustomizeFunctionOption>
+              </div>
+            </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <CustomizeFunctionOption
-              optionId="isAllReplyShown"
-              label={t('admin:customize_setting.function_options.show_all_reply_comments')}
-              isChecked={adminCustomizeContainer.state.isAllReplyShown || false}
-              onChecked={() => { adminCustomizeContainer.switchIsAllReplyShown() }}
-            >
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.show_all_reply_comments_desc')}
-              </p>
-            </CustomizeFunctionOption>
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           </div>
         </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 33 - 29
src/client/js/components/Admin/Customize/CustomizeHeaderSetting.jsx

@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { Card, CardBody } from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -36,36 +37,39 @@ class CustomizeHeaderSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.custom_header')}</h2>
-
-        <p
-          className="well"
-          // eslint-disable-next-line react/no-danger
-          dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_header_detail') }}
-        />
-
-        <div className="help-block">
-          {t('Example')}:
-          <pre className="hljs">
-            {/* eslint-disable-next-line react/no-unescaped-entities */}
-            <code>&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js" defer&gt;&lt;/script&gt;</code>
-          </pre>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.custom_header')}</h2>
+
+            <Card className="card well my-3">
+              <CardBody className="px-0 py-2">
+                <span
+                  // eslint-disable-next-line react/no-danger
+                  dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_header_detail') }}
+                />
+              </CardBody>
+            </Card>
+            <div className="form-text text-muted">
+              { t('Example') }:
+              <pre className="hljs">
+                {/* eslint-disable-next-line react/no-unescaped-entities */}
+                <code>&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js" defer&gt;&lt;/script&gt;</code>
+              </pre>
+            </div>
+
+            <div className="form-group">
+              <CustomHeaderEditor
+                value={adminCustomizeContainer.state.currentCustomizeHeader || ''}
+                onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeHeader(inputValue) }}
+              />
+              <p className="form-text text-muted text-right">
+                <i className="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
+                {t('admin:customize_setting.ctrl_space')}
+              </p>
+            </div>
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+          </div>
         </div>
-
-        <div className="col-xs-12">
-          <CustomHeaderEditor
-            value={adminCustomizeContainer.state.currentCustomizeHeader || ''}
-            onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeHeader(inputValue) }}
-          />
-        </div>
-        <div className="col-xs-12">
-          <p className="help-block text-right">
-            <i className="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
-            {t('admin:customize_setting.ctrl_space')}
-          </p>
-        </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 63 - 44
src/client/js/components/Admin/Customize/CustomizeHighlightSetting.jsx

@@ -2,6 +2,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import {
+  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
+} from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -16,9 +19,18 @@ class CustomizeHighlightSetting extends React.Component {
   constructor(props) {
     super(props);
 
+    this.state = {
+      isDropdownOpen: false,
+    };
+
+    this.onToggleDropdown = this.onToggleDropdown.bind(this);
     this.onClickSubmit = this.onClickSubmit.bind(this);
   }
 
+  onToggleDropdown() {
+    this.setState({ isDropdownOpen: !this.state.isDropdownOpen });
+  }
+
   async onClickSubmit() {
     const { t, adminCustomizeContainer } = this.props;
 
@@ -64,62 +76,69 @@ class CustomizeHighlightSetting extends React.Component {
       const isBorderEnable = option[1].border;
 
       menuItem.push(
-        <li key={styleId} role="presentation" type="button" onClick={() => adminCustomizeContainer.switchHighlightJsStyle(styleId, styleName, isBorderEnable)}>
-          <a role="button">{styleName}</a>
-        </li>,
+        <DropdownItem
+          key={styleId}
+          role="presentation"
+          onClick={() => adminCustomizeContainer.switchHighlightJsStyle(styleId, styleName, isBorderEnable)}
+        >
+          <a role="menuitem">{styleName}</a>
+        </DropdownItem>,
       );
     });
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.code_highlight')}</h2>
-
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <div className="my-0 btn-group">
-              <label>{t('admin:customize_setting.theme')}</label>
-              <div className="dropdown">
-                <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                  <span className="pull-left">{adminCustomizeContainer.state.currentHighlightJsStyleName}</span>
-                  <span className="bs-caret pull-right">
-                    <span className="caret" />
-                  </span>
-                </button>
-                {/* TODO adjust dropdown after BS4 */}
-                <ul className="dropdown-menu" role="menu">
-                  {menuItem}
-                </ul>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.code_highlight')}</h2>
+
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <div className="my-0">
+                  <label>{t('admin:customize_setting.theme')}</label>
+                </div>
+                <Dropdown isOpen={this.state.isDropdownOpen} toggle={this.onToggleDropdown}>
+                  <DropdownToggle className="text-right col-6" caret>
+                    <span className="float-left">{adminCustomizeContainer.state.currentHighlightJsStyleName}</span>
+                  </DropdownToggle>
+                  <DropdownMenu className="dropdown-menu" role="menu">
+                    {menuItem}
+                  </DropdownMenu>
+                </Dropdown>
+                <p className="form-text text-warning">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.nocdn_desc') }} />
+                </p>
+              </div>
+            </div>
+
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <div className="custom-control custom-switch custom-checkbox-success">
+                  <input
+                    type="checkbox"
+                    className="custom-control-input"
+                    id="highlightBorder"
+                    checked={adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled}
+                    onChange={() => { adminCustomizeContainer.switchHighlightJsStyleBorder() }}
+                  />
+                  <label className="custom-control-label" htmlFor="highlightBorder">
+                    <strong>Border</strong>
+                  </label>
+                </div>
               </div>
-              {/* eslint-disable-next-line react/no-danger */}
-              <p className="help-block text-warning"><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.nocdn_desc') }} /></p>
             </div>
-          </div>
-        </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
-              <input
-                type="checkbox"
-                id="highlightBorder"
-                checked={adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled}
-                onChange={() => { adminCustomizeContainer.switchHighlightJsStyleBorder() }}
-              />
-              <label htmlFor="highlightBorder">
-                <strong>Border</strong>
-              </label>
+            <div className="form-text text-muted">
+              <label>Examples:</label>
+              <div className="wiki">
+                {this.renderHljsDemo()}
+              </div>
             </div>
-          </div>
-        </div>
 
-        <div className="help-block">
-          <label>Examples:</label>
-          <div className="wiki">
-            {this.renderHljsDemo()}
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           </div>
         </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 9 - 3
src/client/js/components/Admin/Customize/CustomizeLayoutOption.jsx

@@ -10,9 +10,15 @@ class CustomizeLayoutOption extends React.Component {
     return (
       <React.Fragment>
         <h4>
-          <div className="radio radio-primary">
-            <input type="radio" id={`radio-layout-${layoutType}`} checked={this.props.isSelected} onChange={this.props.onSelected} />
-            <label htmlFor={`radio-layout-${layoutType}`}>
+          <div className="custom-control custom-radio">
+            <input
+              type="radio"
+              className="custom-control-input"
+              id={`radio-layout-${layoutType}`}
+              checked={this.props.isSelected}
+              onChange={this.props.onSelected}
+            />
+            <label className="custom-control-label" htmlFor={`radio-layout-${layoutType}`}>
               {/* eslint-disable-next-line react/no-danger */}
               <span dangerouslySetInnerHTML={{ __html: this.props.labelHtml }} />
             </label>

+ 3 - 3
src/client/js/components/Admin/Customize/CustomizeLayoutOptions.jsx

@@ -15,7 +15,7 @@ class CustomizeLayoutOptions extends React.Component {
 
     return (
       <div className="row">
-        <div className="col-sm-4">
+        <div className="col-md-4">
           <CustomizeLayoutOption
             layoutType="crowi-plus"
             isSelected={adminCustomizeContainer.state.currentLayout === 'growi'}
@@ -31,7 +31,7 @@ class CustomizeLayoutOptions extends React.Component {
           </CustomizeLayoutOption>
         </div>
 
-        <div className="col-sm-4">
+        <div className="col-md-4">
           <CustomizeLayoutOption
             layoutType="kibela"
             isSelected={adminCustomizeContainer.state.currentLayout === 'kibela'}
@@ -47,7 +47,7 @@ class CustomizeLayoutOptions extends React.Component {
           </CustomizeLayoutOption>
         </div>
 
-        <div className="col-sm-4">
+        <div className="col-md-4">
           <CustomizeLayoutOption
             layoutType="classic"
             isSelected={adminCustomizeContainer.state.currentLayout === 'crowi'}

+ 14 - 6
src/client/js/components/Admin/Customize/CustomizeLayoutSetting.jsx

@@ -48,12 +48,20 @@ class CustomizeLayoutSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.layout')}</h2>
-        <CustomizeLayoutOptions />
-        <h2 className="admin-setting-header">{t('admin:customize_setting.theme')}</h2>
-        {this.renderDevAlert()}
-        <CustomizeThemeOptions />
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.layout')}</h2>
+            <CustomizeLayoutOptions />
+          </div>
+        </div>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.theme')}</h2>
+            {this.renderDevAlert()}
+            <CustomizeThemeOptions />
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+          </div>
+        </div>
       </React.Fragment>
     );
   }

+ 57 - 42
src/client/js/components/Admin/Customize/CustomizeScriptSetting.jsx

@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { Card, CardBody } from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -44,50 +45,64 @@ class CustomizeScriptSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.custom_script')}</h2>
-        <p className="well">
-          {t('admin:customize_setting.write_java')}<br />
-          {t('admin:customize_setting.reflect_change')}
-        </p>
-
-        <div className="help-block">
-          Placeholders:<br />
-          (Available after <code>load</code> event)
-          <dl className="dl-horizontal">
-            <dt><code>$</code></dt>
-            <dd>jQuery instance</dd>
-            <dt><code>appContainer</code></dt>
-            <dd>GROWI App <a href="https://github.com/jamiebuilds/unstated">Unstated Container</a></dd>
-            <dt><code>growiRenderer</code></dt>
-            <dd>GROWI Renderer origin instance</dd>
-            <dt><code>growiPlugin</code></dt>
-            <dd>GROWI Plugin Manager instance</dd>
-            <dt><code>Crowi</code></dt>
-            <dd>Crowi legacy instance (jQuery based)</dd>
-          </dl>
-        </div>
-
-        <div className="help-block">
-          Examples:
-          <pre className="hljs"><code>{this.getExampleCode()}</code></pre>
-        </div>
-
-        <div className="form-group">
-          <div className="col-xs-12">
-            <CustomScriptEditor
-              value={adminCustomizeContainer.state.currentCustomizeScript || ''}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeScript(inputValue) }}
-            />
-          </div>
-          <div className="col-xs-12">
-            <p className="help-block text-right">
-              <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
-              {t('admin:customize_setting.ctrl_space')}
-            </p>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.custom_script')}</h2>
+            <Card className="card well">
+              <CardBody className="px-0 py-2">
+                {t('admin:customize_setting.write_java')}<br />
+                {t('admin:customize_setting.reflect_change')}
+              </CardBody>
+            </Card>
+
+            <div className="form-text text-muted">
+              Placeholders:<br />
+              (Available after <code>load</code> event)
+            </div>
+            <table className="table table-borderless table-sm form-text text-muted offset-1">
+              <tbody>
+                <tr>
+                  <th className="text-right"><code>$</code></th>
+                  <td>jQuery instance</td>
+                </tr>
+                <tr>
+                  <th className="text-right"><code>appContainer</code></th>
+                  <td>GROWI App <a href="https://github.com/jamiebuilds/unstated">Unstated Container</a></td>
+                </tr>
+                <tr>
+                  <th className="text-right"><code>growiRenderer</code></th>
+                  <td>GROWI Renderer origin instance</td>
+                </tr>
+                <tr>
+                  <th className="text-right"><code>growiPlugin</code></th>
+                  <td>GROWI Plugin Manager instance</td>
+                </tr>
+                <tr>
+                  <th className="text-right"><code>Crowi</code></th>
+                  <td>Crowi legacy instance (jQuery based)</td>
+                </tr>
+              </tbody>
+            </table>
+
+            <div className="form-text text-muted">
+              Examples:
+              <pre className="hljs"><code>{this.getExampleCode()}</code></pre>
+            </div>
+
+            <div className="form-group">
+              <CustomScriptEditor
+                value={adminCustomizeContainer.state.currentCustomizeScript || ''}
+                onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeScript(inputValue) }}
+              />
+              <p className="form-text text-muted text-right">
+                <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
+                {t('admin:customize_setting.ctrl_space')}
+              </p>
+            </div>
+
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           </div>
         </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 0 - 2
src/client/js/components/Admin/Customize/CustomizeThemeOptions.jsx

@@ -32,8 +32,6 @@ class CustomizeThemeOptions extends React.Component {
     }];
 
     const darkTheme = [{
-      name: 'default-dark', bg: '#212731', topbar: '#151515', theme: '#f75b36',
-    }, {
       name: 'future', bg: '#16282D', topbar: '#011414', theme: '#04B4AE',
     }, {
       name: 'blue-night', bg: '#061F2F', topbar: '#27343B', theme: '#0090C8',

+ 34 - 20
src/client/js/components/Admin/Customize/CustomizeTitle.jsx

@@ -1,6 +1,8 @@
+/* eslint-disable max-len */
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { Card, CardBody } from 'reactstrap';
 
 import AppContainer from '../../../services/AppContainer';
 import AdminCustomizeContainer from '../../../services/AdminCustomizeContainer';
@@ -34,27 +36,39 @@ class CustomizeTitle extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.custom_title')}</h2>
-        <p
-          className="well"
-          // eslint-disable-next-line react/no-danger
-          dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_title_detail') }}
-        />
-        {/* TODO i18n */}
-        <div className="help-block">
-          Default Value: <code>&#123;&#123;page&#125;&#125; - &#123;&#123;sitename&#125;&#125;</code>
-          <br />
-          Default Output: <pre><code className="xml">&lt;title&gt;/Sandbox - {'GROWI'}&lt;&#047;title&gt;</code></pre>
-        </div>
-        <div className="form-group">
-          <input
-            className="form-control"
-            defaultValue={currentCustomizeTitle}
-            onChange={(e) => { adminCustomizeContainer.changeCustomizeTitle(e.target.value) }}
-          />
-        </div>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.custom_title')}</h2>
+          </div>
 
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+          <div className="col-12">
+            <Card className="card well">
+              <CardBody className="px-0 py-2">
+                <span
+                  // eslint-disable-next-line react/no-danger
+                  dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_title_detail') }}
+                />
+              </CardBody>
+            </Card>
+          </div>
+
+          {/* TODO i18n */}
+          <div className="form-text text-muted col-12">
+            Default Value: <code>&#123;&#123;page&#125;&#125; - &#123;&#123;sitename&#125;&#125;</code>
+            <br />
+            Default Output: <pre><code className="xml">&lt;title&gt;/Sandbox - {'GROWI'}&lt;&#047;title&gt;</code></pre>
+          </div>
+          <div className="form-group col-12">
+            <input
+              className="form-control"
+              defaultValue={currentCustomizeTitle}
+              onChange={(e) => { adminCustomizeContainer.changeCustomizeTitle(e.target.value) }}
+            />
+          </div>
+          <div className="col-12">
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+          </div>
+        </div>
       </React.Fragment>
     );
   }

+ 7 - 7
src/client/js/components/Admin/ElasticsearchManagement/ElasticsearchManagement.jsx

@@ -147,7 +147,7 @@ class ElasticsearchManagement extends React.Component {
     return (
       <>
         <div className="row">
-          <div className="col-xs-12">
+          <div className="col-md-12">
             <StatusTable
               isConfigured={isConfigured}
               isConnected={isConnected}
@@ -162,8 +162,8 @@ class ElasticsearchManagement extends React.Component {
 
         {/* Controls */}
         <div className="row">
-          <label className="col-xs-3 control-label">{ t('full_text_search_management.reconnect') }</label>
-          <div className="col-xs-6">
+          <label className="col-md-3 col-form-label">{ t('full_text_search_management.reconnect') }</label>
+          <div className="col-md-6">
             <ReconnectControls
               isConfigured={isConfigured}
               isConnected={isConnected}
@@ -175,8 +175,8 @@ class ElasticsearchManagement extends React.Component {
         <hr />
 
         <div className="row">
-          <label className="col-xs-3 control-label">{ t('full_text_search_management.normalize') }</label>
-          <div className="col-xs-6">
+          <label className="col-md-3 col-form-label">{ t('full_text_search_management.normalize') }</label>
+          <div className="col-md-6">
             <NormalizeIndicesControls
               isRebuildingProcessing={isRebuildingProcessing}
               isRebuildingCompleted={isRebuildingCompleted}
@@ -189,8 +189,8 @@ class ElasticsearchManagement extends React.Component {
         <hr />
 
         <div className="row">
-          <label className="col-xs-3 control-label">{ t('full_text_search_management.rebuild') }</label>
-          <div className="col-xs-6">
+          <label className="col-md-3 col-form-label">{ t('full_text_search_management.rebuild') }</label>
+          <div className="col-md-6">
             <RebuildIndexControls
               isRebuildingProcessing={isRebuildingProcessing}
               isRebuildingCompleted={isRebuildingCompleted}

+ 1 - 1
src/client/js/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.jsx

@@ -15,7 +15,7 @@ class NormalizeIndicesControls extends React.PureComponent {
       <>
         <button
           type="submit"
-          className={`btn btn-outline ${isEnabled ? 'btn-info' : 'btn-default'}`}
+          className={`btn ${isEnabled ? 'btn-outline-info' : 'btn-outline-secondary'}`}
           onClick={() => { this.props.onNormalizingRequested() }}
           disabled={!isEnabled}
         >

+ 2 - 2
src/client/js/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx

@@ -76,14 +76,14 @@ class RebuildIndexControls extends React.Component {
 
         <button
           type="submit"
-          className="btn btn-inverse"
+          className="btn btn-primary"
           onClick={() => { this.props.onRebuildingRequested() }}
           disabled={!isEnabled}
         >
           { t('full_text_search_management.rebuild_button') }
         </button>
 
-        <p className="help-block">
+        <p className="form-text text-muted">
           { t('full_text_search_management.rebuild_description_1') }<br />
           { t('full_text_search_management.rebuild_description_2') }<br />
         </p>

+ 1 - 1
src/client/js/components/Admin/ElasticsearchManagement/ReconnectControls.jsx

@@ -15,7 +15,7 @@ class ReconnectControls extends React.PureComponent {
       <>
         <button
           type="submit"
-          className={`btn btn-outline ${isEnabled ? 'btn-success' : 'btn-default'}`}
+          className={`btn ${isEnabled ? 'btn-outline-success' : 'btn-outline-secondary'}`}
           onClick={() => { this.props.onReconnectingRequested() }}
           disabled={!isEnabled}
         >

+ 23 - 27
src/client/js/components/Admin/ElasticsearchManagement/StatusTable.jsx

@@ -11,24 +11,22 @@ class StatusTable extends React.PureComponent {
 
     const aliasLabels = aliases.map((aliasName) => {
       return (
-        <span key={`label-${indexName}-${aliasName}`} className="label label-primary mr-2">
+        <span key={`badge-${indexName}-${aliasName}`} className="badge badge-pill badge-primary mr-2">
           <i className="icon-tag"></i> {aliasName}
         </span>
       );
     });
 
     return (
-      <div className="panel panel-default">
-        <div className="panel-heading" role="tab">
-          <h4 className="panel-title">
-            <a role="button" data-toggle="collapse" data-parent="#accordion" href={`#${collapseId}`} aria-expanded="true" aria-controls={collapseId}>
-              <i className="fa fa-fw fa-database"></i> {indexName}
-            </a>
-            <span className="ml-3">{aliasLabels}</span>
-          </h4>
+      <div className="card">
+        <div className="card-header">
+          <a role="button" data-toggle="collapse" href={`#${collapseId}`} aria-expanded="true" aria-controls={collapseId}>
+            <i className="fa fa-fw fa-database"></i> {indexName}
+          </a>
+          <span className="ml-3">{aliasLabels}</span>
         </div>
-        <div id={collapseId} className="panel-collapse collapse" role="tabpanel">
-          <div className="panel-body">
+        <div id={collapseId} className="collapse">
+          <div className="card-body">
             <pre>
               {JSON.stringify(body, null, 2)}
             </pre>
@@ -84,7 +82,7 @@ class StatusTable extends React.PureComponent {
       <div className="row">
         { Object.keys(indexNameToDataMap).map((indexName) => {
           return (
-            <div key={`col-${indexName}`} className="col-xs-6">
+            <div key={`col-${indexName}`} className="col-6">
               { this.renderIndexInfoPanel(indexName, indexNameToDataMap[indexName], indexNameToAliasMap[indexName]) }
             </div>
           );
@@ -98,39 +96,37 @@ class StatusTable extends React.PureComponent {
     const { isConfigured, isConnected, isNormalized } = this.props;
 
 
-    let connectionStatusLabel = <span className="label label-default">――</span>;
+    let connectionStatusLabel = <span className="badge badge-pill badge-secondary">――</span>;
     if (isConfigured != null && !isConfigured) {
-      connectionStatusLabel = <span className="label label-default">{ t('full_text_search_management.connection_status_label_unconfigured') }</span>;
+      connectionStatusLabel = <span className="badge badge-pill badge-secondary">{t('full_text_search_management.connection_status_label_unconfigured')}</span>;
     }
     else if (isConnected != null) {
       connectionStatusLabel = isConnected
-        ? <span className="label label-success">{ t('full_text_search_management.connection_status_label_connected') }</span>
-        : <span className="label label-danger">{ t('full_text_search_management.connection_status_label_disconnected') }</span>;
+        ? <span className="badge badge-pill badge-success">{ t('full_text_search_management.connection_status_label_connected') }</span>
+        : <span className="badge badge-pill badge-danger">{ t('full_text_search_management.connection_status_label_disconnected') }</span>;
     }
 
-    let indicesStatusLabel = <span className="label label-default">――</span>;
+    let indicesStatusLabel = <span className="badge badge-pill badge-secondary">――</span>;
     if (isNormalized != null) {
       indicesStatusLabel = isNormalized
-        ? <span className="label label-info">{ t('full_text_search_management.indices_status_label_normalized') }</span>
-        : <span className="label label-warning">{ t('full_text_search_management.indices_status_label_unnormalized') }</span>;
+        ? <span className="badge badge-pill badge-info">{ t('full_text_search_management.indices_status_label_normalized') }</span>
+        : <span className="badge badge-pill badge-warning">{ t('full_text_search_management.indices_status_label_unnormalized') }</span>;
     }
 
     return (
       <table className="table table-bordered">
         <tbody>
           <tr>
-            <th>{ t('full_text_search_management.connection_status') }</th>
-            <td>{connectionStatusLabel}</td>
+            <th className="w-25">{t('full_text_search_management.connection_status')}</th>
+            <td className="w-75">{connectionStatusLabel}</td>
           </tr>
           <tr>
-            <th>{ t('full_text_search_management.indices_status') }</th>
-            <td>{indicesStatusLabel}</td>
+            <th className="w-25">{t('full_text_search_management.indices_status')}</th>
+            <td className="w-75">{indicesStatusLabel}</td>
           </tr>
           <tr>
-            <th className="col-sm-4">{ t('full_text_search_management.indices_summary') }</th>
-            <td className="p-4">
-              { this.renderIndexInfoPanels() }
-            </td>
+            <th className="w-25">{t('full_text_search_management.indices_summary')}</th>
+            <td className="p-4 w-75">{this.renderIndexInfoPanels()}</td>
           </tr>
         </tbody>
       </table>

+ 1 - 1
src/client/js/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.jsx

@@ -13,7 +13,7 @@ class ArchiveFilesTableMenu extends React.Component {
 
     return (
       <div className="btn-group admin-user-menu">
-        <button type="button" className="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown">
+        <button type="button" className="btn btn-sm btn-light dropdown-toggle" data-toggle="dropdown">
           <i className="icon-settings"></i> <span className="caret"></span>
         </button>
         <ul className="dropdown-menu" role="menu">

+ 46 - 42
src/client/js/components/Admin/ExportArchiveData/SelectCollectionsModal.jsx

@@ -1,7 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
-import Modal from 'react-bootstrap/es/Modal';
+import {
+  Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
 import * as toastr from 'toastr';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
@@ -120,7 +122,7 @@ class SelectCollectionsModal extends React.Component {
     const html = this.props.t('admin:export_management.desc_password_seed');
 
     // eslint-disable-next-line react/no-danger
-    return <div className="well well-sm" dangerouslySetInnerHTML={{ __html: html }}></div>;
+    return <div className="card well" dangerouslySetInnerHTML={{ __html: html }}></div>;
   }
 
   renderGroups(groupList, color) {
@@ -140,28 +142,30 @@ class SelectCollectionsModal extends React.Component {
   }
 
   renderCheckboxes(collectionNames, color) {
-    const checkboxColor = color ? `checkbox-${color}` : 'checkbox-info';
+    const checkboxColor = color ? `custom-checkbox-${color}` : 'custom-checkbox-info';
 
     return (
-      <div className={`row checkbox ${checkboxColor}`}>
-        {collectionNames.map((collectionName) => {
-          return (
-            <div className="col-xs-6 my-1" key={collectionName}>
-              <input
-                type="checkbox"
-                id={collectionName}
-                name={collectionName}
-                className="form-check-input"
-                value={collectionName}
-                checked={this.state.selectedCollections.has(collectionName)}
-                onChange={this.toggleCheckbox}
-              />
-              <label className="text-capitalize form-check-label ml-3" htmlFor={collectionName}>
-                {collectionName}
-              </label>
-            </div>
-          );
-        })}
+      <div className={`custom-control custom-checkbox ${checkboxColor}`}>
+        <div className="row">
+          {collectionNames.map((collectionName) => {
+            return (
+              <div className="col-sm-6 my-1" key={collectionName}>
+                <input
+                  type="checkbox"
+                  className="custom-control-input"
+                  id={collectionName}
+                  name={collectionName}
+                  value={collectionName}
+                  checked={this.state.selectedCollections.has(collectionName)}
+                  onChange={this.toggleCheckbox}
+                />
+                <label className="text-capitalize custom-control-label ml-3" htmlFor={collectionName}>
+                  {collectionName}
+                </label>
+              </div>
+            );
+          })}
+        </div>
       </div>
     );
   }
@@ -170,54 +174,54 @@ class SelectCollectionsModal extends React.Component {
     const { t } = this.props;
 
     return (
-      <Modal show={this.props.isOpen} onHide={this.props.onClose}>
-        <Modal.Header closeButton>
-          <Modal.Title>{t('admin:export_management.export_collections')}</Modal.Title>
-        </Modal.Header>
+      <Modal isOpen={this.props.isOpen} toggle={this.props.onClose}>
+        <ModalHeader tag="h4" toggle={this.props.onClose}>
+          {t('admin:export_management.export_collections')}
+        </ModalHeader>
 
         <form onSubmit={this.export}>
-          <Modal.Body>
+          <ModalBody>
             <div className="row">
               <div className="col-sm-12">
-                <button type="button" className="btn btn-sm btn-default mr-2" onClick={this.checkAll}>
+                <button type="button" className="btn btn-sm btn-light mr-2" onClick={this.checkAll}>
                   <i className="fa fa-check-square-o"></i> {t('admin:export_management.check_all')}
                 </button>
-                <button type="button" className="btn btn-sm btn-default mr-2" onClick={this.uncheckAll}>
+                <button type="button" className="btn btn-sm btn-light mr-2" onClick={this.uncheckAll}>
                   <i className="fa fa-square-o"></i> {t('admin:export_management.uncheck_all')}
                 </button>
               </div>
             </div>
             <div className="row mt-4">
-              <div className="col-xs-12">
-                <legend>Page Collections</legend>
+              <div className="col-sm-12">
+                <h3 className="admin-setting-header">Page Collections</h3>
                 {this.renderGroups(GROUPS_PAGE)}
               </div>
             </div>
             <div className="row mt-4">
-              <div className="col-xs-12">
-                <legend>User Collections</legend>
+              <div className="col-sm-12">
+                <h3 className="admin-setting-header">User Collections</h3>
                 {this.renderGroups(GROUPS_USER, 'danger')}
                 {this.renderWarnForUser()}
               </div>
             </div>
             <div className="row mt-4">
-              <div className="col-xs-12">
-                <legend>Config Collections</legend>
+              <div className="col-sm-12">
+                <h3 className="admin-setting-header">Config Collections</h3>
                 {this.renderGroups(GROUPS_CONFIG)}
               </div>
             </div>
             <div className="row mt-4">
-              <div className="col-xs-12">
-                <legend>Other Collections</legend>
+              <div className="col-sm-12">
+                <h3 className="admin-setting-header">Other Collections</h3>
                 {this.renderOthers()}
               </div>
             </div>
-          </Modal.Body>
+          </ModalBody>
 
-          <Modal.Footer>
-            <button type="button" className="btn btn-sm btn-default" onClick={this.props.onClose}>{t('admin:export_management.cancel')}</button>
-            <button type="submit" className="btn btn-sm btn-primary" disabled={!this.validateForm()}>{t('admin:export_management.export')}</button>
-          </Modal.Footer>
+          <ModalFooter>
+            <button type="button" className="btn btn-sm btn-light" onClick={this.props.onClose}>{t('export_management.cancel')}</button>
+            <button type="submit" className="btn btn-sm btn-primary" disabled={!this.validateForm()}>{t('export_management.export')}</button>
+          </ModalFooter>
         </form>
       </Modal>
     );

+ 1 - 1
src/client/js/components/Admin/ExportArchiveDataPage.jsx

@@ -213,7 +213,7 @@ class ExportArchiveDataPage extends React.Component {
       <Fragment>
         <h2>{t('Export Archive Data')}</h2>
 
-        <button type="button" className="btn btn-default" disabled={isExporting} onClick={this.openExportModal}>
+        <button type="button" className="btn btn-light" disabled={isExporting} onClick={this.openExportModal}>
           {t('admin:export_management.create_new_archive_data')}
         </button>
 

+ 7 - 7
src/client/js/components/Admin/ImportData/GrowiArchive/ErrorViewer.jsx

@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
-import Modal from 'react-bootstrap/es/Modal';
+import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 
 import { createSubscribedElement } from '../../../UnstatedUtils';
 
@@ -20,13 +20,13 @@ class ErrorViewer extends React.Component {
     }
 
     return (
-      <Modal show={this.props.isOpen} onHide={this.props.onClose}>
-        <Modal.Header closeButton className="bg-danger">
-          <Modal.Title className="text-white">Errors</Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
+      <Modal isOpen={this.props.isOpen} toggle={this.props.onClose}>
+        <ModalHeader tag="h4" toggle={this.props.onClose} className="bg-danger text-white">
+          Errors
+        </ModalHeader>
+        <ModalBody>
           <textarea className="form-control" rows="8" readOnly wrap="off" defaultValue={value}></textarea>
-        </Modal.Body>
+        </ModalBody>
       </Modal>
     );
   }

+ 46 - 31
src/client/js/components/Admin/ImportData/GrowiArchive/ImportCollectionConfigurationModal.jsx

@@ -3,7 +3,12 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
-import Modal from 'react-bootstrap/es/Modal';
+import {
+  Modal,
+  ModalHeader,
+  ModalBody,
+  ModalFooter,
+} from 'reactstrap';
 
 import GrowiArchiveImportOption from '@commons/models/admin/growi-archive-import-option';
 
@@ -62,82 +67,91 @@ class ImportCollectionConfigurationModal extends React.Component {
     /* eslint-disable react/no-unescaped-entities */
     return (
       <>
-        <div className="checkbox checkbox-warning">
+        <div className="custom-control custom-checkbox custom-checkbox-warning">
           <input
             id="cbOpt4"
             type="checkbox"
+            className="custom-control-input"
             checked={option.isOverwriteAuthorWithCurrentUser || false} // add ' || false' to avoid uncontrolled input warning
             onChange={() => this.changeHandler({ isOverwriteAuthorWithCurrentUser: !option.isOverwriteAuthorWithCurrentUser })}
           />
-          <label htmlFor="cbOpt4">
+          <label htmlFor="cbOpt4" className="custom-control-label">
             {t(`${translationBase}.overwrite_author.label`)}
-            <p className="help-block mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.overwrite_author.desc`) }} />
+            <p className="form-text text-muted mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.overwrite_author.desc`) }} />
           </label>
         </div>
-        <div className="checkbox checkbox-warning">
+        <div className="custom-control custom-checkbox custom-checkbox-warning">
           <input
             id="cbOpt1"
             type="checkbox"
+            className="custom-control-input"
             checked={option.makePublicForGrant2 || false} // add ' || false' to avoid uncontrolled input warning
             onChange={() => this.changeHandler({ makePublicForGrant2: !option.makePublicForGrant2 })}
           />
-          <label htmlFor="cbOpt1">
+          <label htmlFor="cbOpt1" className="custom-control-label">
             {t(`${translationBase}.set_public_to_page.label`, { from: t('Anyone with the link') })}
             <p
-              className="help-block mt-0"
+              className="form-text text-muted mt-0"
               dangerouslySetInnerHTML={{ __html: t(`${translationBase}.set_public_to_page.desc`, { from: t('Anyone with the link') }) }}
             />
           </label>
         </div>
-        <div className="checkbox checkbox-warning">
+        <div className="custom-control custom-checkbox custom-checkbox-warning">
           <input
             id="cbOpt2"
             type="checkbox"
+            className="custom-control-input"
             checked={option.makePublicForGrant4 || false} // add ' || false' to avoid uncontrolled input warning
             onChange={() => this.changeHandler({ makePublicForGrant4: !option.makePublicForGrant4 })}
           />
-          <label htmlFor="cbOpt2">
+          <label htmlFor="cbOpt2" className="custom-control-label">
             {t(`${translationBase}.set_public_to_page.label`, { from: t('Just me') })}
-            <p className="help-block mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.set_public_to_page.desc`, { from: t('Just me') }) }} />
+            <p
+              className="form-text text-muted mt-0"
+              dangerouslySetInnerHTML={{ __html: t(`${translationBase}.set_public_to_page.desc`, { from: t('Just me') }) }}
+            />
           </label>
         </div>
-        <div className="checkbox checkbox-warning">
+        <div className="custom-control custom-checkbox custom-checkbox-warning">
           <input
             id="cbOpt3"
             type="checkbox"
+            className="custom-control-input"
             checked={option.makePublicForGrant5 || false} // add ' || false' to avoid uncontrolled input warning
             onChange={() => this.changeHandler({ makePublicForGrant5: !option.makePublicForGrant5 })}
           />
-          <label htmlFor="cbOpt3">
+          <label htmlFor="cbOpt3" className="custom-control-label">
             {t(`${translationBase}.set_public_to_page.label`, { from: t('Only inside the group') })}
             <p
-              className="help-block mt-0"
+              className="form-text text-muted mt-0"
               dangerouslySetInnerHTML={{ __html: t(`${translationBase}.set_public_to_page.desc`, { from: t('Only inside the group') }) }}
             />
           </label>
         </div>
-        <div className="checkbox checkbox-default">
+        <div className="custom-control custom-checkbox custom-checkbox-warning">
           <input
             id="cbOpt5"
             type="checkbox"
+            className="custom-control-input"
             checked={option.initPageMetadatas || false} // add ' || false' to avoid uncontrolled input warning
             onChange={() => this.changeHandler({ initPageMetadatas: !option.initPageMetadatas })}
           />
-          <label htmlFor="cbOpt5">
+          <label htmlFor="cbOpt5" className="custom-control-label">
             {t(`${translationBase}.initialize_meta_datas.label`)}
-            <p className="help-block mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.initialize_meta_datas.desc`) }} />
+            <p className="form-text text-muted mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.initialize_meta_datas.desc`) }} />
           </label>
         </div>
-        <div className="checkbox checkbox-default">
+        <div className="custom-control custom-checkbox custom-checkbox-warning">
           <input
             id="cbOpt6"
             type="checkbox"
+            className="custom-control-input"
             checked={option.initHackmdDatas || false} // add ' || false' to avoid uncontrolled input warning
             onChange={() => this.changeHandler({ initHackmdDatas: !option.initHackmdDatas })}
           />
-          <label htmlFor="cbOpt6">
+          <label htmlFor="cbOpt6" className="custom-control-label">
             {t(`${translationBase}.initialize_hackmd_related_datas.label`)}
-            <p className="help-block mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.initialize_hackmd_related_datas.desc`) }} />
+            <p className="form-text text-muted mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.initialize_hackmd_related_datas.desc`) }} />
           </label>
         </div>
       </>
@@ -154,16 +168,17 @@ class ImportCollectionConfigurationModal extends React.Component {
     /* eslint-disable react/no-unescaped-entities */
     return (
       <>
-        <div className="checkbox checkbox-warning">
+        <div className="custom-control custom-checkbox custom-checkbox-warning">
           <input
             id="cbOpt1"
             type="checkbox"
+            className="custom-control-input"
             checked={option.isOverwriteAuthorWithCurrentUser || false} // add ' || false' to avoid uncontrolled input warning
             onChange={() => this.changeHandler({ isOverwriteAuthorWithCurrentUser: !option.isOverwriteAuthorWithCurrentUser })}
           />
-          <label htmlFor="cbOpt1">
+          <label htmlFor="cbOpt1" className="custom-control-label">
             {t(`${translationBase}.overwrite_author.label`)}
-            <p className="help-block mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.overwrite_author.desc`) }} />
+            <p className="form-text text-muted mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.overwrite_author.desc`) }} />
           </label>
         </div>
       </>
@@ -188,19 +203,19 @@ class ImportCollectionConfigurationModal extends React.Component {
     }
 
     return (
-      <Modal show={this.props.isOpen} onHide={this.props.onClose} onEnter={this.initialize}>
-        <Modal.Header closeButton>
-          <Modal.Title>{`'${collectionName}'`} Configuration</Modal.Title>
-        </Modal.Header>
+      <Modal isOpen={this.props.isOpen} toggle={this.props.onClose} onEnter={this.initialize}>
+        <ModalHeader tag="h4" toggle={this.props.onClose}>
+          {`'${collectionName}'`} Configuration
+        </ModalHeader>
 
-        <Modal.Body>
+        <ModalBody>
           {contents}
-        </Modal.Body>
+        </ModalBody>
 
-        <Modal.Footer>
-          <button type="button" className="btn btn-sm btn-default" onClick={this.props.onClose}>{t('Cancel')}</button>
+        <ModalFooter>
+          <button type="button" className="btn btn-sm btn-light" onClick={this.props.onClose}>{t('Cancel')}</button>
           <button type="button" className="btn btn-sm btn-primary" onClick={this.updateOption}>{t('Update')}</button>
-        </Modal.Footer>
+        </ModalFooter>
       </Modal>
     );
   }

+ 17 - 19
src/client/js/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 // eslint-disable-next-line no-unused-vars
 import { withTranslation } from 'react-i18next';
 
-import ProgressBar from 'react-bootstrap/es/ProgressBar';
+import { Progress } from 'reactstrap';
 
 import GrowiArchiveImportOption from '@commons/models/admin/growi-archive-import-option';
 
@@ -74,7 +74,7 @@ export default class ImportCollectionItem extends React.Component {
   renderModeLabel(mode, isColorized = false) {
     const attrMap = MODE_ATTR_MAP[mode];
     const className = isColorized ? `text-${attrMap.color}` : '';
-    return <span className={className}><i className={attrMap.icon}></i> {attrMap.label}</span>;
+    return <span className={`text-nowrap ${className}`}><i className={attrMap.icon}></i> {attrMap.label}</span>;
   }
 
   renderCheckbox() {
@@ -83,18 +83,18 @@ export default class ImportCollectionItem extends React.Component {
     } = this.props;
 
     return (
-      <div className="checkbox checkbox-info my-0">
+      <div className="custom-control custom-checkbox custom-checkbox-info my-0">
         <input
           type="checkbox"
           id={collectionName}
           name={collectionName}
-          className="form-check-input"
+          className="custom-control-input"
           value={collectionName}
           checked={isSelected}
           disabled={isImporting}
           onChange={this.changeHandler}
         />
-        <label className="text-capitalize form-check-label" htmlFor={collectionName}>
+        <label className="text-capitalize custom-control-label" htmlFor={collectionName}>
           {collectionName}
         </label>
       </div>
@@ -116,7 +116,7 @@ export default class ImportCollectionItem extends React.Component {
         Mode:&nbsp;
         <div className="dropdown d-inline-block">
           <button
-            className={`btn ${btnColor} btn-xs dropdown-toggle`}
+            className={`btn ${btnColor} btn-sm dropdown-toggle`}
             type="button"
             id="ddmMode"
             disabled={isImporting}
@@ -149,7 +149,7 @@ export default class ImportCollectionItem extends React.Component {
     return (
       <button
         type="button"
-        className="btn btn-default btn-xs ml-2"
+        className="btn btn-light btn-sm p-1 ml-2"
         disabled={isImporting || !isConfigButtonAvailable}
         onClick={isConfigButtonAvailable ? this.configButtonClickedHandler : null}
       >
@@ -166,11 +166,11 @@ export default class ImportCollectionItem extends React.Component {
     const total = insertedCount + modifiedCount + errorsCount;
 
     return (
-      <ProgressBar className="mb-0">
-        <ProgressBar max={total} striped={isImporting} active={isImporting} now={insertedCount} bsStyle="info" />
-        <ProgressBar max={total} striped={isImporting} active={isImporting} now={modifiedCount} bsStyle="success" />
-        <ProgressBar max={total} striped={isImporting} active={isImporting} now={errorsCount} bsStyle="danger" />
-      </ProgressBar>
+      <Progress multi className="mb-0">
+        <Progress bar max={total} color="info" striped={isImporting} animated={isImporting} value={insertedCount} />
+        <Progress bar max={total} color="success" striped={isImporting} animated={isImporting} value={modifiedCount} />
+        <Progress bar max={total} color="danger" striped={isImporting} animated={isImporting} value={errorsCount} />
+      </Progress>
     );
   }
 
@@ -201,8 +201,8 @@ export default class ImportCollectionItem extends React.Component {
     } = this.props;
 
     return (
-      <div className="panel panel-default">
-        <div className="panel-heading">
+      <div className="card border-light">
+        <div className="card-header bg-light">
           <div className="d-flex justify-content-between align-items-center">
             {/* left */}
             {this.renderCheckbox()}
@@ -213,14 +213,12 @@ export default class ImportCollectionItem extends React.Component {
             </span>
           </div>
         </div>
-        { isSelected && (
+        {isSelected && (
           <>
             {this.renderProgressBar()}
-            <div className="panel-body">
-              {this.renderBody()}
-            </div>
+            <div className="card-body">{this.renderBody()}</div>
           </>
-        ) }
+        )}
       </div>
     );
   }

+ 17 - 15
src/client/js/components/Admin/ImportData/GrowiArchive/ImportForm.jsx

@@ -343,17 +343,19 @@ class ImportForm extends React.Component {
     }
 
     return (
-      <div className="mt-4">
-        <legend>{groupName} Collections</legend>
-        {wellContent != null && (
-          <div className="well well-sm small">
-            <ul>
-              <li>{wellContent}</li>
-            </ul>
-          </div>
-        )}
-        {this.renderImportItems(collectionNames)}
-        {this.renderWarnForGroups(errors, `warnFor${groupName}`)}
+      <div className="mt-4 row">
+        <div className="col-12">
+          <h3 className="admin-setting-header">{groupName} Collections</h3>
+          { wellContent != null && (
+            <div className="card well small" role="alert">
+              <ul>
+                <li>{wellContent}</li>
+              </ul>
+            </div>
+          )}
+          {this.renderImportItems(collectionNames)}
+          {this.renderWarnForGroups(errors, `warnFor${groupName}`)}
+        </div>
       </div>
     );
   }
@@ -385,7 +387,7 @@ class ImportForm extends React.Component {
           const isConfigButtonAvailable = Object.keys(IMPORT_OPTION_CLASS_MAPPING).includes(collectionName);
 
           return (
-            <div className="col-xs-6 my-1" key={collectionName}>
+            <div className="col-6 my-1" key={collectionName}>
               <ImportCollectionItem
                 isImporting={isImporting}
                 isImported={collectionProgress ? isImported : false}
@@ -453,12 +455,12 @@ class ImportForm extends React.Component {
       <>
         <form className="form-inline">
           <div className="form-group">
-            <button type="button" className="btn btn-sm btn-default mr-2" onClick={this.checkAll}>
+            <button type="button" className="btn btn-sm btn-light mr-2" onClick={this.checkAll}>
               <i className="fa fa-check-square-o"></i> {t('admin:export_management.check_all')}
             </button>
           </div>
           <div className="form-group">
-            <button type="button" className="btn btn-sm btn-default mr-2" onClick={this.uncheckAll}>
+            <button type="button" className="btn btn-sm btn-light mr-2" onClick={this.uncheckAll}>
               <i className="fa fa-square-o"></i> {t('admin:export_management.uncheck_all')}
             </button>
           </div>
@@ -470,7 +472,7 @@ class ImportForm extends React.Component {
         {this.renderOthers()}
 
         <div className="mt-4 text-center">
-          <button type="button" className="btn btn-default mx-1" onClick={this.props.onDiscard}>
+          <button type="button" className="btn btn-light mx-1" onClick={this.props.onDiscard}>
             {t('admin:importer_management.growi_settings.discard')}
           </button>
           <button type="button" className="btn btn-primary mx-1" onClick={this.import} disabled={!canImport || isImporting}>

+ 6 - 6
src/client/js/components/Admin/ImportData/GrowiArchive/UploadForm.jsx

@@ -48,11 +48,11 @@ class UploadForm extends React.Component {
     const { t } = this.props;
 
     return (
-      <form className="form-horizontal" onSubmit={this.uploadZipFile}>
+      <form onSubmit={this.uploadZipFile}>
         <fieldset>
-          <div className="form-group">
-            <label htmlFor="file" className="col-xs-3 control-label">{t('admin:importer_management.growi_settings.growi_archive_file')}</label>
-            <div className="col-xs-6">
+          <div className="form-group row">
+            <label htmlFor="file" className="col-3 control-label">{t('admin:importer_management.growi_settings.growi_archive_file')}</label>
+            <div className="col-6">
               <input
                 type="file"
                 name="file"
@@ -63,8 +63,8 @@ class UploadForm extends React.Component {
               />
             </div>
           </div>
-          <div className="form-group">
-            <div className="col-xs-offset-3 col-xs-6">
+          <div className="form-group row">
+            <div className="offset-3 col-6">
               <button type="submit" className="btn btn-primary" disabled={!this.validateForm()}>
                 {t('admin:importer_management.growi_settings.upload')}
               </button>

+ 32 - 32
src/client/js/components/Admin/ImportDataPage.jsx

@@ -138,12 +138,12 @@ class ImportDataPage extends React.Component {
         <GrowiArchiveSection />
 
         <form
-          className="form-horizontal mt-5"
+          className="mt-5"
           id="importerSettingFormEsa"
           role="form"
         >
           <fieldset>
-            <legend>{t('admin:importer_management.import_from', { from: 'esa.io' })}</legend>
+            <h2 className="admin-setting-header">{t('admin:importer_management.import_from', { from: 'esa.io' })}</h2>
             <table className="table table-bordered table-mapping">
               <thead>
                 <tr>
@@ -171,37 +171,37 @@ class ImportDataPage extends React.Component {
               </tbody>
             </table>
 
-            <div className="well well-sm mb-0 small">
+            <div className="card well mb-0 small">
               <ul>
                 <li>{t('admin:importer_management.page_skip')}</li>
               </ul>
             </div>
 
-            <div className="form-group">
+            <div className="form-group row">
               <input type="password" name="dummypass" style={{ display: 'none', top: '-100px', left: '-100px' }} />
             </div>
 
-            <div className="form-group">
-              <label htmlFor="settingForm[importer:esa:team_name]" className="col-xs-3 control-label">
-                {t('admin:importer_management.esa_settings.team_name')}
+            <div className="form-group row">
+              <label htmlFor="settingForm[importer:esa:team_name]" className="col-3 col-form-label">
+                { t('admin:importer_management.esa_settings.team_name') }
               </label>
-              <div className="col-xs-6">
+              <div className="col-6">
                 <input className="form-control" type="text" name="esaTeamName" value={esaTeamName} onChange={this.handleInputValue} />
               </div>
 
             </div>
 
-            <div className="form-group">
-              <label htmlFor="settingForm[importer:esa:access_token]" className="col-xs-3 control-label">
-                {t('admin:importer_management.esa_settings.access_token')}
+            <div className="form-group row">
+              <label htmlFor="settingForm[importer:esa:access_token]" className="col-3 col-form-label">
+                { t('admin:importer_management.esa_settings.access_token') }
               </label>
-              <div className="col-xs-6">
+              <div className="col-6">
                 <input className="form-control" type="password" name="esaAccessToken" value={esaAccessToken} onChange={this.handleInputValue} />
               </div>
             </div>
 
-            <div className="form-group">
-              <div className="col-xs-offset-3 col-xs-6">
+            <div className="form-group row">
+              <div className="offset-3 col-6">
                 <input
                   id="testConnectionToEsa"
                   type="button"
@@ -211,12 +211,12 @@ class ImportDataPage extends React.Component {
                   value={t('admin:importer_management.import')}
                 />
                 <input type="button" className="btn btn-secondary" onClick={this.esaHandleSubmitUpdate} value={t('Update')} />
-                <span className="col-xs-offset-1">
+                <span className="offset-1">
                   <input
                     name="Esa"
                     type="button"
                     id="importFromEsa"
-                    className="btn btn-default btn-esa"
+                    className="btn btn-light btn-esa"
                     onClick={this.esaHandleSubmitTest}
                     value={t('admin:importer_management.esa_settings.test_connection')}
                   />
@@ -228,12 +228,12 @@ class ImportDataPage extends React.Component {
         </form>
 
         <form
-          className="form-horizontal mt-5"
+          className="mt-5"
           id="importerSettingFormQiita"
           role="form"
         >
           <fieldset>
-            <legend>{t('admin:importer_management.import_from', { from: 'Qiita:Team' })}</legend>
+            <h2 className="admin-setting-header">{t('admin:importer_management.import_from', { from: 'Qiita:Team' })}</h2>
             <table className="table table-bordered table-mapping">
               <thead>
                 <tr>
@@ -265,36 +265,36 @@ class ImportDataPage extends React.Component {
                 </tr>
               </tbody>
             </table>
-            <div className="well well-sm mb-0 small">
+            <div className="card well mb-0 small">
               <ul>
                 <li>{t('admin:importer_management.page_skip')}</li>
               </ul>
             </div>
 
-            <div className="form-group">
+            <div className="form-group row">
               <input type="password" name="dummypass" style={{ display: 'none', top: '-100px', left: '-100px' }} />
             </div>
-            <div className="form-group">
-              <label htmlFor="settingForm[importer:qiita:team_name]" className="col-xs-3 control-label">
-                {t('admin:importer_management.qiita_settings.team_name')}
+            <div className="form-group row">
+              <label htmlFor="settingForm[importer:qiita:team_name]" className="col-3 col-form-label">
+                { t('admin:importer_management.qiita_settings.team_name') }
               </label>
-              <div className="col-xs-6">
+              <div className="col-6">
                 <input className="form-control" type="text" name="qiitaTeamName" value={qiitaTeamName} onChange={this.handleInputValue} />
               </div>
             </div>
 
-            <div className="form-group">
-              <label htmlFor="settingForm[importer:qiita:access_token]" className="col-xs-3 control-label">
-                {t('admin:importer_management.qiita_settings.access_token')}
+            <div className="form-group row">
+              <label htmlFor="settingForm[importer:qiita:access_token]" className="col-3 col-form-label">
+                { t('admin:importer_management.qiita_settings.access_token') }
               </label>
-              <div className="col-xs-6">
+              <div className="col-6">
                 <input className="form-control" type="password" name="qiitaAccessToken" value={qiitaAccessToken} onChange={this.handleInputValue} />
               </div>
             </div>
 
 
-            <div className="form-group">
-              <div className="col-xs-offset-3 col-xs-6">
+            <div className="form-group row">
+              <div className="offset-3 col-6">
                 <input
                   id="testConnectionToQiita"
                   type="button"
@@ -304,12 +304,12 @@ class ImportDataPage extends React.Component {
                   value={t('admin:importer_management.import')}
                 />
                 <input type="button" className="btn btn-secondary" onClick={this.qiitaHandleSubmitUpdate} value={t('Update')} />
-                <span className="col-xs-offset-1">
+                <span className="offset-1">
                   <input
                     name="Qiita"
                     type="button"
                     id="importFromQiita"
-                    className="btn btn-default btn-qiita"
+                    className="btn btn-light btn-qiita"
                     onClick={this.qiitaHandleSubmitTest}
                     value={t('admin:importer_management.qiita_settings.test_connection')}
                   />

+ 18 - 24
src/client/js/components/Admin/MarkdownSetting/LineBreakForm.jsx

@@ -1,5 +1,6 @@
 /* eslint-disable react/no-danger */
 import React from 'react';
+
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import loggerFactory from '@alias/logger';
@@ -7,8 +8,10 @@ import loggerFactory from '@alias/logger';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
 
+
 import AppContainer from '../../../services/AppContainer';
 import AdminMarkDownContainer from '../../../services/AdminMarkDownContainer';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 const logger = loggerFactory('growi:importer');
 
@@ -41,17 +44,18 @@ class LineBreakForm extends React.Component {
     const helpLineBreak = { __html: t('admin:markdown_setting.lineBreak_options.enable_lineBreak_desc') };
 
     return (
-      <div className="form-group row">
-        <div className="col-xs-offset-4 col-xs-6 text-left">
-          <div className="checkbox checkbox-success">
+      <div className="form-group text-left my-3">
+        <div className="col-8 offset-4">
+          <div className="custom-control custom-switch custom-checkbox-success">
             <input
               type="checkbox"
+              className="custom-control-input"
               id="isEnabledLinebreaks"
               checked={isEnabledLinebreaks}
               onChange={() => { adminMarkDownContainer.setState({ isEnabledLinebreaks: !isEnabledLinebreaks }) }}
             />
-            <label htmlFor="isEnabledLinebreaks">
-              {t('admin:markdown_setting.lineBreak_options.enable_lineBreak')}
+            <label className="custom-control-label" htmlFor="isEnabledLinebreaks">
+              {t('admin:markdown_setting.lineBreak_options.enable_lineBreak') }
             </label>
           </div>
           <p className="help-block" dangerouslySetInnerHTML={helpLineBreak} />
@@ -67,17 +71,18 @@ class LineBreakForm extends React.Component {
     const helpLineBreakInComment = { __html: t('admin:markdown_setting.lineBreak_options.enable_lineBreak_for_comment_desc') };
 
     return (
-      <div className="form-group row">
-        <div className="col-xs-offset-4 col-xs-6 text-left">
-          <div className="checkbox checkbox-success">
+      <div className="form-group text-left my-3">
+        <div className="col-8 offset-4">
+          <div className="custom-control custom-switch custom-checkbox-success">
             <input
               type="checkbox"
+              className="custom-control-input"
               id="isEnabledLinebreaksInComments"
               checked={isEnabledLinebreaksInComments}
               onChange={() => { adminMarkDownContainer.setState({ isEnabledLinebreaksInComments: !isEnabledLinebreaksInComments }) }}
             />
-            <label htmlFor="isEnabledLinebreaksInComments">
-              {t('admin:markdown_setting.lineBreak_options.enable_lineBreak')}
+            <label className="custom-control-label" htmlFor="isEnabledLinebreaksInComments">
+              {t('admin:markdown_setting.lineBreak_options.enable_lineBreak') }
             </label>
           </div>
           <p className="help-block" dangerouslySetInnerHTML={helpLineBreakInComment} />
@@ -87,26 +92,15 @@ class LineBreakForm extends React.Component {
   }
 
   render() {
-    const { t, adminMarkDownContainer } = this.props;
+    const { adminMarkDownContainer } = this.props;
 
     return (
       <React.Fragment>
-        <fieldset className="row">
+        <fieldset className="col-12">
           {this.renderLineBreakOption()}
           {this.renderLineBreakInCommentOption()}
         </fieldset>
-        <div className="form-group my-3">
-          <div className="col-xs-offset-4 col-xs-5">
-            <button
-              type="submit"
-              className="btn btn-primary"
-              onClick={this.onClickSubmit}
-              disabled={adminMarkDownContainer.state.retrieveError != null}
-            >
-              {t('Update')}
-            </button>
-          </div>
-        </div>
+        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminMarkDownContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 16 - 15
src/client/js/components/Admin/MarkdownSetting/MarkDownSetting.jsx

@@ -1,4 +1,5 @@
 import React from 'react';
+import { Card, CardBody } from 'reactstrap';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
@@ -37,25 +38,25 @@ class MarkdownSetting extends React.Component {
     return (
       <React.Fragment>
         {/* Line Break Setting */}
-        <div className="row mb-5">
-          <h2 className="border-bottom">{t('admin:markdown_setting.lineBreak_header')}</h2>
-          <p className="well">{t('admin:markdown_setting.lineBreak_desc')}</p>
-          <LineBreakForm />
-        </div>
+        <h2 className="admin-setting-header">{t('admin:markdown_setting.lineBreak_header')}</h2>
+        <Card className="card well my-3">
+          <CardBody className="px-0 py-2">{ t('admin:markdown_setting.lineBreak_desc') }</CardBody>
+        </Card>
+        <LineBreakForm />
 
         {/* Presentation Setting */}
-        <div className="row mb-5">
-          <h2 className="border-bottom">{t('admin:markdown_setting.presentation_header')}</h2>
-          <p className="well">{t('admin:markdown_setting.presentation_desc')}</p>
-          <PresentationForm />
-        </div>
+        <h2 className="admin-setting-header">{ t('admin:markdown_setting.presentation_header') }</h2>
+        <Card className="card well my-3">
+          <CardBody className="px-0 py-2">{ t('admin:markdown_setting.presentation_desc') }</CardBody>
+        </Card>
+        <PresentationForm />
 
         {/* XSS Setting */}
-        <div className="row mb-5">
-          <h2 className="border-bottom">{t('admin:markdown_setting.xss_header')}</h2>
-          <p className="well">{t('admin:markdown_setting.xss_desc')}</p>
-          <XssForm />
-        </div>
+        <h2 className="admin-setting-header">{ t('admin:markdown_setting.xss_header') }</h2>
+        <Card className="card well my-3">
+          <CardBody className="px-0 py-2">{ t('admin:markdown_setting.xss_desc') }</CardBody>
+        </Card>
+        <XssForm />
       </React.Fragment>
     );
   }

+ 58 - 51
src/client/js/components/Admin/MarkdownSetting/PresentationForm.jsx

@@ -1,4 +1,5 @@
 import React from 'react';
+
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import loggerFactory from '@alias/logger';
@@ -8,6 +9,7 @@ import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 import AppContainer from '../../../services/AppContainer';
 import AdminMarkDownContainer from '../../../services/AdminMarkDownContainer';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 const logger = loggerFactory('growi:markdown:presentation');
 
@@ -38,70 +40,75 @@ class PresentationForm extends React.Component {
     const { pageBreakSeparator, pageBreakCustomSeparator } = adminMarkDownContainer.state;
 
     return (
-      <fieldset className="form-group row my-2">
+      <fieldset className="form-group col-12 my-2">
 
-        <label className="col-xs-3 control-label text-right">
+        <label className="col-12 control-label font-weight-bold text-left mt-3">
           {t('admin:markdown_setting.presentation_options.page_break_setting')}
         </label>
 
-        <div className="col-xs-3 radio radio-primary">
-          <input
-            type="radio"
-            id="pageBreakOption1"
-            checked={pageBreakSeparator === 1}
-            onChange={() => adminMarkDownContainer.switchPageBreakSeparator(1)}
-          />
-          <label htmlFor="pageBreakOption1">
-            <p className="font-weight-bold">{t('admin:markdown_setting.presentation_options.preset_one_separator')}</p>
-            <div className="mt-3">
-              {t('admin:markdown_setting.presentation_options.preset_one_separator_desc')}
-              <pre><code>{t('admin:markdown_setting.presentation_options.preset_one_separator_value')}</code></pre>
+        <div className="form-group form-check-inline col-12 my-3">
+          <div className="col-4 align-self-start">
+            <div className="custom-control custom-radio">
+              <input
+                type="radio"
+                className="custom-control-input"
+                id="pageBreakOption1"
+                checked={pageBreakSeparator === 1}
+                onChange={() => adminMarkDownContainer.switchPageBreakSeparator(1)}
+              />
+              <label className="custom-control-label" htmlFor="pageBreakOption1">
+                <p className="font-weight-bold">{ t('admin:markdown_setting.presentation_options.preset_one_separator') }</p>
+                <div className="mt-3">
+                  { t('admin:markdown_setting.presentation_options.preset_one_separator_desc') }
+                  <pre><code>{ t('admin:markdown_setting.presentation_options.preset_one_separator_value') }</code></pre>
+                </div>
+              </label>
             </div>
-          </label>
-        </div>
+          </div>
 
-        <div className="col-xs-3 radio radio-primary mt-3">
-          <input
-            type="radio"
-            id="pageBreakOption2"
-            checked={pageBreakSeparator === 2}
-            onChange={() => adminMarkDownContainer.switchPageBreakSeparator(2)}
-          />
-          <label htmlFor="pageBreakOption2">
-            <p className="font-weight-bold">{t('admin:markdown_setting.presentation_options.preset_two_separator')}</p>
-            <div className="mt-3">
-              {t('admin:markdown_setting.presentation_options.preset_two_separator_desc')}
-              <pre><code>{t('admin:markdown_setting.presentation_options.preset_two_separator_value')}</code></pre>
+          <div className="col-4 align-self-start">
+            <div className="custom-control custom-radio">
+              <input
+                type="radio"
+                className="custom-control-input"
+                id="pageBreakOption2"
+                checked={pageBreakSeparator === 2}
+                onChange={() => adminMarkDownContainer.switchPageBreakSeparator(2)}
+              />
+              <label className="custom-control-label" htmlFor="pageBreakOption2">
+                <p className="font-weight-bold">{ t('admin:markdown_setting.presentation_options.preset_two_separator') }</p>
+                <div className="mt-3">
+                  { t('admin:markdown_setting.presentation_options.preset_two_separator_desc') }
+                  <pre><code>{ t('admin:markdown_setting.presentation_options.preset_two_separator_value') }</code></pre>
+                </div>
+              </label>
             </div>
-          </label>
-        </div>
-
-        <div className="col-xs-3 radio radio-primary mt-3">
-          <input
-            type="radio"
-            id="pageBreakOption3"
-            checked={pageBreakSeparator === 3}
-            onChange={() => adminMarkDownContainer.switchPageBreakSeparator(3)}
-          />
-          <label htmlFor="pageBreakOption3">
-            <p className="font-weight-bold">{t('admin:markdown_setting.presentation_options.custom_separator')}</p>
-            <div className="mt-3">
-              {t('admin:markdown_setting.presentation_options.custom_separator_desc')}
+          </div>
+          <div className="col-4 align-self-start">
+            <div className="custom-control custom-radio">
               <input
-                className="form-control"
-                defaultValue={pageBreakCustomSeparator}
-                onChange={(e) => { adminMarkDownContainer.setPageBreakCustomSeparator(e.target.value) }}
+                type="radio"
+                id="pageBreakOption3"
+                className="custom-control-input"
+                checked={pageBreakSeparator === 3}
+                onChange={() => adminMarkDownContainer.switchPageBreakSeparator(3)}
               />
+              <label className="custom-control-label" htmlFor="pageBreakOption3">
+                <p className="font-weight-bold">{ t('admin:markdown_setting.presentation_options.custom_separator') }</p>
+                <div className="mt-3">
+                  { t('admin:markdown_setting.presentation_options.custom_separator_desc') }
+                  <input
+                    className="form-control"
+                    defaultValue={pageBreakCustomSeparator}
+                    onChange={(e) => { adminMarkDownContainer.setPageBreakCustomSeparator(e.target.value) }}
+                  />
+                </div>
+              </label>
             </div>
-          </label>
-        </div>
-
-        <div className="form-group my-3">
-          <div className="col-xs-offset-4 col-xs-5">
-            <div className="btn btn-primary" onClick={this.onClickSubmit} disabled={adminMarkDownContainer.state.retrieveError != null}>{t('Update')}</div>
           </div>
         </div>
 
+        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminMarkDownContainer.state.retrieveError != null} />
       </fieldset>
     );
   }

+ 4 - 4
src/client/js/components/Admin/MarkdownSetting/WhiteListInput.jsx

@@ -35,10 +35,10 @@ class WhiteListInput extends React.Component {
 
     return (
       <>
-        <div className="m-t-15">
+        <div className="mt-4">
           <div className="d-flex justify-content-between">
             {t('admin:markdown_setting.xss_options.tag_names')}
-            <p id="btn-import-tags" className="btn btn-xs btn-primary" onClick={this.onClickRecommendTagButton}>
+            <p id="btn-import-tags" className="btn btn-sm btn-primary mb-0" onClick={this.onClickRecommendTagButton}>
               {t('admin:markdown_setting.xss_options.import_recommended', { target: 'Tags' })}
             </p>
           </div>
@@ -52,10 +52,10 @@ class WhiteListInput extends React.Component {
             onChange={(e) => { adminMarkDownContainer.setState({ tagWhiteList: e.target.value }) }}
           />
         </div>
-        <div className="m-t-15">
+        <div className="mt-4">
           <div className="d-flex justify-content-between">
             {t('admin:markdown_setting.xss_options.tag_attributes')}
-            <p id="btn-import-tags" className="btn btn-xs btn-primary" onClick={this.onClickRecommendAttrButton}>
+            <p id="btn-import-tags" className="btn btn-sm btn-primary mb-0" onClick={this.onClickRecommendAttrButton}>
               {t('admin:markdown_setting.xss_options.import_recommended', { target: 'Attrs' })}
             </p>
           </div>

+ 85 - 75
src/client/js/components/Admin/MarkdownSetting/XssForm.jsx

@@ -1,4 +1,5 @@
 import React from 'react';
+
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import loggerFactory from '@alias/logger';
@@ -9,6 +10,7 @@ import { tags, attrs } from '../../../../../lib/service/xss/recommended-whitelis
 
 import AppContainer from '../../../services/AppContainer';
 import AdminMarkDownContainer from '../../../services/AdminMarkDownContainer';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 import WhiteListInput from './WhiteListInput';
 
@@ -40,76 +42,85 @@ class XssForm extends React.Component {
     const { xssOption } = adminMarkDownContainer.state;
 
     return (
-      <fieldset className="row col-xs-12 my-3">
-        <div className="col-xs-4 radio radio-primary">
-          <input
-            type="radio"
-            id="xssOption1"
-            name="XssOption"
-            checked={xssOption === 1}
-            onChange={() => { adminMarkDownContainer.setState({ xssOption: 1 }) }}
-          />
-          <label htmlFor="xssOption1">
-            <p className="font-weight-bold">{t('admin:markdown_setting.xss_options.ignore_all_tags')}</p>
-            <div className="m-t-15">
-              {t('admin:markdown_setting.xss_options.ignore_all_tags_desc')}
-            </div>
-          </label>
+      <div className="form-group form-check-inline col-12 my-3">
+        <div className="col-4 align-self-start">
+          <div className="custom-control custom-radio ">
+            <input
+              type="radio"
+              className="custom-control-input"
+              id="xssOption1"
+              name="XssOption"
+              checked={xssOption === 1}
+              onChange={() => { adminMarkDownContainer.setState({ xssOption: 1 }) }}
+            />
+            <label className="custom-control-label" htmlFor="xssOption1">
+              <p className="font-weight-bold">{t('admin:markdown_setting.xss_options.ignore_all_tags') }</p>
+              <div className="mt-4">
+                {t('admin:markdown_setting.xss_options.ignore_all_tags_desc') }
+              </div>
+            </label>
+          </div>
         </div>
 
-        <div className="col-xs-4 radio radio-primary">
-          <input
-            type="radio"
-            id="xssOption2"
-            name="XssOption"
-            checked={xssOption === 2}
-            onChange={() => { adminMarkDownContainer.setState({ xssOption: 2 }) }}
-          />
-          <label htmlFor="xssOption2">
-            <p className="font-weight-bold">{t('admin:markdown_setting.xss_options.recommended_setting')}</p>
-            <div className="m-t-15">
-              <div className="d-flex justify-content-between">
-                {t('admin:markdown_setting.xss_options.tag_names')}
+        <div className="col-4 align-self-start">
+          <div className="custom-control custom-radio">
+            <input
+              type="radio"
+              className="custom-control-input"
+              id="xssOption2"
+              name="XssOption"
+              checked={xssOption === 2}
+              onChange={() => { adminMarkDownContainer.setState({ xssOption: 2 }) }}
+            />
+            <label className="custom-control-label" htmlFor="xssOption2">
+              <p className="font-weight-bold">{t('admin:markdown_setting.xss_options.recommended_setting')}</p>
+              <div className="m-t-15">
+                <div className="d-flex justify-content-between">
+                  {t('admin:markdown_setting.xss_options.tag_names')}
+                </div>
+                <textarea
+                  className="form-control xss-list"
+                  name="recommendedTags"
+                  rows="6"
+                  cols="40"
+                  readOnly
+                  defaultValue={tags}
+                />
               </div>
-              <textarea
-                className="form-control xss-list"
-                name="recommendedTags"
-                rows="6"
-                cols="40"
-                readOnly
-                defaultValue={tags}
-              />
-            </div>
-            <div className="m-t-15">
-              <div className="d-flex justify-content-between">
-                {t('admin:markdown_setting.xss_options.tag_attributes')}
+              <div className="m-t-15">
+                <div className="d-flex justify-content-between">
+                  {t('admin:markdown_setting.xss_options.tag_attributes')}
+                </div>
+                <textarea
+                  className="form-control xss-list"
+                  name="recommendedAttrs"
+                  rows="6"
+                  cols="40"
+                  readOnly
+                  defaultValue={attrs}
+                />
               </div>
-              <textarea
-                className="form-control xss-list"
-                name="recommendedAttrs"
-                rows="6"
-                cols="40"
-                readOnly
-                defaultValue={attrs}
-              />
-            </div>
-          </label>
+            </label>
+          </div>
         </div>
 
-        <div className="col-xs-4 radio radio-primary">
-          <input
-            type="radio"
-            id="xssOption3"
-            name="XssOption"
-            checked={xssOption === 3}
-            onChange={() => { adminMarkDownContainer.setState({ xssOption: 3 }) }}
-          />
-          <label htmlFor="xssOption3">
-            <p className="font-weight-bold">{t('admin:markdown_setting.xss_options.custom_whitelist')}</p>
-            <WhiteListInput />
-          </label>
+        <div className="col-4 align-self-start">
+          <div className="custom-control custom-radio">
+            <input
+              type="radio"
+              className="custom-control-input"
+              id="xssOption3"
+              name="XssOption"
+              checked={xssOption === 3}
+              onChange={() => { adminMarkDownContainer.setState({ xssOption: 3 }) }}
+            />
+            <label className="custom-control-label" htmlFor="xssOption3">
+              <p className="font-weight-bold">{t('admin:markdown_setting.xss_options.custom_whitelist') }</p>
+              <WhiteListInput customizable />
+            </label>
+          </div>
         </div>
-      </fieldset>
+      </div>
     );
   }
 
@@ -119,31 +130,30 @@ class XssForm extends React.Component {
 
     return (
       <React.Fragment>
-        <form className="row">
+        <fieldset className="col-12">
           <div className="form-group">
-            <div className="col-xs-offset-4 col-xs-4 text-left">
-              <div className="checkbox checkbox-success">
+            <div className="col-8 offset-4 my-3">
+              <div className="custom-control custom-switch custom-checkbox-success">
                 <input
                   type="checkbox"
+                  className="custom-control-input"
                   id="XssEnable"
-                  className="form-check-input"
                   name="isEnabledXss"
                   checked={isEnabledXss}
                   onChange={adminMarkDownContainer.switchEnableXss}
                 />
-                <label htmlFor="XssEnable">
-                  {t('admin:markdown_setting.xss_options.enable_xss_prevention')}
+                <label className="custom-control-label" htmlFor="XssEnable">
+                  {t('admin:markdown_setting.xss_options.enable_xss_prevention') }
                 </label>
               </div>
             </div>
-            {isEnabledXss && this.xssOptions()}
           </div>
-          <div className="form-group my-3">
-            <div className="col-xs-offset-4 col-xs-5">
-              <div className="btn btn-primary" onClick={this.onClickSubmit} disabled={adminMarkDownContainer.state.retrieveError != null}> {t('Update')}</div>
-            </div>
+
+          <div className="col-12">
+            {isEnabledXss && this.xssOptions()}
           </div>
-        </form>
+        </fieldset>
+        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminMarkDownContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 11 - 10
src/client/js/components/Admin/Notification/GlobalNotification.jsx

@@ -42,25 +42,25 @@ class GlobalNotification extends React.Component {
 
         <h2 className="border-bottom">{t('notification_setting.valid_page')}</h2>
 
-        <p className="well">
+        <p className="card well">
           {/* eslint-disable-next-line react/no-danger */}
           <span dangerouslySetInnerHTML={{ __html: t('notification_setting.link_notification_help') }} />
         </p>
 
 
         <div className="row mb-4">
-          <div className="col-md-8 col-md-offset-2">
-            <div className="checkbox checkbox-success">
+          <div className="col-md-8 offset-md-2">
+            <div className="custom-control custom-checkbox custom-checkbox-success">
               <input
                 id="isNotificationForOwnerPageEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminNotificationContainer.state.isNotificationForOwnerPageEnabled || false}
                 onChange={() => { adminNotificationContainer.switchIsNotificationForOwnerPageEnabled() }}
               />
-              <label htmlFor="isNotificationForOwnerPageEnabled">
+              <label className="custom-control-label" htmlFor="isNotificationForOwnerPageEnabled">
                 {/* eslint-disable-next-line react/no-danger */}
                 <span dangerouslySetInnerHTML={{ __html: t('notification_setting.just_me_notification_help') }} />
-
               </label>
             </div>
           </div>
@@ -68,15 +68,16 @@ class GlobalNotification extends React.Component {
 
 
         <div className="row mb-4">
-          <div className="col-md-8 col-md-offset-2">
-            <div className="checkbox checkbox-success">
+          <div className="col-md-8 offset-md-2">
+            <div className="custom-control custom-checkbox custom-checkbox-success">
               <input
                 id="isNotificationForGroupPageEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminNotificationContainer.state.isNotificationForGroupPageEnabled || false}
                 onChange={() => { adminNotificationContainer.switchIsNotificationForGroupPageEnabled() }}
               />
-              <label htmlFor="isNotificationForGroupPageEnabled">
+              <label className="custom-control-label" htmlFor="isNotificationForGroupPageEnabled">
                 {/* eslint-disable-next-line react/no-danger */}
                 <span dangerouslySetInnerHTML={{ __html: t('notification_setting.group_notification_help') }} />
               </label>
@@ -85,7 +86,7 @@ class GlobalNotification extends React.Component {
         </div>
 
         <div className="row my-3">
-          <div className="col-xs-offset-4 col-xs-5">
+          <div className="col-sm-5 offset-sm-4">
             <button
               type="button"
               className="btn btn-primary"
@@ -98,7 +99,7 @@ class GlobalNotification extends React.Component {
 
         <h2 className="border-bottom mb-5">{t('notification_setting.notification_list')}
           <a href="/admin/global-notification/new">
-            <p className="btn btn-default pull-right">{t('notification_setting.add_notification')}</p>
+            <p className="btn btn-light pull-right">{t('notification_setting.add_notification')}</p>
           </a>
         </h2>
 

+ 33 - 26
src/client/js/components/Admin/Notification/GlobalNotificationList.jsx

@@ -76,44 +76,48 @@ class GlobalNotificationList extends React.Component {
           return (
             <tr key={notification._id}>
               <td className="align-middle td-abs-center">
-                <input
-                  id="isNotificationEnabled"
-                  type="checkbox"
-                  defaultChecked={notification.isEnabled}
-                  onClick={e => this.toggleIsEnabled(notification)}
-                />
+                <div className="custom-control custom-switch custom-checkbox-success">
+                  <input
+                    type="checkbox"
+                    className="custom-control-input"
+                    id={notification._id}
+                    defaultChecked={notification.isEnabled}
+                    onClick={() => this.toggleIsEnabled(notification)}
+                  />
+                  <label className="custom-control-label" htmlFor={notification._id} />
+                </div>
               </td>
               <td>
                 {notification.triggerPath}
               </td>
               <td>
                 {notification.triggerEvents.includes('pageCreate') && (
-                  <span className="label label-success" data-toggle="tooltip" data-placement="top" title="Page Create">
+                  <span className="badge badge-pill badge-success" data-toggle="tooltip" data-placement="top" title="Page Create">
                     <i className="icon-doc"></i> CREATE
                   </span>
                 )}
                 {notification.triggerEvents.includes('pageEdit') && (
-                  <span className="label label-warning" data-toggle="tooltip" data-placement="top" title="Page Edit">
+                  <span className="badge badge-pill badge-warning" data-toggle="tooltip" data-placement="top" title="Page Edit">
                     <i className="icon-pencil"></i> EDIT
                   </span>
                 )}
                 {notification.triggerEvents.includes('pageMove') && (
-                  <span className="label label-warning" data-toggle="tooltip" data-placement="top" title="Page Move">
+                  <span className="badge badge-pill badge-warning" data-toggle="tooltip" data-placement="top" title="Page Move">
                     <i className="icon-action-redo"></i> MOVE
                   </span>
                 )}
                 {notification.triggerEvents.includes('pageDelete') && (
-                  <span className="label label-danger" data-toggle="tooltip" data-placement="top" title="Page Delte">
+                  <span className="badge badge-pill badge-danger" data-toggle="tooltip" data-placement="top" title="Page Delte">
                     <i className="icon-fire"></i> DELETE
                   </span>
                 )}
                 {notification.triggerEvents.includes('pageLike') && (
-                  <span className="label label-info" data-toggle="tooltip" data-placement="top" title="Page Like">
+                  <span className="badge badge-pill badge-info" data-toggle="tooltip" data-placement="top" title="Page Like">
                     <i className="icon-like"></i> LIKE
                   </span>
                 )}
                 {notification.triggerEvents.includes('comment') && (
-                  <span className="label label-default" data-toggle="tooltip" data-placement="top" title="New Comment">
+                  <span className="badge badge-pill badge-light" data-toggle="tooltip" data-placement="top" title="New Comment">
                     <i className="icon-fw icon-bubble"></i> POST
                   </span>
                 )}
@@ -125,22 +129,25 @@ class GlobalNotificationList extends React.Component {
                   && <span data-toggle="tooltip" data-placement="top" title="Slack"><i className="fa fa-slack"></i> {notification.slackChannels}</span>}
               </td>
               <td className="td-abs-center">
-                <div className="btn-group admin-group-menu">
-                  <button type="button" className="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
+                <div className="dropdown">
+                  <button
+                    className="btn btn-light dropdown-toggle"
+                    type="button"
+                    id="dropdownMenuButton"
+                    data-toggle="dropdown"
+                    aria-haspopup="true"
+                    aria-expanded="false"
+                  >
                     <i className="icon-settings"></i> <span className="caret"></span>
                   </button>
-                  <ul className="dropdown-menu" role="menu">
-                    <li>
-                      <a href={urljoin('/admin/global-notification/', notification._id)}>
-                        <i className="icon-fw icon-note"></i> {t('Edit')}
-                      </a>
-                    </li>
-                    <li onClick={() => this.openConfirmationModal(notification)}>
-                      <a>
-                        <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
-                      </a>
-                    </li>
-                  </ul>
+                  <div className="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
+                    <a className="dropdown-item" href={urljoin('/admin/global-notification/', notification._id)}>
+                      <i className="icon-fw icon-note"></i> {t('Edit')}
+                    </a>
+                    <a className="dropdown-item" onClick={() => this.openConfirmationModal(notification)}>
+                      <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
+                    </a>
+                  </div>
                 </div>
               </td>
             </tr>

+ 46 - 36
src/client/js/components/Admin/Notification/ManageGlobalNotification.jsx

@@ -100,10 +100,15 @@ class ManageGlobalNotification extends React.Component {
     return (
       <React.Fragment>
 
-        <a href="/admin/notification#global-notification" className="btn btn-default">
-          <i className="icon-fw ti-arrow-left" aria-hidden="true"></i>
-          {t('notification_setting.back_to_list')}
-        </a>
+        <div className="my-3">
+          <a href="/admin/notification#global-notification">
+            <button type="button" className="btn page-link text-dark d-inline-block">
+              <i className="icon-fw ti-arrow-left" aria-hidden="true"></i>
+              {t('notification_setting.back_to_list')}
+            </button>
+          </a>
+        </div>
+
 
         <div className="row">
           <div className="m-t-20 form-box col-md-12">
@@ -111,25 +116,26 @@ class ManageGlobalNotification extends React.Component {
           </div>
 
           <div className="col-sm-4">
+            <h3 htmlFor="triggerPath">{t('notification_setting.trigger_path')}
+              {/* eslint-disable-next-line react/no-danger */}
+              <small dangerouslySetInnerHTML={{ __html: t('notification_setting.trigger_path_help', '<code>*</code>') }} />
+            </h3>
             <div className="form-group">
-              <h3 htmlFor="triggerPath">{t('notification_setting.trigger_path')}
-                {/* eslint-disable-next-line react/no-danger */}
-                <small dangerouslySetInnerHTML={{ __html: t('notification_setting.trigger_path_help', '<code>*</code>') }} />
-                <input
-                  className="form-control"
-                  type="text"
-                  name="triggerPath"
-                  value={this.state.triggerPath}
-                  onChange={(e) => { this.onChangeTriggerPath(e.target.value) }}
-                  required
-                />
-              </h3>
+              <input
+                className="form-control"
+                type="text"
+                name="triggerPath"
+                value={this.state.triggerPath}
+                onChange={(e) => { this.onChangeTriggerPath(e.target.value) }}
+                required
+              />
             </div>
 
+            <h3>{t('notification_setting.notify_to')}</h3>
             <div className="form-group form-inline">
-              <h3>{t('notification_setting.notify_to')}</h3>
-              <div className="radio radio-primary">
+              <div className="custom-control custom-radio">
                 <input
+                  className="custom-control-input"
                   type="radio"
                   id="mail"
                   name="notifyToType"
@@ -137,12 +143,13 @@ class ManageGlobalNotification extends React.Component {
                   checked={this.state.notifyToType === 'mail'}
                   onChange={() => { this.onChangeNotifyToType('mail') }}
                 />
-                <label htmlFor="mail">
+                <label className="custom-control-label" htmlFor="mail">
                   <p className="font-weight-bold">Email</p>
                 </label>
               </div>
-              <div className="radio radio-primary">
+              <div className="custom-control custom-radio ml-2">
                 <input
+                  className="custom-control-input"
                   type="radio"
                   id="slack"
                   name="notifyToType"
@@ -150,7 +157,7 @@ class ManageGlobalNotification extends React.Component {
                   checked={this.state.notifyToType === 'slack'}
                   onChange={() => { this.onChangeNotifyToType('slack') }}
                 />
-                <label htmlFor="slack">
+                <label className="custom-control-label" htmlFor="slack">
                   <p className="font-weight-bold">Slack</p>
                 </label>
               </div>
@@ -187,78 +194,81 @@ class ManageGlobalNotification extends React.Component {
                   />
                 </div>
               )}
-
           </div>
 
-
-          <div className="col-sm-offset-1 col-sm-5">
+          <div className="offset-1 col-sm-5">
             <div className="form-group">
               <h3>{t('notification_setting.trigger_events')}</h3>
               <TriggerEventCheckBox
+                checkbox="success"
                 event="pageCreate"
                 checked={this.state.triggerEvents.has('pageCreate')}
                 onChange={() => this.onChangeTriggerEvents('pageCreate')}
               >
-                <span className="label label-success">
+                <span className="badge badge-pill badge-success">
                   <i className="icon-doc"></i> CREATE
                 </span>
               </TriggerEventCheckBox>
               <TriggerEventCheckBox
+                checkbox="warning"
                 event="pageEdit"
                 checked={this.state.triggerEvents.has('pageEdit')}
                 onChange={() => this.onChangeTriggerEvents('pageEdit')}
               >
-                <span className="label label-warning">
+                <span className="badge badge-pill badge-warning">
                   <i className="icon-pencil"></i>EDIT
                 </span>
               </TriggerEventCheckBox>
               <TriggerEventCheckBox
+                checkbox="warning"
                 event="pageMove"
                 checked={this.state.triggerEvents.has('pageMove')}
                 onChange={() => this.onChangeTriggerEvents('pageMove')}
               >
-                <span className="label label-warning">
+                <span className="badge badge-pill badge-warning">
                   <i className="icon-action-redo"></i>MOVE
                 </span>
               </TriggerEventCheckBox>
               <TriggerEventCheckBox
+                checkbox="danger"
                 event="pageDelete"
                 checked={this.state.triggerEvents.has('pageDelete')}
                 onChange={() => this.onChangeTriggerEvents('pageDelete')}
               >
-                <span className="label label-danger">
+                <span className="badge badge-pill badge-danger">
                   <i className="icon-fire"></i>DELETE
                 </span>
               </TriggerEventCheckBox>
               <TriggerEventCheckBox
+                checkbox="info"
                 event="pageLike"
                 checked={this.state.triggerEvents.has('pageLike')}
                 onChange={() => this.onChangeTriggerEvents('pageLike')}
               >
-                <span className="label label-info">
+                <span className="badge badge-pill badge-info">
                   <i className="icon-like"></i>LIKE
                 </span>
               </TriggerEventCheckBox>
               <TriggerEventCheckBox
+                checkbox="secondary"
                 event="comment"
                 checked={this.state.triggerEvents.has('comment')}
                 onChange={() => this.onChangeTriggerEvents('comment')}
               >
-                <span className="label label-default">
+                <span className="badge badge-pill badge-light">
                   <i className="icon-bubble"></i>POST
                 </span>
               </TriggerEventCheckBox>
 
             </div>
           </div>
-
-          <AdminUpdateButtonRow
-            onClick={this.submitHandler}
-            disabled={this.state.retrieveError != null}
-          />
-
         </div>
 
+        <AdminUpdateButtonRow
+          onClick={this.submitHandler}
+          disabled={this.state.retrieveError != null}
+        />
+
       </React.Fragment>
 
     );

+ 13 - 15
src/client/js/components/Admin/Notification/NotificationDeleteModal.jsx

@@ -2,34 +2,32 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
-import Modal from 'react-bootstrap/es/Modal';
+import {
+  Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
 
 class NotificationDeleteModal extends React.PureComponent {
 
   render() {
     const { t, notificationForConfiguration } = this.props;
     return (
-      <Modal show={this.props.isOpen} onHide={this.props.onClose}>
-        <Modal.Header className="modal-header" closeButton>
-          <Modal.Title>
-            <div className="modal-header bg-danger">
-              <i className="icon icon-fire"></i> Delete Global Notification Setting
-            </div>
-          </Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
+      <Modal isOpen={this.props.isOpen} toggle={this.props.onClose}>
+        <ModalHeader tag="h4" toggle={this.props.onClose} className="modal-header">
+          <i className="icon icon-fire"></i> Delete Global Notification Setting
+        </ModalHeader>
+        <ModalBody>
           <p>
             {t('notification_setting.delete_notification_pattern_desc1', { path: notificationForConfiguration.triggerPath })}
           </p>
-          <span className="text-danger">
+          <p className="text-danger">
             {t('notification_setting.delete_notification_pattern_desc2')}
-          </span>
-        </Modal.Body>
-        <Modal.Footer className="text-right">
+          </p>
+        </ModalBody>
+        <ModalFooter>
           <button type="button" className="btn btn-sm btn-danger" onClick={this.props.onClickSubmit}>
             <i className="icon icon-fire"></i> {t('Delete')}
           </button>
-        </Modal.Footer>
+        </ModalFooter>
       </Modal>
     );
   }

+ 59 - 24
src/client/js/components/Admin/Notification/NotificationSetting.jsx

@@ -1,6 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import {
+  TabContent, TabPane, Nav, NavItem, NavLink,
+} from 'reactstrap';
 
 import loggerFactory from '@alias/logger';
 
@@ -18,6 +21,18 @@ const logger = loggerFactory('growi:NotificationSetting');
 
 class NotificationSetting extends React.Component {
 
+  constructor() {
+    super();
+
+    this.state = {
+      activeTab: 'slack-configuration',
+      // Prevent unnecessary rendering
+      activeComponents: new Set(['slack-configuration']),
+    };
+
+    this.toggleActiveTab = this.toggleActiveTab.bind(this);
+  }
+
   async componentDidMount() {
     const { adminNotificationContainer } = this.props;
 
@@ -32,34 +47,54 @@ class NotificationSetting extends React.Component {
 
   }
 
+  toggleActiveTab(activeTab) {
+    this.setState({
+      activeTab, activeComponents: this.state.activeComponents.add(activeTab),
+    });
+  }
+
   render() {
+    const { activeTab, activeComponents } = this.state;
 
     return (
       <React.Fragment>
-        <div className="notification-settings">
-          <ul className="nav nav-tabs" role="tablist">
-            <li className="active">
-              <a href="#slack-configuration" data-toggle="tab" role="tab"><i className="icon-settings"></i> Slack Configuration</a>
-            </li>
-            <li>
-              <a href="#user-trigger-notification" data-toggle="tab" role="tab"><i className="icon-settings"></i> User Trigger Notification</a>
-            </li>
-            <li>
-              <a href="#global-notification" data-toggle="tab" role="tab"><i className="icon-settings"></i> Global Notification</a>
-            </li>
-          </ul>
-          <div className="tab-content m-t-15">
-            <div id="slack-configuration" className="tab-pane active" role="tabpanel">
-              <SlackAppConfiguration />
-            </div>
-            <div id="user-trigger-notification" className="tab-pane" role="tabpanel">
-              <UserTriggerNotification />
-            </div>
-            <div id="global-notification" className="tab-pane" role="tabpanel">
-              <GlobalNotification />
-            </div>
-          </div>
-        </div>
+        <Nav tabs>
+          <NavItem>
+            <NavLink
+              className={`${activeTab === 'slack-configuration' && 'active'} `}
+              onClick={() => { this.toggleActiveTab('slack-configuration') }}
+            >
+              <i className="icon-settings"></i> Slack Configuration
+            </NavLink>
+          </NavItem>
+          <NavItem>
+            <NavLink
+              className={`${activeTab === 'user-trigger-notification' && 'active'} `}
+              onClick={() => { this.toggleActiveTab('user-trigger-notification') }}
+            >
+              <i className="icon-settings"></i> User Trigger Notification
+            </NavLink>
+          </NavItem>
+          <NavItem>
+            <NavLink
+              className={`${activeTab === 'global-notification' && 'active'} `}
+              onClick={() => { this.toggleActiveTab('global-notification') }}
+            >
+              <i className="icon-settings"></i> Global Notification
+            </NavLink>
+          </NavItem>
+        </Nav>
+        <TabContent activeTab={activeTab}>
+          <TabPane tabId="slack-configuration">
+            {activeComponents.has('slack-configuration') && <SlackAppConfiguration />}
+          </TabPane>
+          <TabPane tabId="user-trigger-notification">
+            {activeComponents.has('user-trigger-notification') && <UserTriggerNotification />}
+          </TabPane>
+          <TabPane tabId="global-notification">
+            {activeComponents.has('global-notification') && <GlobalNotification />}
+          </TabPane>
+        </TabContent>
       </React.Fragment>
     );
   }

+ 32 - 34
src/client/js/components/Admin/Notification/SlackAppConfiguration.jsx

@@ -39,25 +39,22 @@ class SlackAppConfiguration extends React.Component {
 
     return (
       <React.Fragment>
-        <div className="row mb-5">
-          <div className="col-xs-6 text-left">
-            <div className="my-0 btn-group">
-              <div className="dropdown">
-                <button className="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                  <span className="pull-left">Slack {adminNotificationContainer.state.selectSlackOption} </span>
-                  <span className="bs-caret pull-right">
-                    <span className="caret" />
-                  </span>
-                </button>
-                {/* TODO adjust dropdown after BS4 */}
-                <ul className="dropdown-menu" role="menu">
-                  <li type="button" onClick={() => adminNotificationContainer.switchSlackOption('Incoming Webhooks')}>
-                    <a role="menuitem">Slack Incoming Webhooks</a>
-                  </li>
-                  <li type="button" onClick={() => adminNotificationContainer.switchSlackOption('App')}>
-                    <a role="menuitem">Slack App</a>
-                  </li>
-                </ul>
+        <div className="row my-3">
+          <div className="col-6 text-left">
+            <div className="dropdown">
+              <button
+                className="btn btn-secondary dropdown-toggle"
+                type="button"
+                id="dropdownMenuButton"
+                data-toggle="dropdown"
+                aria-haspopup="true"
+                aria-expanded="true"
+              >
+                {`Slack ${adminNotificationContainer.state.selectSlackOption}`}
+              </button>
+              <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                <a className="dropdown-item" onClick={() => adminNotificationContainer.switchSlackOption('Incoming Webhooks')}>Slack Incoming Webhooks</a>
+                <a className="dropdown-item" onClick={() => adminNotificationContainer.switchSlackOption('App')}>Slack App</a>
               </div>
             </div>
           </div>
@@ -66,9 +63,9 @@ class SlackAppConfiguration extends React.Component {
           <React.Fragment>
             <h2 className="border-bottom mb-5">{t('notification_setting.slack_incoming_configuration')}</h2>
 
-            <div className="row mb-5">
-              <label className="col-xs-3 text-right">Webhook URL</label>
-              <div className="col-xs-6">
+            <div className="row mb-3">
+              <label className="col-3 text-right">Webhook URL</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -78,16 +75,17 @@ class SlackAppConfiguration extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+            <div className="row mb-3">
+              <div className="offset-3 col-6 text-left">
+                <div className="custom-control custom-switch custom-checkbox-success">
                   <input
-                    id="cbPrioritizeIWH"
                     type="checkbox"
+                    className="custom-control-input"
+                    id="cbPrioritizeIWH"
                     checked={adminNotificationContainer.state.isIncomingWebhookPrioritized || false}
                     onChange={() => { adminNotificationContainer.switchIsIncomingWebhookPrioritized() }}
                   />
-                  <label htmlFor="cbPrioritizeIWH">
+                  <label className="custom-control-label" htmlFor="cbPrioritizeIWH">
                     {t('notification_setting.prioritize_webhook')}
                   </label>
                 </div>
@@ -102,24 +100,24 @@ class SlackAppConfiguration extends React.Component {
             <React.Fragment>
               <h2 className="border-bottom mb-5">{t('notification_setting.slack_app_configuration')}</h2>
 
-              <div className="well">
-                <i className="icon-fw icon-exclamation text-danger"></i><span className="text-danger">NOT RECOMMENDED</span>
-                <br /><br />
+              <div className="well card">
+                <span className="text-danger"><i className="icon-fw icon-exclamation"></i>NOT RECOMMENDED</span>
+                <br />
                 {/* eslint-disable-next-line react/no-danger */}
                 <span dangerouslySetInnerHTML={{ __html: t('notification_setting.slack_app_configuration_desc') }} />
-                <br /><br />
+                <br />
                 <a
                   href="#slack-incoming-webhooks"
                   data-toggle="tab"
                   onClick={() => adminNotificationContainer.switchSlackOption('Incoming Webhooks')}
                 >
                   {t('notification_setting.use_instead')}
-                </a>{' '}
+                </a>
               </div>
 
               <div className="row mb-5">
-                <label className="col-xs-3 text-right">OAuth Access Token</label>
-                <div className="col-xs-6">
+                <label className="col-3 text-right">OAuth Access Token</label>
+                <div className="col-6">
                   <input
                     className="form-control"
                     type="text"

+ 4 - 3
src/client/js/components/Admin/Notification/TriggerEventCheckBox.jsx

@@ -6,15 +6,15 @@ const TriggerEventCheckBox = (props) => {
   const { t } = props;
 
   return (
-    <div className="checkbox checkbox-inverse">
+    <div className={`custom-control custom-checkbox custom-checkbox-${props.checkbox}`}>
       <input
+        className="custom-control-input"
         type="checkbox"
         id={`trigger-event-${props.event}`}
-        value={props.event}
         checked={props.checked}
         onChange={props.onChange}
       />
-      <label htmlFor={`trigger-event-${props.event}`}>
+      <label className="custom-control-label" htmlFor={`trigger-event-${props.event}`}>
         {props.children}{' '}
         {t(`notification_setting.event_${props.event}`)}
       </label>
@@ -26,6 +26,7 @@ const TriggerEventCheckBox = (props) => {
 TriggerEventCheckBox.propTypes = {
   t: PropTypes.func.isRequired, // i18next
 
+  checkbox: PropTypes.string.isRequired,
   checked: PropTypes.bool.isRequired,
   onChange: PropTypes.func.isRequired,
   event: PropTypes.string.isRequired,

+ 1 - 1
src/client/js/components/Admin/Notification/UserNotificationRow.jsx

@@ -22,7 +22,7 @@ class UserNotificationRow extends React.PureComponent {
             {notification.channel}
           </td>
           <td>
-            <button type="submit" className="btn btn-default" onClick={() => { this.props.onClickDeleteBtn(notification._id) }}>{t('Delete')}</button>
+            <button type="submit" className="btn btn-light" onClick={() => { this.props.onClickDeleteBtn(notification._id) }}>{t('Delete')}</button>
           </td>
         </tr>
       </React.Fragment>

+ 1 - 1
src/client/js/components/Admin/Notification/UserTriggerNotification.jsx

@@ -82,7 +82,7 @@ class UserTriggerNotification extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="border-bottom mb-5">{t('notification_setting.user_trigger_notification_header')}</h2>
+        <h2 className="border-bottom my-5">{t('notification_setting.user_trigger_notification_header')}</h2>
 
         <table className="table table-bordered">
           <thead>

+ 10 - 10
src/client/js/components/Admin/Security/BasicSecuritySetting.jsx

@@ -68,18 +68,16 @@ class BasicSecurityManagement extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.Basic.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="col-6 offset-3">
+            <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isBasicEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isBasicEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsBasicEnabled() }}
               />
-              <label htmlFor="isBasicEnabled">
+              <label className="custom-control-label" htmlFor="isBasicEnabled">
                 { t('security_setting.Basic.enable_basic') }
               </label>
             </div>
@@ -90,22 +88,24 @@ class BasicSecurityManagement extends React.Component {
               </small>
             </p>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('basic') && isBasicEnabled)
-            && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+            && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         {isBasicEnabled && (
         <React.Fragment>
           <div className="row mb-5">
-            <div className="col-xs-offset-3 col-xs-6 text-left">
-              <div className="checkbox checkbox-success">
+            <div className="offset-3 col-6">
+              <div className="custom-control custom-switch custom-checkbox-success">
                 <input
                   id="bindByEmail-basic"
+                  className="custom-control-input"
                   type="checkbox"
                   checked={adminBasicSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
                   onChange={() => { adminBasicSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                 />
                 <label
+                  className="custom-control-label"
                   htmlFor="bindByEmail-basic"
                   dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical', 'username') }}
                 />
@@ -117,7 +117,7 @@ class BasicSecurityManagement extends React.Component {
           </div>
 
           <div className="row my-3">
-            <div className="col-xs-offset-4 col-xs-5">
+            <div className="offset-4 col-5">
               <button type="button" className="btn btn-primary" disabled={adminBasicSecurityContainer.state.retrieveError != null} onClick={this.onClickSubmit}>
                 {t('Update')}
               </button>

+ 16 - 16
src/client/js/components/Admin/Security/GitHubSecuritySetting.jsx

@@ -69,29 +69,27 @@ class GitHubSecurityManagement extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.OAuth.GitHub.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="offset-3 col-6 text-left">
+            <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isGitHubEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isGitHubEnabled || false}
                 onChange={() => { adminGeneralSecurityContainer.switchIsGitHubOAuthEnabled() }}
               />
-              <label htmlFor="isGitHubEnabled">
+              <label className="custom-control-label" htmlFor="isGitHubEnabled">
                 {t('security_setting.OAuth.GitHub.enable_github')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('github') && isGitHubEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badg badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-          <div className="col-xs-6">
+          <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -118,8 +116,8 @@ class GitHubSecurityManagement extends React.Component {
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
             <div className="row mb-5">
-              <label htmlFor="githubClientId" className="col-xs-3 text-right">{t('security_setting.clientID')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="githubClientId" className="col-3 text-right py-2">{t('security_setting.clientID')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -134,8 +132,8 @@ class GitHubSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="githubClientSecret" className="col-xs-3 text-right">{t('security_setting.client_secret')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="githubClientSecret" className="col-3 text-right py-2">{t('security_setting.client_secret')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -150,15 +148,17 @@ class GitHubSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6 text-left">
+                <div className="custom-control custom-switch custom-checkbox-success">
                   <input
                     id="bindByUserNameGitHub"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminGitHubSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
                     onChange={() => { adminGitHubSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByUserNameGitHub"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
                   />
@@ -170,7 +170,7 @@ class GitHubSecurityManagement extends React.Component {
             </div>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <div className="btn btn-primary" disabled={adminGitHubSecurityContainer.state.retrieveError != null} onClick={this.onClickSubmit}>
                   {t('Update')}
                 </div>

+ 16 - 16
src/client/js/components/Admin/Security/GoogleSecuritySetting.jsx

@@ -69,29 +69,27 @@ class GoogleSecurityManagement extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.OAuth.Google.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="offset-3 col-6">
+            <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isGoogleEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isGoogleEnabled || false}
                 onChange={() => { adminGeneralSecurityContainer.switchIsGoogleOAuthEnabled() }}
               />
-              <label htmlFor="isGoogleEnabled">
+              <label className="custom-control-label" htmlFor="isGoogleEnabled">
                 {t('security_setting.OAuth.Google.enable_google')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('google') && isGoogleEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-          <div className="col-xs-6">
+          <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -118,8 +116,8 @@ class GoogleSecurityManagement extends React.Component {
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
             <div className="row mb-5">
-              <label htmlFor="googleClientId" className="col-xs-3 text-right">{t('security_setting.clientID')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="googleClientId" className="col-3 text-right py-2">{t('security_setting.clientID')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -134,8 +132,8 @@ class GoogleSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="googleClientSecret" className="col-xs-3 text-right">{t('security_setting.client_secret')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="googleClientSecret" className="col-3 text-right py-2">{t('security_setting.client_secret')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -150,15 +148,17 @@ class GoogleSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6">
+                <div className="custom-control custom-switch custom-checkbox-success">
                   <input
                     id="bindByUserNameGoogle"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminGoogleSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
                     onChange={() => { adminGoogleSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByUserNameGoogle"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
                   />
@@ -170,7 +170,7 @@ class GoogleSecurityManagement extends React.Component {
             </div>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <button
                   type="button"
                   className="btn btn-primary"

+ 13 - 10
src/client/js/components/Admin/Security/LdapAuthTestModal.jsx

@@ -2,7 +2,12 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
-import Modal from 'react-bootstrap/es/Modal';
+import {
+  Modal,
+  ModalHeader,
+  ModalBody,
+  ModalFooter,
+} from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 
@@ -42,21 +47,19 @@ class LdapAuthTestModal extends React.Component {
   render() {
 
     return (
-      <Modal show={this.props.isOpen} onHide={this.props.onClose}>
-        <Modal.Header className="modal-header" closeButton>
-          <Modal.Title>
-            Test LDAP Account
-          </Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
+      <Modal isOpen={this.props.isOpen} toggle={this.props.onClose}>
+        <ModalHeader tag="h4" className="modal-header" toggle={this.props.onClose}>
+          Test LDAP Account
+        </ModalHeader>
+        <ModalBody>
           <LdapAuthTest
             username={this.state.username}
             password={this.state.password}
             onChangeUsername={this.onChangeUsername}
             onChangePassword={this.onChangePassword}
           />
-        </Modal.Body>
-        <Modal.Footer />
+        </ModalBody>
+        <ModalFooter />
       </Modal>
     );
   }

+ 76 - 53
src/client/js/components/Admin/Security/LdapSecuritySetting.jsx

@@ -74,23 +74,21 @@ class LdapSecuritySetting extends React.Component {
         </h2>
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>Use LDAP</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="col-6 offset-3">
+            <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isLdapEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={isLdapEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsLdapEnabled() }}
               />
-              <label htmlFor="isLdapEnabled">
+              <label className="custom-control-label" htmlFor="isLdapEnabled">
                 {t('security_setting.ldap.enable_ldap')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('ldap') && isLdapEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
@@ -101,8 +99,10 @@ class LdapSecuritySetting extends React.Component {
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
             <div className="row mb-5">
-              <label htmlFor="serverUrl" className="col-xs-3 control-label text-right">Server URL</label>
-              <div className="col-xs-6">
+              <label htmlFor="serverUrl" className="col-3 control-label text-right py-2">
+                Server URL
+              </label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -122,35 +122,40 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <strong className="col-xs-3 text-right">{t('security_setting.ldap.bind_mode')}</strong>
-              <div className="col-xs-6 text-left">
-                <div className="my-0 btn-group">
-                  <div className="dropdown">
-                    <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                      {adminLdapSecurityContainer.state.isUserBind
+              <div className="col-3 text-right py-2">
+                <strong>{t('security_setting.ldap.bind_mode')}</strong>
+              </div>
+              <div className="col-6">
+                <div className="dropdown">
+                  <button
+                    className="btn btn-light dropdown-toggle"
+                    type="button"
+                    id="dropdownMenuButton"
+                    data-toggle="dropdown"
+                    aria-haspopup="true"
+                    aria-expanded="true"
+                  >
+                    {adminLdapSecurityContainer.state.isUserBind
                         ? <span className="pull-left">{t('security_setting.ldap.bind_user')}</span>
                         : <span className="pull-left">{t('security_setting.ldap.bind_manager')}</span>}
-                      <span className="bs-caret pull-right">
-                        <span className="caret" />
-                      </span>
-                    </button>
-                    {/* TODO adjust dropdown after BS4 */}
-                    <ul className="dropdown-menu" role="menu">
-                      <li key="user" role="presentation" type="button" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(true) }}>
-                        <a role="menuitem">{t('security_setting.ldap.bind_user')}</a>
-                      </li>
-                      <li key="manager" role="presentation" type="button" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(false) }}>
-                        <a role="menuitem">{t('security_setting.ldap.bind_manager')}</a>
-                      </li>
-                    </ul>
+                  </button>
+                  <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                    <a className="dropdown-item" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(true) }}>
+                      {t('security_setting.ldap.bind_user')}
+                    </a>
+                    <a className="dropdown-item" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(false) }}>
+                      {t('security_setting.ldap.bind_manager')}
+                    </a>
                   </div>
                 </div>
               </div>
             </div>
 
             <div className="row mb-5">
-              <strong className="col-xs-3 text-right">Bind DN</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong>Bind DN</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -182,10 +187,12 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="bindDNPassword" className="col-xs-3 text-right">{t('security_setting.ldap.bind_DN_password')}</label>
-              <div className="col-xs-6">
+              <div htmlFor="bindDNPassword" className="col-3 text-right py-2">
+                <strong>{t('security_setting.ldap.bind_DN_password')}</strong>
+              </div>
+              <div className="col-6">
                 {(adminLdapSecurityContainer.state.isUserBind) ? (
-                  <p className="help-block passport-ldap-userbind">
+                  <p className="well card passport-ldap-userbind">
                     <small>
                       {t('security_setting.ldap.bind_DN_password_user_detail')}
                     </small>
@@ -193,7 +200,7 @@ class LdapSecuritySetting extends React.Component {
                 )
                   : (
                     <>
-                      <p className="help-block passport-ldap-managerbind">
+                      <p className="well card passport-ldap-managerbind">
                         <small>
                           {t('security_setting.ldap.bind_DN_password_manager_detail')}
                         </small>
@@ -211,8 +218,10 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <strong className="col-xs-3 text-right">{t('security_setting.ldap.search_filter')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong>{t('security_setting.ldap.search_filter')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -245,8 +254,10 @@ class LdapSecuritySetting extends React.Component {
             </h3>
 
             <div className="row mb-5">
-              <strong htmlFor="attrMapUsername" className="col-xs-3 text-right">{t('username')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="attrMapUsername">{t('username')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -263,15 +274,17 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6">
+                <div className="custom-control custom-switch custom-checkbox-success">
                   <input
-                    id="isSameUsernameTreatedAsIdenticalUser"
                     type="checkbox"
+                    className="custom-control-input"
+                    id="isSameUsernameTreatedAsIdenticalUser"
                     checked={adminLdapSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser}
                     onChange={() => { adminLdapSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="isSameUsernameTreatedAsIdenticalUser"
                     // eslint-disable-next-line react/no-danger
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical') }}
@@ -285,8 +298,10 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <strong htmlFor="attrMapMail" className="col-xs-3 text-right">{t('Email')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="attrMapMail">{t('Email')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -304,8 +319,10 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <strong htmlFor="attrMapName" className="col-xs-3 text-right">{t('Name')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="attrMapName">{t('Name')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -327,8 +344,10 @@ class LdapSecuritySetting extends React.Component {
             </h3>
 
             <div className="row mb-5">
-              <strong htmlFor="groupSearchBase" className="col-xs-3 text-right">{t('security_setting.ldap.group_search_base_DN')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="groupSearchBase">{t('security_setting.ldap.group_search_base_DN')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -347,8 +366,10 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <strong htmlFor="groupSearchFilter" className="col-xs-3 text-right">{t('security_setting.ldap.group_search_filter')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="groupSearchFilter">{t('security_setting.ldap.group_search_filter')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -376,8 +397,10 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="groupDnProperty" className="col-xs-3 text-right">{t('security_setting.ldap.group_search_user_DN_property')}</label>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="groupDnProperty">{t('security_setting.ldap.group_search_user_DN_property')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -393,7 +416,7 @@ class LdapSecuritySetting extends React.Component {
               </div>
             </div>
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <button
                   type="button"
                   className="btn btn-primary"
@@ -402,7 +425,7 @@ class LdapSecuritySetting extends React.Component {
                 >
                   {t('Update')}
                 </button>
-                <button type="button" className="btn btn-default ml-2" onClick={this.openLdapAuthTestModal}>{t('security_setting.ldap.test_config')}</button>
+                <button type="button" className="btn btn-light ml-2" onClick={this.openLdapAuthTestModal}>{t('security_setting.ldap.test_config')}</button>
               </div>
             </div>
 

+ 57 - 70
src/client/js/components/Admin/Security/LocalSecuritySetting.jsx

@@ -75,24 +75,22 @@ class LocalSecuritySetting extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.Local.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="col-6 offset-3">
+            <div className="custom-control custom-switch custom-checkbox-success">
               <input
-                id="isLocalEnabled"
                 type="checkbox"
-                checked={adminGeneralSecurityContainer.state.isLocalEnabled}
-                onChange={() => { adminGeneralSecurityContainer.switchIsLocalEnabled() }}
+                className="custom-control-input"
+                id="isLocalEnabled"
+                checked={isLocalEnabled}
+                onChange={() => adminGeneralSecurityContainer.switchIsLocalEnabled()}
                 disabled={adminLocalSecurityContainer.state.useOnlyEnvVars}
               />
-              <label htmlFor="isLocalEnabled">
+              <label className="custom-control-label" htmlFor="isLocalEnabled">
                 {t('security_setting.Local.enable_local')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('local') && isLocalEnabled)
-            && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
@@ -101,74 +99,63 @@ class LocalSecuritySetting extends React.Component {
 
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
-            <div className="row mb-5">
-              <strong className="col-xs-3 text-right">{t('Register limitation')}</strong>
-              <div className="col-xs-9 text-left">
-                <div className="my-0 btn-group">
-                  <div className="dropdown">
-                    <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                      {registrationMode === 'Open' && <span className="pull-left">{t('security_setting.registration_mode.open')}</span>}
-                      {registrationMode === 'Restricted' && <span className="pull-left">{t('security_setting.registration_mode.restricted')}</span>}
-                      {registrationMode === 'Closed' && <span className="pull-left">{t('security_setting.registration_mode.closed')}</span>}
-                      <span className="bs-caret pull-right">
-                        <span className="caret" />
-                      </span>
-                    </button>
-                    {/* TODO adjust dropdown after BS4 */}
-                    <ul className="dropdown-menu" role="menu">
-                      <li
-                        key="Open"
-                        role="presentation"
-                        type="button"
-                        onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Open') }}
-                      >
-                        <a role="menuitem">{t('security_setting.registration_mode.open')}</a>
-                      </li>
-                      <li
-                        key="Restricted"
-                        role="presentation"
-                        type="button"
-                        onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Restricted') }}
-                      >
-                        <a role="menuitem">{t('security_setting.registration_mode.restricted')}</a>
-                      </li>
-                      <li
-                        key="Closed"
-                        role="presentation"
-                        type="button"
-                        onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Closed') }}
-                      >
-                        <a role="menuitem">{t('security_setting.registration_mode.closed')}</a>
-                      </li>
-                    </ul>
+            <div className="row">
+              <div className="col-3 text-right py-2">
+                <strong>{t('Register limitation')}</strong>
+              </div>
+              <div className="col-6">
+                <div className="dropdown">
+                  <button
+                    className="btn btn-light dropdown-toggle"
+                    type="button"
+                    id="dropdownMenuButton"
+                    data-toggle="dropdown"
+                    aria-haspopup="true"
+                    aria-expanded="true"
+                  >
+                    {registrationMode === 'Open' && t('security_setting.registration_mode.open')}
+                    {registrationMode === 'Restricted' && t('security_setting.registration_mode.restricted')}
+                    {registrationMode === 'Closed' && t('security_setting.registration_mode.closed')}
+                  </button>
+                  <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                    <a className="dropdown-item" onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Open') }}>
+                      {t('security_setting.registration_mode.open')}
+                    </a>
+                    <a className="dropdown-item" onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Restricted') }}>
+                      {t('security_setting.registration_mode.restricted')}
+                    </a>
+                    <a className="dropdown-item" onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Closed') }}>
+                      {t('security_setting.registration_mode.closed')}
+                    </a>
                   </div>
-                  <p className="help-block">
-                    {t('security_setting.Register limitation desc')}
-                  </p>
                 </div>
+
+                <p className="help-block small">
+                  {t('security_setting.Register limitation desc')}
+                </p>
               </div>
             </div>
-            <div className="row mb-5">
-              <strong className="col-xs-3 text-right" dangerouslySetInnerHTML={{ __html: t('The whitelist of registration permission E-mail address') }} />
-              <div className="col-xs-6">
-                <div>
-                  <textarea
-                    className="form-control"
-                    type="textarea"
-                    name="registrationWhiteList"
-                    defaultValue={adminLocalSecurityContainer.state.registrationWhiteList.join('\n')}
-                    onChange={e => adminLocalSecurityContainer.changeRegistrationWhiteList(e.target.value)}
-                  />
-                  <p className="help-block small">{t('security_setting.restrict_emails')}<br />{t('security_setting.for_instance')}
-                    <code>@growi.org</code>{t('security_setting.only_those')}<br />
-                    {t('security_setting.insert_single')}
-                  </p>
-                </div>
+            <div className="row">
+              <div className="col-3 text-right">
+                <strong dangerouslySetInnerHTML={{ __html: t('The whitelist of registration permission E-mail address') }} />
+              </div>
+              <div className="col-6">
+                <textarea
+                  className="form-control"
+                  type="textarea"
+                  name="registrationWhiteList"
+                  defaultValue={adminLocalSecurityContainer.state.registrationWhiteList.join('\n')}
+                  onChange={e => adminLocalSecurityContainer.changeRegistrationWhiteList(e.target.value)}
+                />
+                <p className="help-block small">{t('security_setting.restrict_emails')}<br />{t('security_setting.for_instance')}
+                  <code>@growi.org</code>{t('security_setting.only_those')}<br />
+                  {t('security_setting.insert_single')}
+                </p>
               </div>
             </div>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-6">
                 <button
                   type="button"
                   className="btn btn-primary"

+ 34 - 32
src/client/js/components/Admin/Security/OidcSecuritySetting.jsx

@@ -64,29 +64,27 @@ class OidcSecurityManagement extends React.Component {
         </h2>
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.OAuth.OIDC.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="offset-3 col-6">
+            <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isOidcEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isOidcEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsOidcEnabled() }}
               />
-              <label htmlFor="isOidcEnabled">
+              <label className="custom-control-label" htmlFor="isOidcEnabled">
                 {t('security_setting.OAuth.enable_oidc')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('oidc') && isOidcEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-          <div className="col-xs-6">
+          <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -112,8 +110,8 @@ class OidcSecurityManagement extends React.Component {
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
             <div className="row mb-5">
-              <label htmlFor="oidcProviderName" className="col-xs-3 text-right">{t('security_setting.providerName')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcProviderName" className="col-3 text-right py-2">{t('security_setting.providerName')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -125,8 +123,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcIssuerHost" className="col-xs-3 text-right">{t('security_setting.issuerHost')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcIssuerHost" className="col-3 text-right py-2">{t('security_setting.issuerHost')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -141,8 +139,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcClientId" className="col-xs-3 text-right">{t('security_setting.clientID')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcClientId" className="col-3 text-right py-2">{t('security_setting.clientID')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -157,8 +155,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcClientSecret" className="col-xs-3 text-right">{t('security_setting.client_secret')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcClientSecret" className="col-3 text-right py-2">{t('security_setting.client_secret')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -177,8 +175,8 @@ class OidcSecurityManagement extends React.Component {
             </h3>
 
             <div className="row mb-5">
-              <label htmlFor="oidcAttrMapId" className="col-xs-3 text-right">Identifier</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcAttrMapId" className="col-3 text-right py-2">Identifier</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -193,8 +191,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcAttrMapUserName" className="col-xs-3 text-right">{t('username')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcAttrMapUserName" className="col-3 text-right py-2">{t('username')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -209,8 +207,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcAttrMapName" className="col-xs-3 text-right">{t('Name')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcAttrMapName" className="col-3 text-right py-2">{t('Name')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -225,8 +223,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcAttrMapEmail" className="col-xs-3 text-right">{t('Email')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcAttrMapEmail" className="col-3 text-right py-2">{t('Email')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -241,8 +239,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-              <div className="col-xs-6">
+              <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -263,15 +261,17 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-3">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6">
+                <div className="custom-control custom-switch custom-checkbox-success">
                   <input
                     id="bindByUserName-oidc"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminOidcSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser}
                     onChange={() => { adminOidcSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByUserName-oidc"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical') }}
                   />
@@ -283,15 +283,17 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6">
+                <div className="custom-control custom-switch custom-checkbox-success">
                   <input
                     id="bindByEmail-oidc"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminOidcSecurityContainer.state.isSameEmailTreatedAsIdenticalUser || false}
                     onChange={() => { adminOidcSecurityContainer.switchIsSameEmailTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByEmail-oidc"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
                   />
@@ -303,7 +305,7 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <button
                   type="button"
                   className="btn btn-primary"

+ 18 - 16
src/client/js/components/Admin/Security/SamlSecuritySetting.jsx

@@ -90,30 +90,28 @@ class SamlSecurityManagement extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.SAML.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="col-6 offset-3">
+            <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isSamlEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isSamlEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsSamlEnabled() }}
                 disabled={adminSamlSecurityContainer.state.useOnlyEnvVars}
               />
-              <label htmlFor="isSamlEnabled">
+              <label className="custom-control-label" htmlFor="isSamlEnabled">
                 {t('security_setting.SAML.enable_saml')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('ldap') && isSamlEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-          <div className="col-xs-6">
+          <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -223,7 +221,7 @@ class SamlSecurityManagement extends React.Component {
                       defaultValue={adminSamlSecurityContainer.state.samlCert}
                       onChange={e => adminSamlSecurityContainer.changeSamlCert(e.target.value)}
                     />
-                    <p className="help-block">
+                    <p>
                       <small>
                         {t('security_setting.SAML.cert_detail')}
                       </small>
@@ -231,7 +229,7 @@ class SamlSecurityManagement extends React.Component {
                     <div>
                       <small>
                         e.g.
-                        <pre>{`-----BEGIN CERTIFICATE-----
+                        <pre className="well card">{`-----BEGIN CERTIFICATE-----
 MIICBzCCAXACCQD4US7+0A/b/zANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJK
 UDEOMAwGA1UECAwFVG9reW8xFTATBgNVBAoMDFdFU0VFSywgSW5jLjESMBAGA1UE
 ...
@@ -423,15 +421,17 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
             </h3>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6 text-left">
+                <div className="custom-control custom-switch custom-checkbox-success">
                   <input
                     id="bindByUserName-SAML"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminSamlSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
                     onChange={() => { adminSamlSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByUserName-SAML"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical') }}
                   />
@@ -443,15 +443,17 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6 text-left">
+                <div className="custom-control custom-switch custom-checkbox-success">
                   <input
                     id="bindByEmail-SAML"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminSamlSecurityContainer.state.isSameEmailTreatedAsIdenticalUser || false}
                     onChange={() => { adminSamlSecurityContainer.switchIsSameEmailTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByEmail-SAML"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
                   />
@@ -515,7 +517,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
             </table>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <button
                   type="button"
                   className="btn btn-primary"

+ 121 - 62
src/client/js/components/Admin/Security/SecurityManagement.jsx

@@ -1,6 +1,9 @@
 import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import {
+  TabContent, TabPane, Nav, NavItem, NavLink,
+} from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 
@@ -18,13 +21,27 @@ import FacebookSecuritySetting from './FacebookSecuritySetting';
 
 class SecurityManagement extends React.Component {
 
-  constructor(props) {
+  constructor() {
     super();
 
+    this.state = {
+      activeTab: 'passport-local',
+      // Prevent unnecessary rendering
+      activeComponents: new Set(['passport-local']),
+    };
+
+    this.toggleActiveTab = this.toggleActiveTab.bind(this);
+  }
+
+  toggleActiveTab(activeTab) {
+    this.setState({
+      activeTab, activeComponents: this.state.activeComponents.add(activeTab),
+    });
   }
 
   render() {
     const { t } = this.props;
+    const { activeTab, activeComponents } = this.state;
     return (
       <Fragment>
         <div>
@@ -41,69 +58,111 @@ class SecurityManagement extends React.Component {
           </div>
         </div>
 
-        {/* TODO GW-226 adapt BS4 */}
         <div className="auth-mechanism-configurations m-t-10">
           <h2 className="border-bottom">{t('security_setting.Authentication mechanism settings')}</h2>
-          <div className="passport-settings">
-            <ul className="nav nav-tabs" role="tablist">
-              <li className="active">
-                <a href="#passport-local" data-toggle="tab" role="tab"><i className="fa fa-users"></i> ID/Pass</a>
-              </li>
-              <li>
-                <a href="#passport-ldap" data-toggle="tab" role="tab"><i className="fa fa-sitemap"></i> LDAP</a>
-              </li>
-              <li>
-                <a href="#passport-saml" data-toggle="tab" role="tab"><i className="fa fa-key"></i> SAML</a>
-              </li>
-              <li>
-                <a href="#passport-oidc" data-toggle="tab" role="tab"><i className="fa fa-openid"></i> OIDC</a>
-              </li>
-              <li>
-                <a href="#passport-basic" data-toggle="tab" role="tab"><i className="fa fa-lock"></i> Basic</a>
-              </li>
-              <li>
-                <a href="#passport-google-oauth" data-toggle="tab" role="tab"><i className="fa fa-google"></i> Google</a>
-              </li>
-              <li>
-                <a href="#passport-github" data-toggle="tab" role="tab"><i className="fa fa-github"></i> GitHub</a>
-              </li>
-              <li>
-                <a href="#passport-twitter" data-toggle="tab" role="tab"><i className="fa fa-twitter"></i> Twitter</a>
-              </li>
-              <li className="tbd">
-                <a href="#passport-facebook" data-toggle="tab" role="tab"><i className="fa fa-facebook"></i> (TBD) Facebook</a>
-              </li>
-            </ul>
-            <div className="tab-content p-t-10">
-              <div id="passport-local" className="tab-pane active" role="tabpanel">
-                <LocalSecuritySetting />
-              </div>
-              <div id="passport-ldap" className="tab-pane" role="tabpanel">
-                <LdapSecuritySetting />
-              </div>
-              <div id="passport-saml" className="tab-pane" role="tabpanel">
-                <SamlSecuritySetting />
-              </div>
-              <div id="passport-oidc" className="tab-pane" role="tabpanel">
-                <OidcSecuritySetting />
-              </div>
-              <div id="passport-basic" className="tab-pane" role="tabpanel">
-                <BasicSecuritySetting />
-              </div>
-              <div id="passport-google-oauth" className="tab-pane" role="tabpanel">
-                <GoogleSecuritySetting />
-              </div>
-              <div id="passport-github" className="tab-pane" role="tabpanel">
-                <GitHubSecuritySetting />
-              </div>
-              <div id="passport-twitter" className="tab-pane" role="tabpanel">
-                <TwitterSecuritySetting />
-              </div>
-              <div id="passport-facebook" className="tab-pane" role="tabpanel">
-                <FacebookSecuritySetting />
-              </div>
-            </div>
-          </div>
+          <Nav tabs>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-local' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-local') }}
+              >
+                <i className="fa fa-users" /> ID/Pass
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-ldap' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-ldap') }}
+              >
+                <i className="fa fa-sitemap" /> LDAP
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-saml' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-saml') }}
+              >
+                <i className="fa fa-key" /> SAML
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-oidc' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-oidc') }}
+              >
+                <i className="fa fa-openid" /> OIDC
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-basic' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-basic') }}
+              >
+                <i className="fa fa-lock" /> BASIC
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-google' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-google') }}
+              >
+                <i className="fa fa-google" /> Google
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-github' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-github') }}
+              >
+                <i className="fa fa-github" /> GitHub
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-twitter' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-twitter') }}
+              >
+                <i className="fa fa-twitter" /> Twitter
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-facebook' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-facebook') }}
+              >
+                <i className="fa fa-facebook" /> (TBD) Facebook
+              </NavLink>
+            </NavItem>
+          </Nav>
+          <TabContent activeTab={activeTab} className="mt-2">
+            <TabPane tabId="passport-local">
+              {activeComponents.has('passport-local') && <LocalSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-ldap">
+              {activeComponents.has('passport-ldap') && <LdapSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-saml">
+              {activeComponents.has('passport-saml') && <SamlSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-oidc">
+              {activeComponents.has('passport-oidc') && <OidcSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-basic">
+              {activeComponents.has('passport-basic') && <BasicSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-google">
+              {activeComponents.has('passport-google') && <GoogleSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-github">
+              {activeComponents.has('passport-github') && <GitHubSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-twitter">
+              {activeComponents.has('passport-twitter') && <TwitterSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-facebook">
+              {activeComponents.has('passport-facebook') && <FacebookSecuritySetting />}
+            </TabPane>
+          </TabContent>
         </div>
       </Fragment>
     );

+ 114 - 143
src/client/js/components/Admin/Security/SecuritySetting.jsx

@@ -49,166 +49,137 @@ class SecuritySetting extends React.Component {
 
     return (
       <React.Fragment>
-        <fieldset>
-          <h2 className="alert-anchor border-bottom">
-            {t('security_settings')}
-          </h2>
-          {this.state.retrieveError != null && (
-            <div className="alert alert-danger">
-              <p>{t('Error occurred')} : {this.state.retrieveError}</p>
-            </div>
+        <h2 className="alert-anchor border-bottom">
+          {t('security_settings')}
+        </h2>
+        {this.state.retrieveError != null && (
+        <div className="alert alert-danger">
+          <p>{t('Error occurred')} : {this.state.retrieveError}</p>
+        </div>
           )}
-          <div className="row">
-            <strong className="col-xs-3 text-right"> {t('security_setting.Guest Users Access')} </strong>
-            <div className="col-xs-9 text-left">
-              <div className="my-0 btn-group">
-                <div className="dropdown">
-                  <button
-                    className={`btn btn-default dropdown-toggle w-100 ${adminGeneralSecurityContainer.isWikiModeForced && 'disabled'}`}
-                    type="button"
-                    data-toggle="dropdown"
-                    aria-haspopup="true"
-                    aria-expanded="false"
-                  >
-                    <span className="pull-left">
-                      {currentRestrictGuestMode === 'Deny' && t('security_setting.guest_mode.deny')}
-                      {currentRestrictGuestMode === 'Readonly' && t('security_setting.guest_mode.readonly')}
-                    </span>
-                    <span className="bs-caret pull-right">
-                      <span className="caret" />
-                    </span>
-                  </button>
-                  {/* TODO adjust dropdown after BS4 */}
-                  <ul className="dropdown-menu" role="menu">
-                    <li
-                      key="Deny"
-                      role="presentation"
-                      type="button"
-                      onClick={() => { adminGeneralSecurityContainer.changeRestrictGuestMode('Deny') }}
-                    >
-                      <a role="menuitem">{t('security_setting.guest_mode.deny')}</a>
-                    </li>
-                    <li
-                      key="Readonly"
-                      role="presentation"
-                      type="button"
-                      onClick={() => { adminGeneralSecurityContainer.changeRestrictGuestMode('Readonly') }}
-                    >
-                      <a role="menuitem">{t('security_setting.guest_mode.readonly')}</a>
-                    </li>
-                  </ul>
-                </div>
+        <div className="row mb-5">
+          <div className="col-3 text-right py-2">
+            <strong>{t('security_setting.Guest Users Access')}</strong>
+          </div>
+          <div className="col-6">
+            <div className="dropdown">
+              <button
+                className={`btn btn-light dropdown-toggle ${adminGeneralSecurityContainer.isWikiModeForced && 'disabled'}`}
+                type="button"
+                id="dropdownMenuButton"
+                data-toggle="dropdown"
+                aria-haspopup="true"
+                aria-expanded="true"
+              >
+                {currentRestrictGuestMode === 'Deny' && t('security_setting.guest_mode.deny')}
+                {currentRestrictGuestMode === 'Readonly' && t('security_setting.guest_mode.readonly')}
+              </button>
+              <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changeRestrictGuestMode('Deny') }}>
+                  {t('security_setting.guest_mode.deny')}
+                </a>
+                <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changeRestrictGuestMode('Readonly') }}>
+                  {t('security_setting.guest_mode.readonly')}
+                </a>
               </div>
             </div>
           </div>
-          {adminGeneralSecurityContainer.isWikiModeForced && (
-            <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <p className="alert alert-warning mt-2 text-left">
-                  <i className="icon-exclamation icon-fw">
-                  </i><b>FIXED</b><br />
-                  <b
-                    dangerouslySetInnerHTML={{
+        </div>
+        {adminGeneralSecurityContainer.isWikiModeForced && (
+        <div className="row mb-5">
+          <div className="col-xs-offset-3 col-xs-6 text-left">
+            <p className="alert alert-warning mt-2 text-left">
+              <i className="icon-exclamation icon-fw">
+              </i><b>FIXED</b><br />
+              <b
+                dangerouslySetInnerHTML={{
                     __html: t('security_setting.Fixed by env var',
                     { forcewikimode: 'FORCE_WIKI_MODE', wikimode: adminGeneralSecurityContainer.state.wikiMode }),
                     }}
-                  />
-                </p>
-              </div>
-            </div>
+              />
+            </p>
+          </div>
+        </div>
           )}
-          <div className="row mb-5">
-            <strong className="col-xs-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_1') }} />
-            <div className="col-xs-6 text-left">
-              <div className="checkbox checkbox-success">
-                <input
-                  id="isShowRestrictedByOwner"
-                  type="checkbox"
-                  checked={adminGeneralSecurityContainer.state.isShowRestrictedByOwner}
-                  onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByOwner() }}
-                />
-                <label htmlFor="isShowRestrictedByOwner">
-                  {t('security_setting.page_listing_1_desc')}
-                </label>
-              </div>
+        <div className="row mb-5">
+          <strong className="col-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_1') }} />
+          <div className="col-6">
+            <div className="custom-control custom-switch custom-checkbox-success">
+              <input
+                type="checkbox"
+                className="custom-control-input"
+                id="isShowRestrictedByOwner"
+                checked={adminGeneralSecurityContainer.state.isShowRestrictedByOwner}
+                onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByOwner() }}
+              />
+              <label className="custom-control-label" htmlFor="isShowRestrictedByOwner">
+                {t('security_setting.page_listing_1_desc')}
+              </label>
             </div>
           </div>
-
-          <div className="row mb-5">
-            <strong className="col-xs-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_2') }} />
-            <div className="col-xs-6 text-left">
-              <div className="checkbox checkbox-success">
-                <input
-                  id="isShowRestrictedByGroup"
-                  type="checkbox"
-                  checked={adminGeneralSecurityContainer.state.isShowRestrictedByGroup}
-                  onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByGroup() }}
-                />
-                <label htmlFor="isShowRestrictedByGroup">
-                  {t('security_setting.page_listing_2_desc')}
-                </label>
-              </div>
+        </div>
+
+        <div className="row mb-5">
+          <strong className="col-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_2') }} />
+          <div className="col-6">
+            <div className="custom-control custom-switch custom-checkbox-success">
+              <input
+                type="checkbox"
+                className="custom-control-input"
+                id="isShowRestrictedByGroup"
+                checked={adminGeneralSecurityContainer.state.isShowRestrictedByGroup}
+                onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByGroup() }}
+              />
+              <label className="custom-control-label" htmlFor="isShowRestrictedByGroup">
+                {t('security_setting.page_listing_2_desc')}
+              </label>
             </div>
           </div>
+        </div>
 
-          <div className="row mb-5">
-            <strong className="col-xs-3 text-right"> {t('security_setting.complete_deletion')} </strong>
-            <div className="col-xs-9 text-left">
-              <div className="my-0 btn-group">
-                <div className="dropdown">
-                  <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                    <span className="pull-left">
-                      {currentPageCompleteDeletionAuthority === 'anyOne' && t('security_setting.anyone')}
-                      {currentPageCompleteDeletionAuthority === 'adminOnly' && t('security_setting.admin_only')}
-                      {(currentPageCompleteDeletionAuthority === 'adminAndAuthor' || currentPageCompleteDeletionAuthority == null)
-                        && t('security_setting.admin_and_author')}
-                    </span>
-                    <span className="bs-caret pull-right">
-                      <span className="caret" />
-                    </span>
-                  </button>
-                  {/* TODO adjust dropdown after BS4 */}
-                  <ul className="dropdown-menu" role="menu">
-                    <li
-                      key="anyone"
-                      role="presentation"
-                      type="button"
-                      onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('anyOne') }}
-                    >
-                      <a role="menuitem">{t('security_setting.anyone')}</a>
-                    </li>
-                    <li
-                      key="admin_only"
-                      role="presentation"
-                      type="button"
-                      onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('adminOnly') }}
-                    >
-                      <a role="menuitem">{t('security_setting.admin_only')}</a>
-                    </li>
-                    <li
-                      key="admin_and_author"
-                      role="presentation"
-                      type="button"
-                      onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('adminAndAuthor') }}
-                    >
-                      <a role="menuitem">{t('security_setting.admin_and_author')}</a>
-                    </li>
-                  </ul>
-                </div>
-                <p className="help-block small">
-                  {t('security_setting.complete_deletion_explain')}
-                </p>
-              </div>
-            </div>
+        <div className="row mb-5">
+          <div className="col-3 text-right">
+            <strong>{t('security_setting.complete_deletion')}</strong>
           </div>
-          <div className="row my-3">
-            <div className="col-xs-offset-3 col-xs-5">
-              <button type="submit" className="btn btn-primary" disabled={this.state.retrieveError != null} onClick={this.putSecuritySetting}>
-                {t('Update')}
+          <div className="col-9">
+            <div className="dropdown">
+              <button
+                className="btn btn-light dropdown-toggle"
+                type="button"
+                id="dropdownMenuButton"
+                data-toggle="dropdown"
+                aria-haspopup="true"
+                aria-expanded="true"
+              >
+                {currentPageCompleteDeletionAuthority === 'anyOne' && t('security_setting.anyone')}
+                {currentPageCompleteDeletionAuthority === 'adminOnly' && t('security_setting.admin_only')}
+                {(currentPageCompleteDeletionAuthority === 'adminAndAuthor' || currentPageCompleteDeletionAuthority == null)
+                    && t('security_setting.admin_and_author')}
               </button>
+              <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('anyOne') }}>
+                  {t('security_setting.anyone')}
+                </a>
+                <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('adminOnly') }}>
+                  {t('security_setting.admin_only')}
+                </a>
+                <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('adminAndAuthor') }}>
+                  {t('security_setting.admin_and_author')}
+                </a>
+              </div>
+              <p className="help-block small">
+                {t('security_setting.complete_deletion_explain')}
+              </p>
             </div>
           </div>
-        </fieldset>
+        </div>
+        <div className="row my-3">
+          <div className="offset-3 col-5">
+            <button type="button" className="btn btn-primary" disabled={this.state.retrieveError != null} onClick={this.putSecuritySetting}>
+              {t('Update')}
+            </button>
+          </div>
+        </div>
       </React.Fragment>
     );
   }

+ 16 - 16
src/client/js/components/Admin/Security/TwitterSecuritySetting.jsx

@@ -69,29 +69,27 @@ class TwitterSecurityManagement extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.OAuth.Twitter.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="offset-3 col-6">
+            <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isTwitterEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isTwitterEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsTwitterOAuthEnabled() }}
               />
-              <label htmlFor="isTwitterEnabled">
+              <label className="custom-control-label" htmlFor="isTwitterEnabled">
                 {t('security_setting.OAuth.Twitter.enable_twitter')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('twitter') && isTwitterEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-          <div className="col-xs-6">
+          <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -118,8 +116,8 @@ class TwitterSecurityManagement extends React.Component {
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
             <div className="row mb-5">
-              <label htmlFor="TwitterConsumerId" className="col-xs-3 text-right">{t('security_setting.clientID')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="TwitterConsumerId" className="col-3 text-right py-2">{t('security_setting.clientID')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -134,8 +132,8 @@ class TwitterSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="TwitterConsumerSecret" className="col-xs-3 text-right">{t('security_setting.client_secret')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="TwitterConsumerSecret" className="col-3 text-right py-2">{t('security_setting.client_secret')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -150,15 +148,17 @@ class TwitterSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6">
+                <div className="custom-control custom-switch custom-checkbox-success">
                   <input
                     id="bindByUserNameTwitter"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminTwitterSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
                     onChange={() => { adminTwitterSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByUserNameTwitter"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
                   />
@@ -170,7 +170,7 @@ class TwitterSecurityManagement extends React.Component {
             </div>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <button
                   type="button"
                   className="btn btn-primary"

+ 1 - 1
src/client/js/components/Admin/UserGroup/UserGroupCreateForm.jsx

@@ -70,7 +70,7 @@ class UserGroupCreateForm extends React.Component {
         <p>
           {this.props.isAclEnabled
             ? (
-              <button type="button" data-toggle="collapse" className="btn btn-default" href="#createGroupForm">
+              <button type="button" data-toggle="collapse" className="btn btn-light" href="#createGroupForm">
                 {t('admin:user_group_management.create_group')}
               </button>
             )

+ 14 - 15
src/client/js/components/Admin/UserGroup/UserGroupDeleteModal.jsx

@@ -1,8 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
-
-import Modal from 'react-bootstrap/es/Modal';
+import {
+  Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
@@ -162,31 +163,29 @@ class UserGroupDeleteModal extends React.Component {
     const { t } = this.props;
 
     return (
-      <Modal show={this.props.isShow} onHide={this.onHide}>
-        <Modal.Header className="modal-header bg-danger" closeButton>
-          <Modal.Title>
-            <i className="icon icon-fire"></i> {t('admin:user_group_management.delete_modal.header')}
-          </Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
+      <Modal className="modal-md" isOpen={this.props.isShow} toggle={this.props.onHide}>
+        <ModalHeader tag="h4" toggle={this.props.onHide} className="modal-header bg-danger">
+          <i className="icon icon-fire"></i> {t('admin:user_group_management.delete_modal.header')}
+        </ModalHeader>
+        <ModalBody>
           <div>
             <span className="font-weight-bold">{t('admin:user_group_management.group_name')}</span> : &quot;{this.props.deleteUserGroup.name}&quot;
           </div>
           <div className="text-danger mt-5">
             {t('admin:user_group_management.delete_modal.desc')}
           </div>
-        </Modal.Body>
-        <Modal.Footer>
-          <form className="d-flex justify-content-between" onSubmit={this.handleSubmit}>
-            <div className="d-flex">
+        </ModalBody>
+        <ModalFooter>
+          <form className="d-flex justify-content-between w-100" onSubmit={this.handleSubmit}>
+            <div className="d-flex form-group mb-0">
               {this.renderPageActionSelector()}
               {this.renderGroupSelector()}
             </div>
-            <button type="submit" value="" className="btn btn-sm btn-danger" disabled={!this.validateForm()}>
+            <button type="submit" value="" className="btn btn-sm btn-danger text-nowrap" disabled={!this.validateForm()}>
               <i className="icon icon-fire"></i> {t('Delete')}
             </button>
           </form>
-        </Modal.Footer>
+        </ModalFooter>
       </Modal>
     );
   }

+ 16 - 17
src/client/js/components/Admin/UserGroup/UserGroupTable.jsx

@@ -69,7 +69,7 @@ class UserGroupTable extends React.Component {
                   <td>
                     <ul className="list-inline">
                       {this.state.userGroupRelations[group._id].map((user) => {
-                        return <li key={user._id} className="list-inline-item badge badge-primary">{this.xss.process(user.username)}</li>;
+                        return <li key={user._id} className="list-inline-item badge badge-pill badge-primary">{this.xss.process(user.username)}</li>;
                       })}
                     </ul>
                   </td>
@@ -78,23 +78,22 @@ class UserGroupTable extends React.Component {
                     ? (
                       <td>
                         <div className="btn-group admin-group-menu">
-                          <button type="button" className="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
-                            <i className="icon-settings"></i> <span className="caret"></span>
+                          <button
+                            type="button"
+                            id={`admin-group-menu-button-${group._id}`}
+                            className="btn btn-light btn-sm dropdown-toggle"
+                            data-toggle="dropdown"
+                          >
+                            <i className="icon-settings"></i>
                           </button>
-                          <ul className="dropdown-menu" role="menu">
-                            <li>
-                              <a href={`/admin/user-group-detail/${group._id}`}>
-                                <i className="icon-fw icon-note"></i> {t('Edit')}
-                              </a>
-                            </li>
-
-                            <li>
-                              <a role="button" onClick={this.onDelete} data-user-group-id={group._id}>
-                                <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
-                              </a>
-                            </li>
-
-                          </ul>
+                          <div className="dropdown-menu" role="menu" aria-labelledby={`admin-group-menu-button-${group._id}`}>
+                            <a className="dropdown-item" href={`/admin/user-group-detail/${group._id}`}>
+                              <i className="icon-fw icon-note"></i> {t('Edit')}
+                            </a>
+                            <a className="dropdown-item" role="button" onClick={this.onDelete} data-user-group-id={group._id}>
+                              <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
+                            </a>
+                          </div>
                         </div>
                       </td>
                     )

+ 3 - 3
src/client/js/components/Admin/UserGroupDetail/CheckBoxForSerchUserOption.jsx

@@ -8,15 +8,15 @@ class CheckBoxForSerchUserOption extends React.Component {
   render() {
     const { t, option } = this.props;
     return (
-      <div className="checkbox checkbox-info" key={`isAlso${option}Searched`}>
+      <div className="custom-control custom-checkbox custom-checkbox-info" key={`isAlso${option}Searched`}>
         <input
           type="checkbox"
           id={`isAlso${option}Searched`}
-          className="form-check-input"
+          className="custom-control-input"
           checked={this.props.checked}
           onChange={this.props.onChange}
         />
-        <label className="text-capitalize form-check-label ml-3" htmlFor={`isAlso${option}Searched`}>
+        <label className="text-capitalize custom-control-label ml-3" htmlFor={`isAlso${option}Searched`}>
           {t('admin:user_group_management.add_modal.enable_option', { option })}
         </label>
       </div>

+ 3 - 3
src/client/js/components/Admin/UserGroupDetail/RadioButtonForSerchUserOption.jsx

@@ -8,15 +8,15 @@ class RadioButtonForSerchUserOption extends React.Component {
   render() {
     const { t, searchType } = this.props;
     return (
-      <div className="radio" key={`${searchType}Match`}>
+      <div className="custom-control custom-radio custom-control-inline" key={`${searchType}Match`}>
         <input
           type="radio"
           id={`${searchType}Match`}
-          className="form-check-radio"
+          className="custom-control-input"
           checked={this.props.checked}
           onChange={this.props.onChange}
         />
-        <label className="text-capitalize form-check-label ml-3" htmlFor={`${searchType}Match`}>
+        <label className="text-capitalize custom-control-label ml-3" htmlFor={`${searchType}Match`}>
           {t(`admin:user_group_management.add_modal.${searchType}_match`)}
         </label>
       </div>

+ 4 - 4
src/client/js/components/Admin/UserGroupDetail/UserGroupDetailPage.jsx

@@ -16,17 +16,17 @@ class UserGroupDetailPage extends React.Component {
 
     return (
       <div>
-        <a href="/admin/user-groups" className="btn btn-default">
+        <a href="/admin/user-groups" className="btn btn-light">
           <i className="icon-fw ti-arrow-left" aria-hidden="true"></i>
           {t('admin:user_group_management.back_to_list')}
         </a>
-        <div className="m-t-20 form-box">
+        <div className="mt-4 form-box">
           <UserGroupEditForm />
         </div>
-        <legend className="m-t-20">{t('admin:user_group_management.user_list')}</legend>
+        <h2 className="admin-setting-header mt-4">{t('admin:user_group_management.user_list')}</h2>
         <UserGroupUserTable />
         <UserGroupUserModal />
-        <legend className="m-t-20">{t('Page')}</legend>
+        <h2 className="admin-setting-header mt-4">{t('Page')}</h2>
         <div className="page-list">
           <UserGroupPageList />
         </div>

+ 15 - 11
src/client/js/components/Admin/UserGroupDetail/UserGroupEditForm.jsx

@@ -61,18 +61,20 @@ class UserGroupEditForm extends React.Component {
     const { t, adminUserGroupDetailContainer } = this.props;
 
     return (
-      <form className="form-horizontal" onSubmit={this.handleSubmit}>
+      <form onSubmit={this.handleSubmit}>
         <fieldset>
-          <legend>{t('admin:user_group_management.basic_info')}</legend>
-          <div className="form-group">
-            <label htmlFor="name" className="col-sm-2 control-label">{t('Name')}</label>
-            <div className="col-sm-4">
+          <h2 className="admin-setting-header">{t('admin:user_group_management.basic_info')}</h2>
+          <div className="form-group row">
+            <label htmlFor="name" className="col-md-2 col-form-label">
+              {t('Name')}
+            </label>
+            <div className="col-md-4">
               <input className="form-control" type="text" name="name" value={this.state.name} onChange={this.changeUserGroupName} />
             </div>
           </div>
-          <div className="form-group">
-            <label className="col-sm-2 control-label">{t('Created')}</label>
-            <div className="col-sm-4">
+          <div className="form-group row">
+            <label className="col-md-2 col-form-label">{t('Created')}</label>
+            <div className="col-md-4">
               <input
                 type="text"
                 className="form-control"
@@ -81,9 +83,11 @@ class UserGroupEditForm extends React.Component {
               />
             </div>
           </div>
-          <div className="form-group">
-            <div className="col-sm-offset-2 col-sm-10">
-              <button type="submit" className="btn btn-primary" disabled={!this.validateForm()}>{t('Update')}</button>
+          <div className="form-group row">
+            <div className="offset-md-2 col-md-10">
+              <button type="submit" className="btn btn-primary" disabled={!this.validateForm()}>
+                {t('Update')}
+              </button>
             </div>
           </div>
         </fieldset>

+ 1 - 1
src/client/js/components/Admin/UserGroupDetail/UserGroupPageList.jsx

@@ -55,7 +55,7 @@ class UserGroupPageList extends React.Component {
 
     return (
       <Fragment>
-        <ul className="page-list-ul page-list-ul-flat">
+        <ul className="page-list-ul page-list-ul-flat mb-3">
           {this.state.currentPages.map((page) => { return <Page key={page._id} page={page} /> })}
         </ul>
         {adminUserGroupDetailContainer.state.relatedPages.length === 0 ? <p>{t('admin:user_group_management.no_pages')}</p> : null}

+ 3 - 3
src/client/js/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx

@@ -116,8 +116,8 @@ class UserGroupUserFormByInput extends React.Component {
     const inputProps = { autoComplete: 'off' };
 
     return (
-      <div className="row">
-        <div className="col-xs-8 pr-0">
+      <div className="form-group row">
+        <div className="col-8 pr-0">
           <AsyncTypeahead
             {...this.props}
             id="name-typeahead-asynctypeahead"
@@ -137,7 +137,7 @@ class UserGroupUserFormByInput extends React.Component {
             clearButton
           />
         </div>
-        <div className="col-xs-2 pl-0">
+        <div className="col-2 pl-0">
           <button
             type="button"
             className="btn btn-sm btn-success"

+ 11 - 9
src/client/js/components/Admin/UserGroupDetail/UserGroupUserModal.jsx

@@ -1,7 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
-import Modal from 'react-bootstrap/es/Modal';
+import {
+  Modal, ModalHeader, ModalBody,
+} from 'reactstrap';
 
 import UserGroupUserFormByInput from './UserGroupUserFormByInput';
 import { createSubscribedElement } from '../../UnstatedUtils';
@@ -16,17 +18,17 @@ class UserGroupUserModal extends React.Component {
     const { t, adminUserGroupDetailContainer } = this.props;
 
     return (
-      <Modal show={adminUserGroupDetailContainer.state.isUserGroupUserModalOpen} onHide={adminUserGroupDetailContainer.closeUserGroupUserModal}>
-        <Modal.Header closeButton>
-          <Modal.Title>{t('admin:user_group_management.add_modal.add_user')}</Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
+      <Modal isOpen={adminUserGroupDetailContainer.state.isUserGroupUserModalOpen} toggle={adminUserGroupDetailContainer.closeUserGroupUserModal}>
+        <ModalHeader tag="h4" toggle={adminUserGroupDetailContainer.closeUserGroupUserModal}>
+          {t('admin:user_group_management.add_modal.add_user') }
+        </ModalHeader>
+        <ModalBody>
           <div className="p-3">
             <UserGroupUserFormByInput />
           </div>
           <h2 className="border-bottom">{t('admin:user_group_management.add_modal.search_option')}</h2>
           <div className="row mt-4">
-            <div className="col-xs-6">
+            <div className="col-6">
               <div className="mb-5">
                 <CheckBoxForSerchUserOption
                   option="Mail"
@@ -42,7 +44,7 @@ class UserGroupUserModal extends React.Component {
                 />
               </div>
             </div>
-            <div className="col-xs-6">
+            <div className="col-6">
               <div className="mb-5">
                 <RadioButtonForSerchUserOption
                   searchType="forward"
@@ -66,7 +68,7 @@ class UserGroupUserModal extends React.Component {
               </div>
             </div>
           </div>
-        </Modal.Body>
+        </ModalBody>
       </Modal>
     );
   }

+ 19 - 11
src/client/js/components/Admin/UserGroupDetail/UserGroupUserTable.jsx

@@ -54,7 +54,7 @@ class UserGroupUserTable extends React.Component {
             return (
               <tr key={sRelation._id}>
                 <td>
-                  <UserPicture user={relatedUser} className="picture img-circle" />
+                  <UserPicture user={relatedUser} className="picture rounded-circle" />
                 </td>
                 <td>
                   <strong>{relatedUser.username}</strong>
@@ -64,16 +64,24 @@ class UserGroupUserTable extends React.Component {
                 <td>{relatedUser.lastLoginAt ? dateFnsFormat(new Date(relatedUser.lastLoginAt), 'yyyy-MM-dd HH:mm:ss') : ''}</td>
                 <td>
                   <div className="btn-group admin-user-menu">
-                    <button type="button" className="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
-                      <i className="icon-settings"></i> <span className="caret"></span>
+                    <button
+                      type="button"
+                      id={`admin-group-menu-button-${relatedUser._id}`}
+                      className="btn btn-light btn-sm dropdown-toggle"
+                      data-toggle="dropdown"
+                    >
+                      <i className="icon-settings"></i>
                     </button>
-                    <ul className="dropdown-menu" role="menu">
-                      <li>
-                        <a onClick={() => { return this.removeUser(relatedUser.username) }}>
-                          <i className="icon-fw icon-user-unfollow"></i> {t('admin:user_group_management.remove_from_group')}
-                        </a>
-                      </li>
-                    </ul>
+                    <div className="dropdown-menu" role="menu" aria-labelledby={`admin-group-menu-button-${relatedUser._id}`}>
+                      <a
+                        className="dropdown-item"
+                        onClick={() => {
+                          return this.removeUser(relatedUser.username);
+                        }}
+                      >
+                        <i className="icon-fw icon-user-unfollow"></i> {t('admin:user_group_management.remove_from_group')}
+                      </a>
+                    </div>
                   </div>
                 </td>
               </tr>
@@ -83,7 +91,7 @@ class UserGroupUserTable extends React.Component {
           <tr>
             <td></td>
             <td className="text-center">
-              <button className="btn btn-default" type="button" onClick={adminUserGroupDetailContainer.openUserGroupUserModal}>
+              <button className="btn btn-light" type="button" onClick={adminUserGroupDetailContainer.openUserGroupUserModal}>
                 <i className="ti-plus"></i>
               </button>
             </td>

+ 23 - 18
src/client/js/components/Admin/UserManagement.jsx

@@ -124,7 +124,7 @@ class UserManagement extends React.Component {
         {adminUsersContainer.state.userForPasswordResetModal && <PasswordResetModal />}
         <p>
           <InviteUserControl />
-          <a className="btn btn-default btn-outline ml-2" href="/admin/users/external-accounts">
+          <a className="btn text-dark btn-outline-secondary ml-2" href="/admin/users/external-accounts" role="button">
             <i className="icon-user-follow" aria-hidden="true"></i>
             {t('admin:user_management.external_account')}
           </a>
@@ -148,63 +148,68 @@ class UserManagement extends React.Component {
             </div>
 
             <div className="mx-5 form-inline">
-              <div className="checkbox checkbox-primary pl-0">
+              <div className="custom-control custom-checkbox custom-checkbox-primary mr-2">
                 <input
+                  className="custom-control-input"
                   type="checkbox"
                   id="c1"
                   checked={adminUsersContainer.isSelected('all')}
                   onClick={() => { this.handleClick('all') }}
                 />
-                <label htmlFor="c1">
-                  <span className="label label-primary d-inline-block vt mt-1">All</span>
+                <label className="custom-control-label" htmlFor="c1">
+                  <span className="badge badge-primary d-inline-block vt mt-1">All</span>
                 </label>
               </div>
 
-              <div className="checkbox checkbox-info">
+              <div className="custom-control custom-checkbox custom-checkbox-info mr-2">
                 <input
+                  className="custom-control-input"
                   type="checkbox"
                   id="c2"
                   checked={adminUsersContainer.isSelected('registered')}
                   onClick={() => { this.handleClick('registered') }}
                 />
-                <label htmlFor="c2">
-                  <span className="label label-info d-inline-block vt mt-1">Approval Pending</span>
+                <label className="custom-control-label" htmlFor="c2">
+                  <span className="badge badge-info d-inline-block vt mt-1">Approval Pending</span>
                 </label>
               </div>
 
-              <div className="checkbox checkbox-success">
+              <div className="custom-control custom-checkbox custom-checkbox-success mr-2">
                 <input
+                  className="custom-control-input"
                   type="checkbox"
                   id="c3"
                   checked={adminUsersContainer.isSelected('active')}
                   onClick={() => { this.handleClick('active') }}
                 />
-                <label htmlFor="c3">
-                  <span className="label label-success d-inline-block vt mt-1">Active</span>
+                <label className="custom-control-label" htmlFor="c3">
+                  <span className="badge badge-success d-inline-block vt mt-1">Active</span>
                 </label>
               </div>
 
-              <div className="checkbox checkbox-warning">
+              <div className="custom-control custom-checkbox custom-checkbox-warning mr-2">
                 <input
+                  className="custom-control-input"
                   type="checkbox"
                   id="c4"
                   checked={adminUsersContainer.isSelected('suspended')}
                   onClick={() => { this.handleClick('suspended') }}
                 />
-                <label htmlFor="c4">
-                  <span className="label label-warning d-inline-block vt mt-1">Suspended</span>
+                <label className="custom-control-label" htmlFor="c4">
+                  <span className="badge badge-warning d-inline-block vt mt-1">Suspended</span>
                 </label>
               </div>
 
-              <div className="checkbox checkbox-info">
+              <div className="custom-control custom-checkbox custom-checkbox-info">
                 <input
+                  className="custom-control-input"
                   type="checkbox"
                   id="c5"
                   checked={adminUsersContainer.isSelected('invited')}
                   onClick={() => { this.handleClick('invited') }}
                 />
-                <label htmlFor="c5">
-                  <span className="label label-info d-inline-block vt mt-1">Invited</span>
+                <label className="custom-control-label" htmlFor="c5">
+                  <span className="badge badge-info d-inline-block vt mt-1">Invited</span>
                 </label>
               </div>
             </div>
@@ -212,7 +217,7 @@ class UserManagement extends React.Component {
             <div>
               <button
                 type="button"
-                className="btn btn-default btn-outline btn-sm"
+                className="btn btn-outline-secondary btn-sm"
                 onClick={() => { this.resetButtonClickHandler() }}
               >
                 <span
@@ -224,7 +229,7 @@ class UserManagement extends React.Component {
             </div>
 
             <div className="ml-5">
-              {this.state.isNotifyCommentShow && <span className="text-warning">{t('admin:user_management.click_twice_same_checkbox')}</span>}
+              {this.state.isNotifyCommentShow && <span className="text-warning small">{t('admin:user_management.click_twice_same_checkbox')}</span>}
             </div>
 
           </div>

+ 1 - 1
src/client/js/components/Admin/Users/ExternalAccountTable.jsx

@@ -85,7 +85,7 @@ class ExternalAccountTable extends React.Component {
                         </span>
                       )
                       : (
-                        <span className="label label-warning">
+                        <span className="badge badge-warning">
                           {t('admin:user_management.unset')}
                         </span>
                       )

+ 1 - 1
src/client/js/components/Admin/Users/GiveAdminButton.jsx

@@ -31,7 +31,7 @@ class GiveAdminButton extends React.Component {
     const { t } = this.props;
 
     return (
-      <a role="button" className="px-4" onClick={() => { this.onClickGiveAdminBtn() }}>
+      <a className="dropdown-item" href="#" onClick={() => { this.onClickGiveAdminBtn() }}>
         <i className="icon-fw icon-user-following"></i> {t('admin:user_management.user_table.give_admin_access')}
       </a>
     );

+ 1 - 1
src/client/js/components/Admin/Users/InviteUserControl.jsx

@@ -14,7 +14,7 @@ class InviteUserControl extends React.Component {
 
     return (
       <Fragment>
-        <button type="button" className="btn btn-default" onClick={adminUsersContainer.toggleUserInviteModal}>
+        <button type="button" className="btn btn-light" onClick={adminUsersContainer.toggleUserInviteModal}>
           {t('admin:user_management.invite_users')}
         </button>
         <UserInviteModal />

+ 11 - 12
src/client/js/components/Admin/Users/PasswordResetModal.jsx

@@ -1,8 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
-
-import Modal from 'react-bootstrap/es/Modal';
+import {
+  Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
 
 import { toastError } from '../../../util/apiNotification';
 import { createSubscribedElement } from '../../UnstatedUtils';
@@ -85,18 +86,16 @@ class PasswordResetModal extends React.Component {
     const { t, adminUsersContainer } = this.props;
 
     return (
-      <Modal show={adminUsersContainer.state.isPasswordResetModalShown} onHide={adminUsersContainer.hidePasswordResetModal}>
-        <Modal.Header className="modal-header" closeButton>
-          <Modal.Title>
-            {t('admin:user_management.reset_password')}
-          </Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
+      <Modal isOpen={adminUsersContainer.state.isPasswordResetModalShown} toggle={adminUsersContainer.hidePasswordResetModal}>
+        <ModalHeader tag="h4" toggle={adminUsersContainer.hidePasswordResetModal} className="modal-header">
+          {t('admin:user_management.reset_password') }
+        </ModalHeader>
+        <ModalBody>
           {this.state.isPasswordResetDone ? this.renderModalBodyBeforeReset() : this.returnModalBodyAfterReset()}
-        </Modal.Body>
-        <Modal.Footer>
+        </ModalBody>
+        <ModalFooter>
           {this.state.isPasswordResetDone && this.returnModalFooter()}
-        </Modal.Footer>
+        </ModalFooter>
       </Modal>
     );
   }

+ 1 - 1
src/client/js/components/Admin/Users/RemoveAdminButton.jsx

@@ -32,7 +32,7 @@ class RemoveAdminButton extends React.Component {
     const { t } = this.props;
 
     return (
-      <a role="button" className="px-4" onClick={() => { this.onClickRemoveAdminBtn() }}>
+      <a className="dropdown-item" href="" onClick={() => { this.onClickRemoveAdminBtn() }}>
         <i className="icon-fw icon-user-unfollow"></i> {t('admin:user_management.user_table.remove_admin_access')}
       </a>
     );

+ 1 - 1
src/client/js/components/Admin/Users/StatusActivateButton.jsx

@@ -31,7 +31,7 @@ class StatusActivateButton extends React.Component {
     const { t } = this.props;
 
     return (
-      <a className="px-4" onClick={() => { this.onClickAcceptBtn() }}>
+      <a className="dropdown-item" href="" onClick={() => { this.onClickAcceptBtn() }}>
         <i className="icon-fw icon-user-following"></i> {t('admin:user_management.user_table.accept')}
       </a>
     );

+ 1 - 1
src/client/js/components/Admin/Users/StatusSuspendedButton.jsx

@@ -31,7 +31,7 @@ class StatusSuspendedButton extends React.Component {
     const { t } = this.props;
 
     return (
-      <a className="px-4" onClick={() => { this.onClickDeactiveBtn() }}>
+      <a className="dropdown-item" href="" onClick={() => { this.onClickDeactiveBtn() }}>
         <i className="icon-fw icon-ban"></i> {t('admin:user_management.user_table.deactivate_account')}
       </a>
     );

+ 34 - 28
src/client/js/components/Admin/Users/UserInviteModal.jsx

@@ -4,8 +4,10 @@ import { withTranslation } from 'react-i18next';
 
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 
-import Button from 'react-bootstrap/es/Button';
-import Modal from 'react-bootstrap/es/Modal';
+// import Button from 'react-bootstrap/es/Button';
+import {
+  Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
 
 import { toastSuccess, toastError } from '../../../util/apiNotification';
 
@@ -76,24 +78,29 @@ class UserInviteModal extends React.Component {
 
     return (
       <>
-        <div className="checkbox checkbox-success text-left" onChange={this.handleCheckBox} style={{ flex: 1 }}>
-          <input type="checkbox" id="sendEmail" className="form-check-input" name="sendEmail" defaultChecked={this.state.sendEmail} />
-          <label htmlFor="sendEmail">
+        <div className="col text-left custom-control custom-checkbox custom-checkbox-info text-left" onChange={this.handleCheckBox}>
+          <input type="checkbox" id="sendEmail" className="custom-control-input" name="sendEmail" defaultChecked={this.state.sendEmail} />
+          <label className="custom-control-label" htmlFor="sendEmail">
             {t('admin:user_management.invite_modal.invite_thru_email')}
           </label>
         </div>
         <div>
-          <Button bsStyle="danger" className="fcbtn btn btn-xs btn-danger btn-outline btn-rounded" onClick={this.onToggleModal}>
+          <button
+            type="button"
+            className="fcbtn btn btn-outline-light rounded-pill mr-2"
+            onClick={this.onToggleModal}
+          >
             Cancel
-          </Button>
-          <Button
-            bsStyle="primary"
-            className="fcbtn btn btn-primary btn-outline btn-rounded btn-1b"
+          </button>
+
+          <button
+            type="button"
+            className="fcbtn btn btn-outline-primary rounded-pill btn-1b"
             onClick={this.handleSubmit}
             disabled={!this.validEmail()}
           >
-            Done
-          </Button>
+            Invite
+          </button>
         </div>
       </>
     );
@@ -107,13 +114,13 @@ class UserInviteModal extends React.Component {
         <label className="mr-3 text-left text-danger" style={{ flex: 1 }}>
           {t('admin:user_management.invite_modal.send_temporary_password')}
         </label>
-        <Button
-          bsStyle="primary"
-          className="fcbtn btn btn-primary btn-outline btn-rounded"
+        <button
+          type="button"
+          className="fcbtn btn btn-primary"
           onClick={this.onToggleModal}
         >
           Close
-        </Button>
+        </button>
       </>
     );
   }
@@ -184,21 +191,20 @@ class UserInviteModal extends React.Component {
     const { t, adminUsersContainer } = this.props;
     const { invitedEmailList } = this.state;
 
+
     return (
-      <Modal show={adminUsersContainer.state.isUserInviteModalShown} onHide={this.onToggleModal}>
-        <Modal.Header className="modal-header" closeButton>
-          <Modal.Title>
-            {t('admin:user_management.invite_users')}
-          </Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
+      <Modal isOpen={adminUsersContainer.state.isUserInviteModalShown} toggle={this.onToggleModal}>
+        <ModalHeader tag="h4" toggle={this.onToggleModal} className="modal-header">
+          {t('admin:user_management.invite_users') }
+        </ModalHeader>
+        <ModalBody>
           {invitedEmailList == null ? this.renderModalBody()
-            : this.renderCreatedModalBody()}
-        </Modal.Body>
-        <Modal.Footer className="d-flex">
+           : this.renderCreatedModalBody()}
+        </ModalBody>
+        <ModalFooter className="d-flex">
           {invitedEmailList == null ? this.renderModalFooter()
-            : this.renderCreatedModalFooter()}
-        </Modal.Footer>
+           : this.renderCreatedModalFooter()}
+        </ModalFooter>
       </Modal>
     );
   }

+ 10 - 9
src/client/js/components/Admin/Users/UserMenu.jsx

@@ -33,10 +33,11 @@ class UserMenu extends React.Component {
 
     return (
       <Fragment>
+        <li className="dropdown-divider"></li>
         <li className="dropdown-header">{t('admin:user_management.user_table.edit_menu')}</li>
         <li>
-          <a role="button" onClick={this.onPasswordResetClicked}>
-            <i className="icon-fw icon-key"></i>{t('admin:user_management.user_table.reset_password')}
+          <a className="dropdown-item" href="" onClick={this.onPasswordResetClicked}>
+            <i className="icon-fw icon-key"></i>{ t('admin:user_management.reset_password') }
           </a>
         </li>
       </Fragment>
@@ -48,7 +49,7 @@ class UserMenu extends React.Component {
 
     return (
       <Fragment>
-        <li className="divider"></li>
+        <li className="dropdown-divider"></li>
         <li className="dropdown-header">{t('status')}</li>
         <li>
           {(user.status === 1 || user.status === 3) && <StatusActivateButton user={user} />}
@@ -64,7 +65,7 @@ class UserMenu extends React.Component {
 
     return (
       <Fragment>
-        <li className="divider pl-0"></li>
+        <li className="dropdown-divider pl-0"></li>
         <li className="dropdown-header">{t('admin:user_management.user_table.administrator_menu')}</li>
         <li>
           {user.admin === true && <RemoveAdminButton user={user} />}
@@ -79,15 +80,15 @@ class UserMenu extends React.Component {
 
     return (
       <Fragment>
-        <div className="btn-group admin-user-menu">
-          <button type="button" className="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown">
-            <i className="icon-settings"></i> <span className="caret"></span>
+        <div className="btn-group admin-user-menu" role="group">
+          <button id="userMenu" type="button" className="btn btn-light btn-sm dropdown-toggle" data-toggle="dropdown">
+            <i className="icon-settings"></i>
           </button>
-          <ul className="dropdown-menu" role="menu">
+          <div className="dropdown-menu" aria-labelledby="userMenu">
             {this.renderEditMenu()}
             {user.status !== 4 && this.renderStatusMenu()}
             {user.status === 2 && this.renderAdminMenu()}
-          </ul>
+          </div>
         </div>
       </Fragment>
     );

+ 1 - 1
src/client/js/components/Admin/Users/UserRemoveButton.jsx

@@ -32,7 +32,7 @@ class UserRemoveButton extends React.Component {
     const { t } = this.props;
 
     return (
-      <a role="button" className="px-4" onClick={() => { this.onClickDeleteBtn() }}>
+      <a className="dropdown-item" href="" onClick={() => { this.onClickDeleteBtn() }}>
         <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
       </a>
     );

+ 8 - 8
src/client/js/components/Admin/Users/UserTable.jsx

@@ -34,29 +34,29 @@ class UserTable extends React.Component {
 
     switch (userStatus) {
       case 1:
-        additionalClassName = 'label-info';
+        additionalClassName = 'badge-info';
         text = 'Approval Pending';
         break;
       case 2:
-        additionalClassName = 'label-success';
+        additionalClassName = 'badge-success';
         text = 'Active';
         break;
       case 3:
-        additionalClassName = 'label-warning';
+        additionalClassName = 'badge-warning';
         text = 'Suspended';
         break;
       case 4:
-        additionalClassName = 'label-danger';
+        additionalClassName = 'badge-danger';
         text = 'Deleted';
         break;
       case 5:
-        additionalClassName = 'label-info';
+        additionalClassName = 'badge-info';
         text = 'Invited';
         break;
     }
 
     return (
-      <span className={`label ${additionalClassName}`}>
+      <span className={`badge ${additionalClassName}`}>
         {text}
       </span>
     );
@@ -71,7 +71,7 @@ class UserTable extends React.Component {
     const { t } = this.props;
 
     if (isAdmin) {
-      return <span className="label label-inverse label-admin ml-2">{t('admin:user_management.user_table.administrator')}</span>;
+      return <span className="badge badge-primary ml-2">{t('admin:user_management.user_table.administrator')}</span>;
     }
   }
 
@@ -185,7 +185,7 @@ class UserTable extends React.Component {
               return (
                 <tr key={user._id}>
                   <td>
-                    <UserPicture user={user} className="picture img-circle" />
+                    <UserPicture user={user} className="picture rounded-circle" />
                   </td>
                   <td>{this.getUserStatusLabel(user.status)} {this.getUserAdminLabel(user.admin)}</td>
                   <td>

Некоторые файлы не были показаны из-за большого количества измененных файлов