From fa540c046d126449f77e46edd379bbc84e02d05d Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Sat, 26 Jun 2021 00:00:54 +0800
Subject: ...

---
 FrontEnd/src/views/admin/UserAdmin.tsx | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

(limited to 'FrontEnd/src/views/admin/UserAdmin.tsx')

diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx
index eb141520..2a123a76 100644
--- a/FrontEnd/src/views/admin/UserAdmin.tsx
+++ b/FrontEnd/src/views/admin/UserAdmin.tsx
@@ -1,6 +1,5 @@
 import React, { useState, useEffect } from "react";
 import classnames from "classnames";
-import { ListGroup, Row, Col, Spinner, Button } from "react-bootstrap";
 
 import OperationDialog, {
   OperationDialogBoolInput,
@@ -14,6 +13,7 @@ import {
   UserPermission,
 } from "@/http/user";
 import { Trans, useTranslation } from "react-i18next";
+import Button from "../common/button/Button";
 import TextButton from "../common/button/TextButton";
 
 interface DialogProps<TData = undefined, TReturn = undefined> {
@@ -372,20 +372,19 @@ const UserAdmin: React.FC<UserAdminProps> = () => {
 
     return (
       <>
-        <Row className="justify-content-end my-2">
-          <Col xs="auto">
+        <div className="row justify-content-end my-2">
+          <div className="col col-auto">
             <Button
-              variant="outline-success"
+              text="admin:create"
+              color="success"
               onClick={() =>
                 setDialog({
                   type: "create",
                 })
               }
-            >
-              {t("admin:create")}
-            </Button>
-          </Col>
-        </Row>
+            />
+          </div>
+        </div>
         {userComponents}
         {dialogNode}
       </>
-- 
cgit v1.2.3


From 29da3cef05ef138af2028ad44a27d706e9ae2df8 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Sat, 26 Jun 2021 18:46:58 +0800
Subject: ...

---
 FrontEnd/src/bootstrap-grid.css                    | 5002 --------------------
 FrontEnd/src/views/admin/UserAdmin.tsx             |   10 +-
 FrontEnd/src/views/center/TimelineCreateDialog.tsx |    4 +-
 FrontEnd/src/views/common/ConfirmDialog.tsx        |   42 -
 FrontEnd/src/views/common/LoadingButton.tsx        |   29 -
 FrontEnd/src/views/common/OperationDialog.tsx      |  471 --
 FrontEnd/src/views/common/ToggleIconButton.tsx     |   30 -
 FrontEnd/src/views/common/button/LoadingButton.tsx |   28 +
 FrontEnd/src/views/common/dailog/ConfirmDialog.tsx |   40 +
 FrontEnd/src/views/common/dailog/Dialog.css        |    0
 FrontEnd/src/views/common/dailog/Dialog.tsx        |   13 +
 .../src/views/common/dailog/OperationDialog.tsx    |  472 ++
 FrontEnd/src/views/login/index.tsx                 |    2 +-
 FrontEnd/src/views/settings/ChangeAvatarDialog.tsx |   63 +-
 .../src/views/settings/ChangeNicknameDialog.tsx    |    4 +-
 .../src/views/settings/ChangePasswordDialog.tsx    |    4 +-
 .../src/views/timeline-common/MarkdownPostEdit.tsx |    2 +-
 .../timeline-common/PostPropertyChangeDialog.tsx   |    4 +-
 .../src/views/timeline-common/TimelinePostEdit.tsx |    2 +-
 .../TimelinePropertyChangeDialog.tsx               |    4 +-
 .../src/views/timeline/TimelineDeleteDialog.tsx    |    4 +-
 21 files changed, 604 insertions(+), 5626 deletions(-)
 delete mode 100644 FrontEnd/src/bootstrap-grid.css
 delete mode 100644 FrontEnd/src/views/common/ConfirmDialog.tsx
 delete mode 100644 FrontEnd/src/views/common/LoadingButton.tsx
 delete mode 100644 FrontEnd/src/views/common/OperationDialog.tsx
 delete mode 100644 FrontEnd/src/views/common/ToggleIconButton.tsx
 create mode 100644 FrontEnd/src/views/common/button/LoadingButton.tsx
 create mode 100644 FrontEnd/src/views/common/dailog/ConfirmDialog.tsx
 create mode 100644 FrontEnd/src/views/common/dailog/Dialog.css
 create mode 100644 FrontEnd/src/views/common/dailog/Dialog.tsx
 create mode 100644 FrontEnd/src/views/common/dailog/OperationDialog.tsx

(limited to 'FrontEnd/src/views/admin/UserAdmin.tsx')

diff --git a/FrontEnd/src/bootstrap-grid.css b/FrontEnd/src/bootstrap-grid.css
deleted file mode 100644
index 7acbf004..00000000
--- a/FrontEnd/src/bootstrap-grid.css
+++ /dev/null
@@ -1,5002 +0,0 @@
-/*!
- * Bootstrap Grid v5.0.2 (https://getbootstrap.com/)
- * Copyright 2011-2021 The Bootstrap Authors
- * Copyright 2011-2021 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
- */
-.container,
-.container-fluid,
-.container-xxl,
-.container-xl,
-.container-lg,
-.container-md,
-.container-sm {
-  width: 100%;
-  padding-right: var(--bs-gutter-x, 0.75rem);
-  padding-left: var(--bs-gutter-x, 0.75rem);
-  margin-right: auto;
-  margin-left: auto;
-}
-
-@media (min-width: 576px) {
-  .container-sm, .container {
-    max-width: 540px;
-  }
-}
-@media (min-width: 768px) {
-  .container-md, .container-sm, .container {
-    max-width: 720px;
-  }
-}
-@media (min-width: 992px) {
-  .container-lg, .container-md, .container-sm, .container {
-    max-width: 960px;
-  }
-}
-@media (min-width: 1200px) {
-  .container-xl, .container-lg, .container-md, .container-sm, .container {
-    max-width: 1140px;
-  }
-}
-@media (min-width: 1400px) {
-  .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {
-    max-width: 1320px;
-  }
-}
-.row {
-  --bs-gutter-x: 1.5rem;
-  --bs-gutter-y: 0;
-  display: flex;
-  flex-wrap: wrap;
-  margin-top: calc(var(--bs-gutter-y) * -1);
-  margin-right: calc(var(--bs-gutter-x) * -.5);
-  margin-left: calc(var(--bs-gutter-x) * -.5);
-}
-.row > * {
-  box-sizing: border-box;
-  flex-shrink: 0;
-  width: 100%;
-  max-width: 100%;
-  padding-right: calc(var(--bs-gutter-x) * .5);
-  padding-left: calc(var(--bs-gutter-x) * .5);
-  margin-top: var(--bs-gutter-y);
-}
-
-.col {
-  flex: 1 0 0%;
-}
-
-.row-cols-auto > * {
-  flex: 0 0 auto;
-  width: auto;
-}
-
-.row-cols-1 > * {
-  flex: 0 0 auto;
-  width: 100%;
-}
-
-.row-cols-2 > * {
-  flex: 0 0 auto;
-  width: 50%;
-}
-
-.row-cols-3 > * {
-  flex: 0 0 auto;
-  width: 33.3333333333%;
-}
-
-.row-cols-4 > * {
-  flex: 0 0 auto;
-  width: 25%;
-}
-
-.row-cols-5 > * {
-  flex: 0 0 auto;
-  width: 20%;
-}
-
-.row-cols-6 > * {
-  flex: 0 0 auto;
-  width: 16.6666666667%;
-}
-
-@media (min-width: 576px) {
-  .col-sm {
-    flex: 1 0 0%;
-  }
-
-  .row-cols-sm-auto > * {
-    flex: 0 0 auto;
-    width: auto;
-  }
-
-  .row-cols-sm-1 > * {
-    flex: 0 0 auto;
-    width: 100%;
-  }
-
-  .row-cols-sm-2 > * {
-    flex: 0 0 auto;
-    width: 50%;
-  }
-
-  .row-cols-sm-3 > * {
-    flex: 0 0 auto;
-    width: 33.3333333333%;
-  }
-
-  .row-cols-sm-4 > * {
-    flex: 0 0 auto;
-    width: 25%;
-  }
-
-  .row-cols-sm-5 > * {
-    flex: 0 0 auto;
-    width: 20%;
-  }
-
-  .row-cols-sm-6 > * {
-    flex: 0 0 auto;
-    width: 16.6666666667%;
-  }
-}
-@media (min-width: 768px) {
-  .col-md {
-    flex: 1 0 0%;
-  }
-
-  .row-cols-md-auto > * {
-    flex: 0 0 auto;
-    width: auto;
-  }
-
-  .row-cols-md-1 > * {
-    flex: 0 0 auto;
-    width: 100%;
-  }
-
-  .row-cols-md-2 > * {
-    flex: 0 0 auto;
-    width: 50%;
-  }
-
-  .row-cols-md-3 > * {
-    flex: 0 0 auto;
-    width: 33.3333333333%;
-  }
-
-  .row-cols-md-4 > * {
-    flex: 0 0 auto;
-    width: 25%;
-  }
-
-  .row-cols-md-5 > * {
-    flex: 0 0 auto;
-    width: 20%;
-  }
-
-  .row-cols-md-6 > * {
-    flex: 0 0 auto;
-    width: 16.6666666667%;
-  }
-}
-@media (min-width: 992px) {
-  .col-lg {
-    flex: 1 0 0%;
-  }
-
-  .row-cols-lg-auto > * {
-    flex: 0 0 auto;
-    width: auto;
-  }
-
-  .row-cols-lg-1 > * {
-    flex: 0 0 auto;
-    width: 100%;
-  }
-
-  .row-cols-lg-2 > * {
-    flex: 0 0 auto;
-    width: 50%;
-  }
-
-  .row-cols-lg-3 > * {
-    flex: 0 0 auto;
-    width: 33.3333333333%;
-  }
-
-  .row-cols-lg-4 > * {
-    flex: 0 0 auto;
-    width: 25%;
-  }
-
-  .row-cols-lg-5 > * {
-    flex: 0 0 auto;
-    width: 20%;
-  }
-
-  .row-cols-lg-6 > * {
-    flex: 0 0 auto;
-    width: 16.6666666667%;
-  }
-}
-@media (min-width: 1200px) {
-  .col-xl {
-    flex: 1 0 0%;
-  }
-
-  .row-cols-xl-auto > * {
-    flex: 0 0 auto;
-    width: auto;
-  }
-
-  .row-cols-xl-1 > * {
-    flex: 0 0 auto;
-    width: 100%;
-  }
-
-  .row-cols-xl-2 > * {
-    flex: 0 0 auto;
-    width: 50%;
-  }
-
-  .row-cols-xl-3 > * {
-    flex: 0 0 auto;
-    width: 33.3333333333%;
-  }
-
-  .row-cols-xl-4 > * {
-    flex: 0 0 auto;
-    width: 25%;
-  }
-
-  .row-cols-xl-5 > * {
-    flex: 0 0 auto;
-    width: 20%;
-  }
-
-  .row-cols-xl-6 > * {
-    flex: 0 0 auto;
-    width: 16.6666666667%;
-  }
-}
-@media (min-width: 1400px) {
-  .col-xxl {
-    flex: 1 0 0%;
-  }
-
-  .row-cols-xxl-auto > * {
-    flex: 0 0 auto;
-    width: auto;
-  }
-
-  .row-cols-xxl-1 > * {
-    flex: 0 0 auto;
-    width: 100%;
-  }
-
-  .row-cols-xxl-2 > * {
-    flex: 0 0 auto;
-    width: 50%;
-  }
-
-  .row-cols-xxl-3 > * {
-    flex: 0 0 auto;
-    width: 33.3333333333%;
-  }
-
-  .row-cols-xxl-4 > * {
-    flex: 0 0 auto;
-    width: 25%;
-  }
-
-  .row-cols-xxl-5 > * {
-    flex: 0 0 auto;
-    width: 20%;
-  }
-
-  .row-cols-xxl-6 > * {
-    flex: 0 0 auto;
-    width: 16.6666666667%;
-  }
-}
-.col-auto {
-  flex: 0 0 auto;
-  width: auto;
-}
-
-.col-1 {
-  flex: 0 0 auto;
-  width: 8.33333333%;
-}
-
-.col-2 {
-  flex: 0 0 auto;
-  width: 16.66666667%;
-}
-
-.col-3 {
-  flex: 0 0 auto;
-  width: 25%;
-}
-
-.col-4 {
-  flex: 0 0 auto;
-  width: 33.33333333%;
-}
-
-.col-5 {
-  flex: 0 0 auto;
-  width: 41.66666667%;
-}
-
-.col-6 {
-  flex: 0 0 auto;
-  width: 50%;
-}
-
-.col-7 {
-  flex: 0 0 auto;
-  width: 58.33333333%;
-}
-
-.col-8 {
-  flex: 0 0 auto;
-  width: 66.66666667%;
-}
-
-.col-9 {
-  flex: 0 0 auto;
-  width: 75%;
-}
-
-.col-10 {
-  flex: 0 0 auto;
-  width: 83.33333333%;
-}
-
-.col-11 {
-  flex: 0 0 auto;
-  width: 91.66666667%;
-}
-
-.col-12 {
-  flex: 0 0 auto;
-  width: 100%;
-}
-
-.offset-1 {
-  margin-left: 8.33333333%;
-}
-
-.offset-2 {
-  margin-left: 16.66666667%;
-}
-
-.offset-3 {
-  margin-left: 25%;
-}
-
-.offset-4 {
-  margin-left: 33.33333333%;
-}
-
-.offset-5 {
-  margin-left: 41.66666667%;
-}
-
-.offset-6 {
-  margin-left: 50%;
-}
-
-.offset-7 {
-  margin-left: 58.33333333%;
-}
-
-.offset-8 {
-  margin-left: 66.66666667%;
-}
-
-.offset-9 {
-  margin-left: 75%;
-}
-
-.offset-10 {
-  margin-left: 83.33333333%;
-}
-
-.offset-11 {
-  margin-left: 91.66666667%;
-}
-
-.g-0,
-.gx-0 {
-  --bs-gutter-x: 0;
-}
-
-.g-0,
-.gy-0 {
-  --bs-gutter-y: 0;
-}
-
-.g-1,
-.gx-1 {
-  --bs-gutter-x: 0.25rem;
-}
-
-.g-1,
-.gy-1 {
-  --bs-gutter-y: 0.25rem;
-}
-
-.g-2,
-.gx-2 {
-  --bs-gutter-x: 0.5rem;
-}
-
-.g-2,
-.gy-2 {
-  --bs-gutter-y: 0.5rem;
-}
-
-.g-3,
-.gx-3 {
-  --bs-gutter-x: 1rem;
-}
-
-.g-3,
-.gy-3 {
-  --bs-gutter-y: 1rem;
-}
-
-.g-4,
-.gx-4 {
-  --bs-gutter-x: 1.5rem;
-}
-
-.g-4,
-.gy-4 {
-  --bs-gutter-y: 1.5rem;
-}
-
-.g-5,
-.gx-5 {
-  --bs-gutter-x: 3rem;
-}
-
-.g-5,
-.gy-5 {
-  --bs-gutter-y: 3rem;
-}
-
-@media (min-width: 576px) {
-  .col-sm-auto {
-    flex: 0 0 auto;
-    width: auto;
-  }
-
-  .col-sm-1 {
-    flex: 0 0 auto;
-    width: 8.33333333%;
-  }
-
-  .col-sm-2 {
-    flex: 0 0 auto;
-    width: 16.66666667%;
-  }
-
-  .col-sm-3 {
-    flex: 0 0 auto;
-    width: 25%;
-  }
-
-  .col-sm-4 {
-    flex: 0 0 auto;
-    width: 33.33333333%;
-  }
-
-  .col-sm-5 {
-    flex: 0 0 auto;
-    width: 41.66666667%;
-  }
-
-  .col-sm-6 {
-    flex: 0 0 auto;
-    width: 50%;
-  }
-
-  .col-sm-7 {
-    flex: 0 0 auto;
-    width: 58.33333333%;
-  }
-
-  .col-sm-8 {
-    flex: 0 0 auto;
-    width: 66.66666667%;
-  }
-
-  .col-sm-9 {
-    flex: 0 0 auto;
-    width: 75%;
-  }
-
-  .col-sm-10 {
-    flex: 0 0 auto;
-    width: 83.33333333%;
-  }
-
-  .col-sm-11 {
-    flex: 0 0 auto;
-    width: 91.66666667%;
-  }
-
-  .col-sm-12 {
-    flex: 0 0 auto;
-    width: 100%;
-  }
-
-  .offset-sm-0 {
-    margin-left: 0;
-  }
-
-  .offset-sm-1 {
-    margin-left: 8.33333333%;
-  }
-
-  .offset-sm-2 {
-    margin-left: 16.66666667%;
-  }
-
-  .offset-sm-3 {
-    margin-left: 25%;
-  }
-
-  .offset-sm-4 {
-    margin-left: 33.33333333%;
-  }
-
-  .offset-sm-5 {
-    margin-left: 41.66666667%;
-  }
-
-  .offset-sm-6 {
-    margin-left: 50%;
-  }
-
-  .offset-sm-7 {
-    margin-left: 58.33333333%;
-  }
-
-  .offset-sm-8 {
-    margin-left: 66.66666667%;
-  }
-
-  .offset-sm-9 {
-    margin-left: 75%;
-  }
-
-  .offset-sm-10 {
-    margin-left: 83.33333333%;
-  }
-
-  .offset-sm-11 {
-    margin-left: 91.66666667%;
-  }
-
-  .g-sm-0,
-.gx-sm-0 {
-    --bs-gutter-x: 0;
-  }
-
-  .g-sm-0,
-.gy-sm-0 {
-    --bs-gutter-y: 0;
-  }
-
-  .g-sm-1,
-.gx-sm-1 {
-    --bs-gutter-x: 0.25rem;
-  }
-
-  .g-sm-1,
-.gy-sm-1 {
-    --bs-gutter-y: 0.25rem;
-  }
-
-  .g-sm-2,
-.gx-sm-2 {
-    --bs-gutter-x: 0.5rem;
-  }
-
-  .g-sm-2,
-.gy-sm-2 {
-    --bs-gutter-y: 0.5rem;
-  }
-
-  .g-sm-3,
-.gx-sm-3 {
-    --bs-gutter-x: 1rem;
-  }
-
-  .g-sm-3,
-.gy-sm-3 {
-    --bs-gutter-y: 1rem;
-  }
-
-  .g-sm-4,
-.gx-sm-4 {
-    --bs-gutter-x: 1.5rem;
-  }
-
-  .g-sm-4,
-.gy-sm-4 {
-    --bs-gutter-y: 1.5rem;
-  }
-
-  .g-sm-5,
-.gx-sm-5 {
-    --bs-gutter-x: 3rem;
-  }
-
-  .g-sm-5,
-.gy-sm-5 {
-    --bs-gutter-y: 3rem;
-  }
-}
-@media (min-width: 768px) {
-  .col-md-auto {
-    flex: 0 0 auto;
-    width: auto;
-  }
-
-  .col-md-1 {
-    flex: 0 0 auto;
-    width: 8.33333333%;
-  }
-
-  .col-md-2 {
-    flex: 0 0 auto;
-    width: 16.66666667%;
-  }
-
-  .col-md-3 {
-    flex: 0 0 auto;
-    width: 25%;
-  }
-
-  .col-md-4 {
-    flex: 0 0 auto;
-    width: 33.33333333%;
-  }
-
-  .col-md-5 {
-    flex: 0 0 auto;
-    width: 41.66666667%;
-  }
-
-  .col-md-6 {
-    flex: 0 0 auto;
-    width: 50%;
-  }
-
-  .col-md-7 {
-    flex: 0 0 auto;
-    width: 58.33333333%;
-  }
-
-  .col-md-8 {
-    flex: 0 0 auto;
-    width: 66.66666667%;
-  }
-
-  .col-md-9 {
-    flex: 0 0 auto;
-    width: 75%;
-  }
-
-  .col-md-10 {
-    flex: 0 0 auto;
-    width: 83.33333333%;
-  }
-
-  .col-md-11 {
-    flex: 0 0 auto;
-    width: 91.66666667%;
-  }
-
-  .col-md-12 {
-    flex: 0 0 auto;
-    width: 100%;
-  }
-
-  .offset-md-0 {
-    margin-left: 0;
-  }
-
-  .offset-md-1 {
-    margin-left: 8.33333333%;
-  }
-
-  .offset-md-2 {
-    margin-left: 16.66666667%;
-  }
-
-  .offset-md-3 {
-    margin-left: 25%;
-  }
-
-  .offset-md-4 {
-    margin-left: 33.33333333%;
-  }
-
-  .offset-md-5 {
-    margin-left: 41.66666667%;
-  }
-
-  .offset-md-6 {
-    margin-left: 50%;
-  }
-
-  .offset-md-7 {
-    margin-left: 58.33333333%;
-  }
-
-  .offset-md-8 {
-    margin-left: 66.66666667%;
-  }
-
-  .offset-md-9 {
-    margin-left: 75%;
-  }
-
-  .offset-md-10 {
-    margin-left: 83.33333333%;
-  }
-
-  .offset-md-11 {
-    margin-left: 91.66666667%;
-  }
-
-  .g-md-0,
-.gx-md-0 {
-    --bs-gutter-x: 0;
-  }
-
-  .g-md-0,
-.gy-md-0 {
-    --bs-gutter-y: 0;
-  }
-
-  .g-md-1,
-.gx-md-1 {
-    --bs-gutter-x: 0.25rem;
-  }
-
-  .g-md-1,
-.gy-md-1 {
-    --bs-gutter-y: 0.25rem;
-  }
-
-  .g-md-2,
-.gx-md-2 {
-    --bs-gutter-x: 0.5rem;
-  }
-
-  .g-md-2,
-.gy-md-2 {
-    --bs-gutter-y: 0.5rem;
-  }
-
-  .g-md-3,
-.gx-md-3 {
-    --bs-gutter-x: 1rem;
-  }
-
-  .g-md-3,
-.gy-md-3 {
-    --bs-gutter-y: 1rem;
-  }
-
-  .g-md-4,
-.gx-md-4 {
-    --bs-gutter-x: 1.5rem;
-  }
-
-  .g-md-4,
-.gy-md-4 {
-    --bs-gutter-y: 1.5rem;
-  }
-
-  .g-md-5,
-.gx-md-5 {
-    --bs-gutter-x: 3rem;
-  }
-
-  .g-md-5,
-.gy-md-5 {
-    --bs-gutter-y: 3rem;
-  }
-}
-@media (min-width: 992px) {
-  .col-lg-auto {
-    flex: 0 0 auto;
-    width: auto;
-  }
-
-  .col-lg-1 {
-    flex: 0 0 auto;
-    width: 8.33333333%;
-  }
-
-  .col-lg-2 {
-    flex: 0 0 auto;
-    width: 16.66666667%;
-  }
-
-  .col-lg-3 {
-    flex: 0 0 auto;
-    width: 25%;
-  }
-
-  .col-lg-4 {
-    flex: 0 0 auto;
-    width: 33.33333333%;
-  }
-
-  .col-lg-5 {
-    flex: 0 0 auto;
-    width: 41.66666667%;
-  }
-
-  .col-lg-6 {
-    flex: 0 0 auto;
-    width: 50%;
-  }
-
-  .col-lg-7 {
-    flex: 0 0 auto;
-    width: 58.33333333%;
-  }
-
-  .col-lg-8 {
-    flex: 0 0 auto;
-    width: 66.66666667%;
-  }
-
-  .col-lg-9 {
-    flex: 0 0 auto;
-    width: 75%;
-  }
-
-  .col-lg-10 {
-    flex: 0 0 auto;
-    width: 83.33333333%;
-  }
-
-  .col-lg-11 {
-    flex: 0 0 auto;
-    width: 91.66666667%;
-  }
-
-  .col-lg-12 {
-    flex: 0 0 auto;
-    width: 100%;
-  }
-
-  .offset-lg-0 {
-    margin-left: 0;
-  }
-
-  .offset-lg-1 {
-    margin-left: 8.33333333%;
-  }
-
-  .offset-lg-2 {
-    margin-left: 16.66666667%;
-  }
-
-  .offset-lg-3 {
-    margin-left: 25%;
-  }
-
-  .offset-lg-4 {
-    margin-left: 33.33333333%;
-  }
-
-  .offset-lg-5 {
-    margin-left: 41.66666667%;
-  }
-
-  .offset-lg-6 {
-    margin-left: 50%;
-  }
-
-  .offset-lg-7 {
-    margin-left: 58.33333333%;
-  }
-
-  .offset-lg-8 {
-    margin-left: 66.66666667%;
-  }
-
-  .offset-lg-9 {
-    margin-left: 75%;
-  }
-
-  .offset-lg-10 {
-    margin-left: 83.33333333%;
-  }
-
-  .offset-lg-11 {
-    margin-left: 91.66666667%;
-  }
-
-  .g-lg-0,
-.gx-lg-0 {
-    --bs-gutter-x: 0;
-  }
-
-  .g-lg-0,
-.gy-lg-0 {
-    --bs-gutter-y: 0;
-  }
-
-  .g-lg-1,
-.gx-lg-1 {
-    --bs-gutter-x: 0.25rem;
-  }
-
-  .g-lg-1,
-.gy-lg-1 {
-    --bs-gutter-y: 0.25rem;
-  }
-
-  .g-lg-2,
-.gx-lg-2 {
-    --bs-gutter-x: 0.5rem;
-  }
-
-  .g-lg-2,
-.gy-lg-2 {
-    --bs-gutter-y: 0.5rem;
-  }
-
-  .g-lg-3,
-.gx-lg-3 {
-    --bs-gutter-x: 1rem;
-  }
-
-  .g-lg-3,
-.gy-lg-3 {
-    --bs-gutter-y: 1rem;
-  }
-
-  .g-lg-4,
-.gx-lg-4 {
-    --bs-gutter-x: 1.5rem;
-  }
-
-  .g-lg-4,
-.gy-lg-4 {
-    --bs-gutter-y: 1.5rem;
-  }
-
-  .g-lg-5,
-.gx-lg-5 {
-    --bs-gutter-x: 3rem;
-  }
-
-  .g-lg-5,
-.gy-lg-5 {
-    --bs-gutter-y: 3rem;
-  }
-}
-@media (min-width: 1200px) {
-  .col-xl-auto {
-    flex: 0 0 auto;
-    width: auto;
-  }
-
-  .col-xl-1 {
-    flex: 0 0 auto;
-    width: 8.33333333%;
-  }
-
-  .col-xl-2 {
-    flex: 0 0 auto;
-    width: 16.66666667%;
-  }
-
-  .col-xl-3 {
-    flex: 0 0 auto;
-    width: 25%;
-  }
-
-  .col-xl-4 {
-    flex: 0 0 auto;
-    width: 33.33333333%;
-  }
-
-  .col-xl-5 {
-    flex: 0 0 auto;
-    width: 41.66666667%;
-  }
-
-  .col-xl-6 {
-    flex: 0 0 auto;
-    width: 50%;
-  }
-
-  .col-xl-7 {
-    flex: 0 0 auto;
-    width: 58.33333333%;
-  }
-
-  .col-xl-8 {
-    flex: 0 0 auto;
-    width: 66.66666667%;
-  }
-
-  .col-xl-9 {
-    flex: 0 0 auto;
-    width: 75%;
-  }
-
-  .col-xl-10 {
-    flex: 0 0 auto;
-    width: 83.33333333%;
-  }
-
-  .col-xl-11 {
-    flex: 0 0 auto;
-    width: 91.66666667%;
-  }
-
-  .col-xl-12 {
-    flex: 0 0 auto;
-    width: 100%;
-  }
-
-  .offset-xl-0 {
-    margin-left: 0;
-  }
-
-  .offset-xl-1 {
-    margin-left: 8.33333333%;
-  }
-
-  .offset-xl-2 {
-    margin-left: 16.66666667%;
-  }
-
-  .offset-xl-3 {
-    margin-left: 25%;
-  }
-
-  .offset-xl-4 {
-    margin-left: 33.33333333%;
-  }
-
-  .offset-xl-5 {
-    margin-left: 41.66666667%;
-  }
-
-  .offset-xl-6 {
-    margin-left: 50%;
-  }
-
-  .offset-xl-7 {
-    margin-left: 58.33333333%;
-  }
-
-  .offset-xl-8 {
-    margin-left: 66.66666667%;
-  }
-
-  .offset-xl-9 {
-    margin-left: 75%;
-  }
-
-  .offset-xl-10 {
-    margin-left: 83.33333333%;
-  }
-
-  .offset-xl-11 {
-    margin-left: 91.66666667%;
-  }
-
-  .g-xl-0,
-.gx-xl-0 {
-    --bs-gutter-x: 0;
-  }
-
-  .g-xl-0,
-.gy-xl-0 {
-    --bs-gutter-y: 0;
-  }
-
-  .g-xl-1,
-.gx-xl-1 {
-    --bs-gutter-x: 0.25rem;
-  }
-
-  .g-xl-1,
-.gy-xl-1 {
-    --bs-gutter-y: 0.25rem;
-  }
-
-  .g-xl-2,
-.gx-xl-2 {
-    --bs-gutter-x: 0.5rem;
-  }
-
-  .g-xl-2,
-.gy-xl-2 {
-    --bs-gutter-y: 0.5rem;
-  }
-
-  .g-xl-3,
-.gx-xl-3 {
-    --bs-gutter-x: 1rem;
-  }
-
-  .g-xl-3,
-.gy-xl-3 {
-    --bs-gutter-y: 1rem;
-  }
-
-  .g-xl-4,
-.gx-xl-4 {
-    --bs-gutter-x: 1.5rem;
-  }
-
-  .g-xl-4,
-.gy-xl-4 {
-    --bs-gutter-y: 1.5rem;
-  }
-
-  .g-xl-5,
-.gx-xl-5 {
-    --bs-gutter-x: 3rem;
-  }
-
-  .g-xl-5,
-.gy-xl-5 {
-    --bs-gutter-y: 3rem;
-  }
-}
-@media (min-width: 1400px) {
-  .col-xxl-auto {
-    flex: 0 0 auto;
-    width: auto;
-  }
-
-  .col-xxl-1 {
-    flex: 0 0 auto;
-    width: 8.33333333%;
-  }
-
-  .col-xxl-2 {
-    flex: 0 0 auto;
-    width: 16.66666667%;
-  }
-
-  .col-xxl-3 {
-    flex: 0 0 auto;
-    width: 25%;
-  }
-
-  .col-xxl-4 {
-    flex: 0 0 auto;
-    width: 33.33333333%;
-  }
-
-  .col-xxl-5 {
-    flex: 0 0 auto;
-    width: 41.66666667%;
-  }
-
-  .col-xxl-6 {
-    flex: 0 0 auto;
-    width: 50%;
-  }
-
-  .col-xxl-7 {
-    flex: 0 0 auto;
-    width: 58.33333333%;
-  }
-
-  .col-xxl-8 {
-    flex: 0 0 auto;
-    width: 66.66666667%;
-  }
-
-  .col-xxl-9 {
-    flex: 0 0 auto;
-    width: 75%;
-  }
-
-  .col-xxl-10 {
-    flex: 0 0 auto;
-    width: 83.33333333%;
-  }
-
-  .col-xxl-11 {
-    flex: 0 0 auto;
-    width: 91.66666667%;
-  }
-
-  .col-xxl-12 {
-    flex: 0 0 auto;
-    width: 100%;
-  }
-
-  .offset-xxl-0 {
-    margin-left: 0;
-  }
-
-  .offset-xxl-1 {
-    margin-left: 8.33333333%;
-  }
-
-  .offset-xxl-2 {
-    margin-left: 16.66666667%;
-  }
-
-  .offset-xxl-3 {
-    margin-left: 25%;
-  }
-
-  .offset-xxl-4 {
-    margin-left: 33.33333333%;
-  }
-
-  .offset-xxl-5 {
-    margin-left: 41.66666667%;
-  }
-
-  .offset-xxl-6 {
-    margin-left: 50%;
-  }
-
-  .offset-xxl-7 {
-    margin-left: 58.33333333%;
-  }
-
-  .offset-xxl-8 {
-    margin-left: 66.66666667%;
-  }
-
-  .offset-xxl-9 {
-    margin-left: 75%;
-  }
-
-  .offset-xxl-10 {
-    margin-left: 83.33333333%;
-  }
-
-  .offset-xxl-11 {
-    margin-left: 91.66666667%;
-  }
-
-  .g-xxl-0,
-.gx-xxl-0 {
-    --bs-gutter-x: 0;
-  }
-
-  .g-xxl-0,
-.gy-xxl-0 {
-    --bs-gutter-y: 0;
-  }
-
-  .g-xxl-1,
-.gx-xxl-1 {
-    --bs-gutter-x: 0.25rem;
-  }
-
-  .g-xxl-1,
-.gy-xxl-1 {
-    --bs-gutter-y: 0.25rem;
-  }
-
-  .g-xxl-2,
-.gx-xxl-2 {
-    --bs-gutter-x: 0.5rem;
-  }
-
-  .g-xxl-2,
-.gy-xxl-2 {
-    --bs-gutter-y: 0.5rem;
-  }
-
-  .g-xxl-3,
-.gx-xxl-3 {
-    --bs-gutter-x: 1rem;
-  }
-
-  .g-xxl-3,
-.gy-xxl-3 {
-    --bs-gutter-y: 1rem;
-  }
-
-  .g-xxl-4,
-.gx-xxl-4 {
-    --bs-gutter-x: 1.5rem;
-  }
-
-  .g-xxl-4,
-.gy-xxl-4 {
-    --bs-gutter-y: 1.5rem;
-  }
-
-  .g-xxl-5,
-.gx-xxl-5 {
-    --bs-gutter-x: 3rem;
-  }
-
-  .g-xxl-5,
-.gy-xxl-5 {
-    --bs-gutter-y: 3rem;
-  }
-}
-.d-inline {
-  display: inline !important;
-}
-
-.d-inline-block {
-  display: inline-block !important;
-}
-
-.d-block {
-  display: block !important;
-}
-
-.d-grid {
-  display: grid !important;
-}
-
-.d-table {
-  display: table !important;
-}
-
-.d-table-row {
-  display: table-row !important;
-}
-
-.d-table-cell {
-  display: table-cell !important;
-}
-
-.d-flex {
-  display: flex !important;
-}
-
-.d-inline-flex {
-  display: inline-flex !important;
-}
-
-.d-none {
-  display: none !important;
-}
-
-.flex-fill {
-  flex: 1 1 auto !important;
-}
-
-.flex-row {
-  flex-direction: row !important;
-}
-
-.flex-column {
-  flex-direction: column !important;
-}
-
-.flex-row-reverse {
-  flex-direction: row-reverse !important;
-}
-
-.flex-column-reverse {
-  flex-direction: column-reverse !important;
-}
-
-.flex-grow-0 {
-  flex-grow: 0 !important;
-}
-
-.flex-grow-1 {
-  flex-grow: 1 !important;
-}
-
-.flex-shrink-0 {
-  flex-shrink: 0 !important;
-}
-
-.flex-shrink-1 {
-  flex-shrink: 1 !important;
-}
-
-.flex-wrap {
-  flex-wrap: wrap !important;
-}
-
-.flex-nowrap {
-  flex-wrap: nowrap !important;
-}
-
-.flex-wrap-reverse {
-  flex-wrap: wrap-reverse !important;
-}
-
-.justify-content-start {
-  justify-content: flex-start !important;
-}
-
-.justify-content-end {
-  justify-content: flex-end !important;
-}
-
-.justify-content-center {
-  justify-content: center !important;
-}
-
-.justify-content-between {
-  justify-content: space-between !important;
-}
-
-.justify-content-around {
-  justify-content: space-around !important;
-}
-
-.justify-content-evenly {
-  justify-content: space-evenly !important;
-}
-
-.align-items-start {
-  align-items: flex-start !important;
-}
-
-.align-items-end {
-  align-items: flex-end !important;
-}
-
-.align-items-center {
-  align-items: center !important;
-}
-
-.align-items-baseline {
-  align-items: baseline !important;
-}
-
-.align-items-stretch {
-  align-items: stretch !important;
-}
-
-.align-content-start {
-  align-content: flex-start !important;
-}
-
-.align-content-end {
-  align-content: flex-end !important;
-}
-
-.align-content-center {
-  align-content: center !important;
-}
-
-.align-content-between {
-  align-content: space-between !important;
-}
-
-.align-content-around {
-  align-content: space-around !important;
-}
-
-.align-content-stretch {
-  align-content: stretch !important;
-}
-
-.align-self-auto {
-  align-self: auto !important;
-}
-
-.align-self-start {
-  align-self: flex-start !important;
-}
-
-.align-self-end {
-  align-self: flex-end !important;
-}
-
-.align-self-center {
-  align-self: center !important;
-}
-
-.align-self-baseline {
-  align-self: baseline !important;
-}
-
-.align-self-stretch {
-  align-self: stretch !important;
-}
-
-.order-first {
-  order: -1 !important;
-}
-
-.order-0 {
-  order: 0 !important;
-}
-
-.order-1 {
-  order: 1 !important;
-}
-
-.order-2 {
-  order: 2 !important;
-}
-
-.order-3 {
-  order: 3 !important;
-}
-
-.order-4 {
-  order: 4 !important;
-}
-
-.order-5 {
-  order: 5 !important;
-}
-
-.order-last {
-  order: 6 !important;
-}
-
-.m-0 {
-  margin: 0 !important;
-}
-
-.m-1 {
-  margin: 0.25rem !important;
-}
-
-.m-2 {
-  margin: 0.5rem !important;
-}
-
-.m-3 {
-  margin: 1rem !important;
-}
-
-.m-4 {
-  margin: 1.5rem !important;
-}
-
-.m-5 {
-  margin: 3rem !important;
-}
-
-.m-auto {
-  margin: auto !important;
-}
-
-.mx-0 {
-  margin-right: 0 !important;
-  margin-left: 0 !important;
-}
-
-.mx-1 {
-  margin-right: 0.25rem !important;
-  margin-left: 0.25rem !important;
-}
-
-.mx-2 {
-  margin-right: 0.5rem !important;
-  margin-left: 0.5rem !important;
-}
-
-.mx-3 {
-  margin-right: 1rem !important;
-  margin-left: 1rem !important;
-}
-
-.mx-4 {
-  margin-right: 1.5rem !important;
-  margin-left: 1.5rem !important;
-}
-
-.mx-5 {
-  margin-right: 3rem !important;
-  margin-left: 3rem !important;
-}
-
-.mx-auto {
-  margin-right: auto !important;
-  margin-left: auto !important;
-}
-
-.my-0 {
-  margin-top: 0 !important;
-  margin-bottom: 0 !important;
-}
-
-.my-1 {
-  margin-top: 0.25rem !important;
-  margin-bottom: 0.25rem !important;
-}
-
-.my-2 {
-  margin-top: 0.5rem !important;
-  margin-bottom: 0.5rem !important;
-}
-
-.my-3 {
-  margin-top: 1rem !important;
-  margin-bottom: 1rem !important;
-}
-
-.my-4 {
-  margin-top: 1.5rem !important;
-  margin-bottom: 1.5rem !important;
-}
-
-.my-5 {
-  margin-top: 3rem !important;
-  margin-bottom: 3rem !important;
-}
-
-.my-auto {
-  margin-top: auto !important;
-  margin-bottom: auto !important;
-}
-
-.mt-0 {
-  margin-top: 0 !important;
-}
-
-.mt-1 {
-  margin-top: 0.25rem !important;
-}
-
-.mt-2 {
-  margin-top: 0.5rem !important;
-}
-
-.mt-3 {
-  margin-top: 1rem !important;
-}
-
-.mt-4 {
-  margin-top: 1.5rem !important;
-}
-
-.mt-5 {
-  margin-top: 3rem !important;
-}
-
-.mt-auto {
-  margin-top: auto !important;
-}
-
-.me-0 {
-  margin-right: 0 !important;
-}
-
-.me-1 {
-  margin-right: 0.25rem !important;
-}
-
-.me-2 {
-  margin-right: 0.5rem !important;
-}
-
-.me-3 {
-  margin-right: 1rem !important;
-}
-
-.me-4 {
-  margin-right: 1.5rem !important;
-}
-
-.me-5 {
-  margin-right: 3rem !important;
-}
-
-.me-auto {
-  margin-right: auto !important;
-}
-
-.mb-0 {
-  margin-bottom: 0 !important;
-}
-
-.mb-1 {
-  margin-bottom: 0.25rem !important;
-}
-
-.mb-2 {
-  margin-bottom: 0.5rem !important;
-}
-
-.mb-3 {
-  margin-bottom: 1rem !important;
-}
-
-.mb-4 {
-  margin-bottom: 1.5rem !important;
-}
-
-.mb-5 {
-  margin-bottom: 3rem !important;
-}
-
-.mb-auto {
-  margin-bottom: auto !important;
-}
-
-.ms-0 {
-  margin-left: 0 !important;
-}
-
-.ms-1 {
-  margin-left: 0.25rem !important;
-}
-
-.ms-2 {
-  margin-left: 0.5rem !important;
-}
-
-.ms-3 {
-  margin-left: 1rem !important;
-}
-
-.ms-4 {
-  margin-left: 1.5rem !important;
-}
-
-.ms-5 {
-  margin-left: 3rem !important;
-}
-
-.ms-auto {
-  margin-left: auto !important;
-}
-
-.p-0 {
-  padding: 0 !important;
-}
-
-.p-1 {
-  padding: 0.25rem !important;
-}
-
-.p-2 {
-  padding: 0.5rem !important;
-}
-
-.p-3 {
-  padding: 1rem !important;
-}
-
-.p-4 {
-  padding: 1.5rem !important;
-}
-
-.p-5 {
-  padding: 3rem !important;
-}
-
-.px-0 {
-  padding-right: 0 !important;
-  padding-left: 0 !important;
-}
-
-.px-1 {
-  padding-right: 0.25rem !important;
-  padding-left: 0.25rem !important;
-}
-
-.px-2 {
-  padding-right: 0.5rem !important;
-  padding-left: 0.5rem !important;
-}
-
-.px-3 {
-  padding-right: 1rem !important;
-  padding-left: 1rem !important;
-}
-
-.px-4 {
-  padding-right: 1.5rem !important;
-  padding-left: 1.5rem !important;
-}
-
-.px-5 {
-  padding-right: 3rem !important;
-  padding-left: 3rem !important;
-}
-
-.py-0 {
-  padding-top: 0 !important;
-  padding-bottom: 0 !important;
-}
-
-.py-1 {
-  padding-top: 0.25rem !important;
-  padding-bottom: 0.25rem !important;
-}
-
-.py-2 {
-  padding-top: 0.5rem !important;
-  padding-bottom: 0.5rem !important;
-}
-
-.py-3 {
-  padding-top: 1rem !important;
-  padding-bottom: 1rem !important;
-}
-
-.py-4 {
-  padding-top: 1.5rem !important;
-  padding-bottom: 1.5rem !important;
-}
-
-.py-5 {
-  padding-top: 3rem !important;
-  padding-bottom: 3rem !important;
-}
-
-.pt-0 {
-  padding-top: 0 !important;
-}
-
-.pt-1 {
-  padding-top: 0.25rem !important;
-}
-
-.pt-2 {
-  padding-top: 0.5rem !important;
-}
-
-.pt-3 {
-  padding-top: 1rem !important;
-}
-
-.pt-4 {
-  padding-top: 1.5rem !important;
-}
-
-.pt-5 {
-  padding-top: 3rem !important;
-}
-
-.pe-0 {
-  padding-right: 0 !important;
-}
-
-.pe-1 {
-  padding-right: 0.25rem !important;
-}
-
-.pe-2 {
-  padding-right: 0.5rem !important;
-}
-
-.pe-3 {
-  padding-right: 1rem !important;
-}
-
-.pe-4 {
-  padding-right: 1.5rem !important;
-}
-
-.pe-5 {
-  padding-right: 3rem !important;
-}
-
-.pb-0 {
-  padding-bottom: 0 !important;
-}
-
-.pb-1 {
-  padding-bottom: 0.25rem !important;
-}
-
-.pb-2 {
-  padding-bottom: 0.5rem !important;
-}
-
-.pb-3 {
-  padding-bottom: 1rem !important;
-}
-
-.pb-4 {
-  padding-bottom: 1.5rem !important;
-}
-
-.pb-5 {
-  padding-bottom: 3rem !important;
-}
-
-.ps-0 {
-  padding-left: 0 !important;
-}
-
-.ps-1 {
-  padding-left: 0.25rem !important;
-}
-
-.ps-2 {
-  padding-left: 0.5rem !important;
-}
-
-.ps-3 {
-  padding-left: 1rem !important;
-}
-
-.ps-4 {
-  padding-left: 1.5rem !important;
-}
-
-.ps-5 {
-  padding-left: 3rem !important;
-}
-
-@media (min-width: 576px) {
-  .d-sm-inline {
-    display: inline !important;
-  }
-
-  .d-sm-inline-block {
-    display: inline-block !important;
-  }
-
-  .d-sm-block {
-    display: block !important;
-  }
-
-  .d-sm-grid {
-    display: grid !important;
-  }
-
-  .d-sm-table {
-    display: table !important;
-  }
-
-  .d-sm-table-row {
-    display: table-row !important;
-  }
-
-  .d-sm-table-cell {
-    display: table-cell !important;
-  }
-
-  .d-sm-flex {
-    display: flex !important;
-  }
-
-  .d-sm-inline-flex {
-    display: inline-flex !important;
-  }
-
-  .d-sm-none {
-    display: none !important;
-  }
-
-  .flex-sm-fill {
-    flex: 1 1 auto !important;
-  }
-
-  .flex-sm-row {
-    flex-direction: row !important;
-  }
-
-  .flex-sm-column {
-    flex-direction: column !important;
-  }
-
-  .flex-sm-row-reverse {
-    flex-direction: row-reverse !important;
-  }
-
-  .flex-sm-column-reverse {
-    flex-direction: column-reverse !important;
-  }
-
-  .flex-sm-grow-0 {
-    flex-grow: 0 !important;
-  }
-
-  .flex-sm-grow-1 {
-    flex-grow: 1 !important;
-  }
-
-  .flex-sm-shrink-0 {
-    flex-shrink: 0 !important;
-  }
-
-  .flex-sm-shrink-1 {
-    flex-shrink: 1 !important;
-  }
-
-  .flex-sm-wrap {
-    flex-wrap: wrap !important;
-  }
-
-  .flex-sm-nowrap {
-    flex-wrap: nowrap !important;
-  }
-
-  .flex-sm-wrap-reverse {
-    flex-wrap: wrap-reverse !important;
-  }
-
-  .justify-content-sm-start {
-    justify-content: flex-start !important;
-  }
-
-  .justify-content-sm-end {
-    justify-content: flex-end !important;
-  }
-
-  .justify-content-sm-center {
-    justify-content: center !important;
-  }
-
-  .justify-content-sm-between {
-    justify-content: space-between !important;
-  }
-
-  .justify-content-sm-around {
-    justify-content: space-around !important;
-  }
-
-  .justify-content-sm-evenly {
-    justify-content: space-evenly !important;
-  }
-
-  .align-items-sm-start {
-    align-items: flex-start !important;
-  }
-
-  .align-items-sm-end {
-    align-items: flex-end !important;
-  }
-
-  .align-items-sm-center {
-    align-items: center !important;
-  }
-
-  .align-items-sm-baseline {
-    align-items: baseline !important;
-  }
-
-  .align-items-sm-stretch {
-    align-items: stretch !important;
-  }
-
-  .align-content-sm-start {
-    align-content: flex-start !important;
-  }
-
-  .align-content-sm-end {
-    align-content: flex-end !important;
-  }
-
-  .align-content-sm-center {
-    align-content: center !important;
-  }
-
-  .align-content-sm-between {
-    align-content: space-between !important;
-  }
-
-  .align-content-sm-around {
-    align-content: space-around !important;
-  }
-
-  .align-content-sm-stretch {
-    align-content: stretch !important;
-  }
-
-  .align-self-sm-auto {
-    align-self: auto !important;
-  }
-
-  .align-self-sm-start {
-    align-self: flex-start !important;
-  }
-
-  .align-self-sm-end {
-    align-self: flex-end !important;
-  }
-
-  .align-self-sm-center {
-    align-self: center !important;
-  }
-
-  .align-self-sm-baseline {
-    align-self: baseline !important;
-  }
-
-  .align-self-sm-stretch {
-    align-self: stretch !important;
-  }
-
-  .order-sm-first {
-    order: -1 !important;
-  }
-
-  .order-sm-0 {
-    order: 0 !important;
-  }
-
-  .order-sm-1 {
-    order: 1 !important;
-  }
-
-  .order-sm-2 {
-    order: 2 !important;
-  }
-
-  .order-sm-3 {
-    order: 3 !important;
-  }
-
-  .order-sm-4 {
-    order: 4 !important;
-  }
-
-  .order-sm-5 {
-    order: 5 !important;
-  }
-
-  .order-sm-last {
-    order: 6 !important;
-  }
-
-  .m-sm-0 {
-    margin: 0 !important;
-  }
-
-  .m-sm-1 {
-    margin: 0.25rem !important;
-  }
-
-  .m-sm-2 {
-    margin: 0.5rem !important;
-  }
-
-  .m-sm-3 {
-    margin: 1rem !important;
-  }
-
-  .m-sm-4 {
-    margin: 1.5rem !important;
-  }
-
-  .m-sm-5 {
-    margin: 3rem !important;
-  }
-
-  .m-sm-auto {
-    margin: auto !important;
-  }
-
-  .mx-sm-0 {
-    margin-right: 0 !important;
-    margin-left: 0 !important;
-  }
-
-  .mx-sm-1 {
-    margin-right: 0.25rem !important;
-    margin-left: 0.25rem !important;
-  }
-
-  .mx-sm-2 {
-    margin-right: 0.5rem !important;
-    margin-left: 0.5rem !important;
-  }
-
-  .mx-sm-3 {
-    margin-right: 1rem !important;
-    margin-left: 1rem !important;
-  }
-
-  .mx-sm-4 {
-    margin-right: 1.5rem !important;
-    margin-left: 1.5rem !important;
-  }
-
-  .mx-sm-5 {
-    margin-right: 3rem !important;
-    margin-left: 3rem !important;
-  }
-
-  .mx-sm-auto {
-    margin-right: auto !important;
-    margin-left: auto !important;
-  }
-
-  .my-sm-0 {
-    margin-top: 0 !important;
-    margin-bottom: 0 !important;
-  }
-
-  .my-sm-1 {
-    margin-top: 0.25rem !important;
-    margin-bottom: 0.25rem !important;
-  }
-
-  .my-sm-2 {
-    margin-top: 0.5rem !important;
-    margin-bottom: 0.5rem !important;
-  }
-
-  .my-sm-3 {
-    margin-top: 1rem !important;
-    margin-bottom: 1rem !important;
-  }
-
-  .my-sm-4 {
-    margin-top: 1.5rem !important;
-    margin-bottom: 1.5rem !important;
-  }
-
-  .my-sm-5 {
-    margin-top: 3rem !important;
-    margin-bottom: 3rem !important;
-  }
-
-  .my-sm-auto {
-    margin-top: auto !important;
-    margin-bottom: auto !important;
-  }
-
-  .mt-sm-0 {
-    margin-top: 0 !important;
-  }
-
-  .mt-sm-1 {
-    margin-top: 0.25rem !important;
-  }
-
-  .mt-sm-2 {
-    margin-top: 0.5rem !important;
-  }
-
-  .mt-sm-3 {
-    margin-top: 1rem !important;
-  }
-
-  .mt-sm-4 {
-    margin-top: 1.5rem !important;
-  }
-
-  .mt-sm-5 {
-    margin-top: 3rem !important;
-  }
-
-  .mt-sm-auto {
-    margin-top: auto !important;
-  }
-
-  .me-sm-0 {
-    margin-right: 0 !important;
-  }
-
-  .me-sm-1 {
-    margin-right: 0.25rem !important;
-  }
-
-  .me-sm-2 {
-    margin-right: 0.5rem !important;
-  }
-
-  .me-sm-3 {
-    margin-right: 1rem !important;
-  }
-
-  .me-sm-4 {
-    margin-right: 1.5rem !important;
-  }
-
-  .me-sm-5 {
-    margin-right: 3rem !important;
-  }
-
-  .me-sm-auto {
-    margin-right: auto !important;
-  }
-
-  .mb-sm-0 {
-    margin-bottom: 0 !important;
-  }
-
-  .mb-sm-1 {
-    margin-bottom: 0.25rem !important;
-  }
-
-  .mb-sm-2 {
-    margin-bottom: 0.5rem !important;
-  }
-
-  .mb-sm-3 {
-    margin-bottom: 1rem !important;
-  }
-
-  .mb-sm-4 {
-    margin-bottom: 1.5rem !important;
-  }
-
-  .mb-sm-5 {
-    margin-bottom: 3rem !important;
-  }
-
-  .mb-sm-auto {
-    margin-bottom: auto !important;
-  }
-
-  .ms-sm-0 {
-    margin-left: 0 !important;
-  }
-
-  .ms-sm-1 {
-    margin-left: 0.25rem !important;
-  }
-
-  .ms-sm-2 {
-    margin-left: 0.5rem !important;
-  }
-
-  .ms-sm-3 {
-    margin-left: 1rem !important;
-  }
-
-  .ms-sm-4 {
-    margin-left: 1.5rem !important;
-  }
-
-  .ms-sm-5 {
-    margin-left: 3rem !important;
-  }
-
-  .ms-sm-auto {
-    margin-left: auto !important;
-  }
-
-  .p-sm-0 {
-    padding: 0 !important;
-  }
-
-  .p-sm-1 {
-    padding: 0.25rem !important;
-  }
-
-  .p-sm-2 {
-    padding: 0.5rem !important;
-  }
-
-  .p-sm-3 {
-    padding: 1rem !important;
-  }
-
-  .p-sm-4 {
-    padding: 1.5rem !important;
-  }
-
-  .p-sm-5 {
-    padding: 3rem !important;
-  }
-
-  .px-sm-0 {
-    padding-right: 0 !important;
-    padding-left: 0 !important;
-  }
-
-  .px-sm-1 {
-    padding-right: 0.25rem !important;
-    padding-left: 0.25rem !important;
-  }
-
-  .px-sm-2 {
-    padding-right: 0.5rem !important;
-    padding-left: 0.5rem !important;
-  }
-
-  .px-sm-3 {
-    padding-right: 1rem !important;
-    padding-left: 1rem !important;
-  }
-
-  .px-sm-4 {
-    padding-right: 1.5rem !important;
-    padding-left: 1.5rem !important;
-  }
-
-  .px-sm-5 {
-    padding-right: 3rem !important;
-    padding-left: 3rem !important;
-  }
-
-  .py-sm-0 {
-    padding-top: 0 !important;
-    padding-bottom: 0 !important;
-  }
-
-  .py-sm-1 {
-    padding-top: 0.25rem !important;
-    padding-bottom: 0.25rem !important;
-  }
-
-  .py-sm-2 {
-    padding-top: 0.5rem !important;
-    padding-bottom: 0.5rem !important;
-  }
-
-  .py-sm-3 {
-    padding-top: 1rem !important;
-    padding-bottom: 1rem !important;
-  }
-
-  .py-sm-4 {
-    padding-top: 1.5rem !important;
-    padding-bottom: 1.5rem !important;
-  }
-
-  .py-sm-5 {
-    padding-top: 3rem !important;
-    padding-bottom: 3rem !important;
-  }
-
-  .pt-sm-0 {
-    padding-top: 0 !important;
-  }
-
-  .pt-sm-1 {
-    padding-top: 0.25rem !important;
-  }
-
-  .pt-sm-2 {
-    padding-top: 0.5rem !important;
-  }
-
-  .pt-sm-3 {
-    padding-top: 1rem !important;
-  }
-
-  .pt-sm-4 {
-    padding-top: 1.5rem !important;
-  }
-
-  .pt-sm-5 {
-    padding-top: 3rem !important;
-  }
-
-  .pe-sm-0 {
-    padding-right: 0 !important;
-  }
-
-  .pe-sm-1 {
-    padding-right: 0.25rem !important;
-  }
-
-  .pe-sm-2 {
-    padding-right: 0.5rem !important;
-  }
-
-  .pe-sm-3 {
-    padding-right: 1rem !important;
-  }
-
-  .pe-sm-4 {
-    padding-right: 1.5rem !important;
-  }
-
-  .pe-sm-5 {
-    padding-right: 3rem !important;
-  }
-
-  .pb-sm-0 {
-    padding-bottom: 0 !important;
-  }
-
-  .pb-sm-1 {
-    padding-bottom: 0.25rem !important;
-  }
-
-  .pb-sm-2 {
-    padding-bottom: 0.5rem !important;
-  }
-
-  .pb-sm-3 {
-    padding-bottom: 1rem !important;
-  }
-
-  .pb-sm-4 {
-    padding-bottom: 1.5rem !important;
-  }
-
-  .pb-sm-5 {
-    padding-bottom: 3rem !important;
-  }
-
-  .ps-sm-0 {
-    padding-left: 0 !important;
-  }
-
-  .ps-sm-1 {
-    padding-left: 0.25rem !important;
-  }
-
-  .ps-sm-2 {
-    padding-left: 0.5rem !important;
-  }
-
-  .ps-sm-3 {
-    padding-left: 1rem !important;
-  }
-
-  .ps-sm-4 {
-    padding-left: 1.5rem !important;
-  }
-
-  .ps-sm-5 {
-    padding-left: 3rem !important;
-  }
-}
-@media (min-width: 768px) {
-  .d-md-inline {
-    display: inline !important;
-  }
-
-  .d-md-inline-block {
-    display: inline-block !important;
-  }
-
-  .d-md-block {
-    display: block !important;
-  }
-
-  .d-md-grid {
-    display: grid !important;
-  }
-
-  .d-md-table {
-    display: table !important;
-  }
-
-  .d-md-table-row {
-    display: table-row !important;
-  }
-
-  .d-md-table-cell {
-    display: table-cell !important;
-  }
-
-  .d-md-flex {
-    display: flex !important;
-  }
-
-  .d-md-inline-flex {
-    display: inline-flex !important;
-  }
-
-  .d-md-none {
-    display: none !important;
-  }
-
-  .flex-md-fill {
-    flex: 1 1 auto !important;
-  }
-
-  .flex-md-row {
-    flex-direction: row !important;
-  }
-
-  .flex-md-column {
-    flex-direction: column !important;
-  }
-
-  .flex-md-row-reverse {
-    flex-direction: row-reverse !important;
-  }
-
-  .flex-md-column-reverse {
-    flex-direction: column-reverse !important;
-  }
-
-  .flex-md-grow-0 {
-    flex-grow: 0 !important;
-  }
-
-  .flex-md-grow-1 {
-    flex-grow: 1 !important;
-  }
-
-  .flex-md-shrink-0 {
-    flex-shrink: 0 !important;
-  }
-
-  .flex-md-shrink-1 {
-    flex-shrink: 1 !important;
-  }
-
-  .flex-md-wrap {
-    flex-wrap: wrap !important;
-  }
-
-  .flex-md-nowrap {
-    flex-wrap: nowrap !important;
-  }
-
-  .flex-md-wrap-reverse {
-    flex-wrap: wrap-reverse !important;
-  }
-
-  .justify-content-md-start {
-    justify-content: flex-start !important;
-  }
-
-  .justify-content-md-end {
-    justify-content: flex-end !important;
-  }
-
-  .justify-content-md-center {
-    justify-content: center !important;
-  }
-
-  .justify-content-md-between {
-    justify-content: space-between !important;
-  }
-
-  .justify-content-md-around {
-    justify-content: space-around !important;
-  }
-
-  .justify-content-md-evenly {
-    justify-content: space-evenly !important;
-  }
-
-  .align-items-md-start {
-    align-items: flex-start !important;
-  }
-
-  .align-items-md-end {
-    align-items: flex-end !important;
-  }
-
-  .align-items-md-center {
-    align-items: center !important;
-  }
-
-  .align-items-md-baseline {
-    align-items: baseline !important;
-  }
-
-  .align-items-md-stretch {
-    align-items: stretch !important;
-  }
-
-  .align-content-md-start {
-    align-content: flex-start !important;
-  }
-
-  .align-content-md-end {
-    align-content: flex-end !important;
-  }
-
-  .align-content-md-center {
-    align-content: center !important;
-  }
-
-  .align-content-md-between {
-    align-content: space-between !important;
-  }
-
-  .align-content-md-around {
-    align-content: space-around !important;
-  }
-
-  .align-content-md-stretch {
-    align-content: stretch !important;
-  }
-
-  .align-self-md-auto {
-    align-self: auto !important;
-  }
-
-  .align-self-md-start {
-    align-self: flex-start !important;
-  }
-
-  .align-self-md-end {
-    align-self: flex-end !important;
-  }
-
-  .align-self-md-center {
-    align-self: center !important;
-  }
-
-  .align-self-md-baseline {
-    align-self: baseline !important;
-  }
-
-  .align-self-md-stretch {
-    align-self: stretch !important;
-  }
-
-  .order-md-first {
-    order: -1 !important;
-  }
-
-  .order-md-0 {
-    order: 0 !important;
-  }
-
-  .order-md-1 {
-    order: 1 !important;
-  }
-
-  .order-md-2 {
-    order: 2 !important;
-  }
-
-  .order-md-3 {
-    order: 3 !important;
-  }
-
-  .order-md-4 {
-    order: 4 !important;
-  }
-
-  .order-md-5 {
-    order: 5 !important;
-  }
-
-  .order-md-last {
-    order: 6 !important;
-  }
-
-  .m-md-0 {
-    margin: 0 !important;
-  }
-
-  .m-md-1 {
-    margin: 0.25rem !important;
-  }
-
-  .m-md-2 {
-    margin: 0.5rem !important;
-  }
-
-  .m-md-3 {
-    margin: 1rem !important;
-  }
-
-  .m-md-4 {
-    margin: 1.5rem !important;
-  }
-
-  .m-md-5 {
-    margin: 3rem !important;
-  }
-
-  .m-md-auto {
-    margin: auto !important;
-  }
-
-  .mx-md-0 {
-    margin-right: 0 !important;
-    margin-left: 0 !important;
-  }
-
-  .mx-md-1 {
-    margin-right: 0.25rem !important;
-    margin-left: 0.25rem !important;
-  }
-
-  .mx-md-2 {
-    margin-right: 0.5rem !important;
-    margin-left: 0.5rem !important;
-  }
-
-  .mx-md-3 {
-    margin-right: 1rem !important;
-    margin-left: 1rem !important;
-  }
-
-  .mx-md-4 {
-    margin-right: 1.5rem !important;
-    margin-left: 1.5rem !important;
-  }
-
-  .mx-md-5 {
-    margin-right: 3rem !important;
-    margin-left: 3rem !important;
-  }
-
-  .mx-md-auto {
-    margin-right: auto !important;
-    margin-left: auto !important;
-  }
-
-  .my-md-0 {
-    margin-top: 0 !important;
-    margin-bottom: 0 !important;
-  }
-
-  .my-md-1 {
-    margin-top: 0.25rem !important;
-    margin-bottom: 0.25rem !important;
-  }
-
-  .my-md-2 {
-    margin-top: 0.5rem !important;
-    margin-bottom: 0.5rem !important;
-  }
-
-  .my-md-3 {
-    margin-top: 1rem !important;
-    margin-bottom: 1rem !important;
-  }
-
-  .my-md-4 {
-    margin-top: 1.5rem !important;
-    margin-bottom: 1.5rem !important;
-  }
-
-  .my-md-5 {
-    margin-top: 3rem !important;
-    margin-bottom: 3rem !important;
-  }
-
-  .my-md-auto {
-    margin-top: auto !important;
-    margin-bottom: auto !important;
-  }
-
-  .mt-md-0 {
-    margin-top: 0 !important;
-  }
-
-  .mt-md-1 {
-    margin-top: 0.25rem !important;
-  }
-
-  .mt-md-2 {
-    margin-top: 0.5rem !important;
-  }
-
-  .mt-md-3 {
-    margin-top: 1rem !important;
-  }
-
-  .mt-md-4 {
-    margin-top: 1.5rem !important;
-  }
-
-  .mt-md-5 {
-    margin-top: 3rem !important;
-  }
-
-  .mt-md-auto {
-    margin-top: auto !important;
-  }
-
-  .me-md-0 {
-    margin-right: 0 !important;
-  }
-
-  .me-md-1 {
-    margin-right: 0.25rem !important;
-  }
-
-  .me-md-2 {
-    margin-right: 0.5rem !important;
-  }
-
-  .me-md-3 {
-    margin-right: 1rem !important;
-  }
-
-  .me-md-4 {
-    margin-right: 1.5rem !important;
-  }
-
-  .me-md-5 {
-    margin-right: 3rem !important;
-  }
-
-  .me-md-auto {
-    margin-right: auto !important;
-  }
-
-  .mb-md-0 {
-    margin-bottom: 0 !important;
-  }
-
-  .mb-md-1 {
-    margin-bottom: 0.25rem !important;
-  }
-
-  .mb-md-2 {
-    margin-bottom: 0.5rem !important;
-  }
-
-  .mb-md-3 {
-    margin-bottom: 1rem !important;
-  }
-
-  .mb-md-4 {
-    margin-bottom: 1.5rem !important;
-  }
-
-  .mb-md-5 {
-    margin-bottom: 3rem !important;
-  }
-
-  .mb-md-auto {
-    margin-bottom: auto !important;
-  }
-
-  .ms-md-0 {
-    margin-left: 0 !important;
-  }
-
-  .ms-md-1 {
-    margin-left: 0.25rem !important;
-  }
-
-  .ms-md-2 {
-    margin-left: 0.5rem !important;
-  }
-
-  .ms-md-3 {
-    margin-left: 1rem !important;
-  }
-
-  .ms-md-4 {
-    margin-left: 1.5rem !important;
-  }
-
-  .ms-md-5 {
-    margin-left: 3rem !important;
-  }
-
-  .ms-md-auto {
-    margin-left: auto !important;
-  }
-
-  .p-md-0 {
-    padding: 0 !important;
-  }
-
-  .p-md-1 {
-    padding: 0.25rem !important;
-  }
-
-  .p-md-2 {
-    padding: 0.5rem !important;
-  }
-
-  .p-md-3 {
-    padding: 1rem !important;
-  }
-
-  .p-md-4 {
-    padding: 1.5rem !important;
-  }
-
-  .p-md-5 {
-    padding: 3rem !important;
-  }
-
-  .px-md-0 {
-    padding-right: 0 !important;
-    padding-left: 0 !important;
-  }
-
-  .px-md-1 {
-    padding-right: 0.25rem !important;
-    padding-left: 0.25rem !important;
-  }
-
-  .px-md-2 {
-    padding-right: 0.5rem !important;
-    padding-left: 0.5rem !important;
-  }
-
-  .px-md-3 {
-    padding-right: 1rem !important;
-    padding-left: 1rem !important;
-  }
-
-  .px-md-4 {
-    padding-right: 1.5rem !important;
-    padding-left: 1.5rem !important;
-  }
-
-  .px-md-5 {
-    padding-right: 3rem !important;
-    padding-left: 3rem !important;
-  }
-
-  .py-md-0 {
-    padding-top: 0 !important;
-    padding-bottom: 0 !important;
-  }
-
-  .py-md-1 {
-    padding-top: 0.25rem !important;
-    padding-bottom: 0.25rem !important;
-  }
-
-  .py-md-2 {
-    padding-top: 0.5rem !important;
-    padding-bottom: 0.5rem !important;
-  }
-
-  .py-md-3 {
-    padding-top: 1rem !important;
-    padding-bottom: 1rem !important;
-  }
-
-  .py-md-4 {
-    padding-top: 1.5rem !important;
-    padding-bottom: 1.5rem !important;
-  }
-
-  .py-md-5 {
-    padding-top: 3rem !important;
-    padding-bottom: 3rem !important;
-  }
-
-  .pt-md-0 {
-    padding-top: 0 !important;
-  }
-
-  .pt-md-1 {
-    padding-top: 0.25rem !important;
-  }
-
-  .pt-md-2 {
-    padding-top: 0.5rem !important;
-  }
-
-  .pt-md-3 {
-    padding-top: 1rem !important;
-  }
-
-  .pt-md-4 {
-    padding-top: 1.5rem !important;
-  }
-
-  .pt-md-5 {
-    padding-top: 3rem !important;
-  }
-
-  .pe-md-0 {
-    padding-right: 0 !important;
-  }
-
-  .pe-md-1 {
-    padding-right: 0.25rem !important;
-  }
-
-  .pe-md-2 {
-    padding-right: 0.5rem !important;
-  }
-
-  .pe-md-3 {
-    padding-right: 1rem !important;
-  }
-
-  .pe-md-4 {
-    padding-right: 1.5rem !important;
-  }
-
-  .pe-md-5 {
-    padding-right: 3rem !important;
-  }
-
-  .pb-md-0 {
-    padding-bottom: 0 !important;
-  }
-
-  .pb-md-1 {
-    padding-bottom: 0.25rem !important;
-  }
-
-  .pb-md-2 {
-    padding-bottom: 0.5rem !important;
-  }
-
-  .pb-md-3 {
-    padding-bottom: 1rem !important;
-  }
-
-  .pb-md-4 {
-    padding-bottom: 1.5rem !important;
-  }
-
-  .pb-md-5 {
-    padding-bottom: 3rem !important;
-  }
-
-  .ps-md-0 {
-    padding-left: 0 !important;
-  }
-
-  .ps-md-1 {
-    padding-left: 0.25rem !important;
-  }
-
-  .ps-md-2 {
-    padding-left: 0.5rem !important;
-  }
-
-  .ps-md-3 {
-    padding-left: 1rem !important;
-  }
-
-  .ps-md-4 {
-    padding-left: 1.5rem !important;
-  }
-
-  .ps-md-5 {
-    padding-left: 3rem !important;
-  }
-}
-@media (min-width: 992px) {
-  .d-lg-inline {
-    display: inline !important;
-  }
-
-  .d-lg-inline-block {
-    display: inline-block !important;
-  }
-
-  .d-lg-block {
-    display: block !important;
-  }
-
-  .d-lg-grid {
-    display: grid !important;
-  }
-
-  .d-lg-table {
-    display: table !important;
-  }
-
-  .d-lg-table-row {
-    display: table-row !important;
-  }
-
-  .d-lg-table-cell {
-    display: table-cell !important;
-  }
-
-  .d-lg-flex {
-    display: flex !important;
-  }
-
-  .d-lg-inline-flex {
-    display: inline-flex !important;
-  }
-
-  .d-lg-none {
-    display: none !important;
-  }
-
-  .flex-lg-fill {
-    flex: 1 1 auto !important;
-  }
-
-  .flex-lg-row {
-    flex-direction: row !important;
-  }
-
-  .flex-lg-column {
-    flex-direction: column !important;
-  }
-
-  .flex-lg-row-reverse {
-    flex-direction: row-reverse !important;
-  }
-
-  .flex-lg-column-reverse {
-    flex-direction: column-reverse !important;
-  }
-
-  .flex-lg-grow-0 {
-    flex-grow: 0 !important;
-  }
-
-  .flex-lg-grow-1 {
-    flex-grow: 1 !important;
-  }
-
-  .flex-lg-shrink-0 {
-    flex-shrink: 0 !important;
-  }
-
-  .flex-lg-shrink-1 {
-    flex-shrink: 1 !important;
-  }
-
-  .flex-lg-wrap {
-    flex-wrap: wrap !important;
-  }
-
-  .flex-lg-nowrap {
-    flex-wrap: nowrap !important;
-  }
-
-  .flex-lg-wrap-reverse {
-    flex-wrap: wrap-reverse !important;
-  }
-
-  .justify-content-lg-start {
-    justify-content: flex-start !important;
-  }
-
-  .justify-content-lg-end {
-    justify-content: flex-end !important;
-  }
-
-  .justify-content-lg-center {
-    justify-content: center !important;
-  }
-
-  .justify-content-lg-between {
-    justify-content: space-between !important;
-  }
-
-  .justify-content-lg-around {
-    justify-content: space-around !important;
-  }
-
-  .justify-content-lg-evenly {
-    justify-content: space-evenly !important;
-  }
-
-  .align-items-lg-start {
-    align-items: flex-start !important;
-  }
-
-  .align-items-lg-end {
-    align-items: flex-end !important;
-  }
-
-  .align-items-lg-center {
-    align-items: center !important;
-  }
-
-  .align-items-lg-baseline {
-    align-items: baseline !important;
-  }
-
-  .align-items-lg-stretch {
-    align-items: stretch !important;
-  }
-
-  .align-content-lg-start {
-    align-content: flex-start !important;
-  }
-
-  .align-content-lg-end {
-    align-content: flex-end !important;
-  }
-
-  .align-content-lg-center {
-    align-content: center !important;
-  }
-
-  .align-content-lg-between {
-    align-content: space-between !important;
-  }
-
-  .align-content-lg-around {
-    align-content: space-around !important;
-  }
-
-  .align-content-lg-stretch {
-    align-content: stretch !important;
-  }
-
-  .align-self-lg-auto {
-    align-self: auto !important;
-  }
-
-  .align-self-lg-start {
-    align-self: flex-start !important;
-  }
-
-  .align-self-lg-end {
-    align-self: flex-end !important;
-  }
-
-  .align-self-lg-center {
-    align-self: center !important;
-  }
-
-  .align-self-lg-baseline {
-    align-self: baseline !important;
-  }
-
-  .align-self-lg-stretch {
-    align-self: stretch !important;
-  }
-
-  .order-lg-first {
-    order: -1 !important;
-  }
-
-  .order-lg-0 {
-    order: 0 !important;
-  }
-
-  .order-lg-1 {
-    order: 1 !important;
-  }
-
-  .order-lg-2 {
-    order: 2 !important;
-  }
-
-  .order-lg-3 {
-    order: 3 !important;
-  }
-
-  .order-lg-4 {
-    order: 4 !important;
-  }
-
-  .order-lg-5 {
-    order: 5 !important;
-  }
-
-  .order-lg-last {
-    order: 6 !important;
-  }
-
-  .m-lg-0 {
-    margin: 0 !important;
-  }
-
-  .m-lg-1 {
-    margin: 0.25rem !important;
-  }
-
-  .m-lg-2 {
-    margin: 0.5rem !important;
-  }
-
-  .m-lg-3 {
-    margin: 1rem !important;
-  }
-
-  .m-lg-4 {
-    margin: 1.5rem !important;
-  }
-
-  .m-lg-5 {
-    margin: 3rem !important;
-  }
-
-  .m-lg-auto {
-    margin: auto !important;
-  }
-
-  .mx-lg-0 {
-    margin-right: 0 !important;
-    margin-left: 0 !important;
-  }
-
-  .mx-lg-1 {
-    margin-right: 0.25rem !important;
-    margin-left: 0.25rem !important;
-  }
-
-  .mx-lg-2 {
-    margin-right: 0.5rem !important;
-    margin-left: 0.5rem !important;
-  }
-
-  .mx-lg-3 {
-    margin-right: 1rem !important;
-    margin-left: 1rem !important;
-  }
-
-  .mx-lg-4 {
-    margin-right: 1.5rem !important;
-    margin-left: 1.5rem !important;
-  }
-
-  .mx-lg-5 {
-    margin-right: 3rem !important;
-    margin-left: 3rem !important;
-  }
-
-  .mx-lg-auto {
-    margin-right: auto !important;
-    margin-left: auto !important;
-  }
-
-  .my-lg-0 {
-    margin-top: 0 !important;
-    margin-bottom: 0 !important;
-  }
-
-  .my-lg-1 {
-    margin-top: 0.25rem !important;
-    margin-bottom: 0.25rem !important;
-  }
-
-  .my-lg-2 {
-    margin-top: 0.5rem !important;
-    margin-bottom: 0.5rem !important;
-  }
-
-  .my-lg-3 {
-    margin-top: 1rem !important;
-    margin-bottom: 1rem !important;
-  }
-
-  .my-lg-4 {
-    margin-top: 1.5rem !important;
-    margin-bottom: 1.5rem !important;
-  }
-
-  .my-lg-5 {
-    margin-top: 3rem !important;
-    margin-bottom: 3rem !important;
-  }
-
-  .my-lg-auto {
-    margin-top: auto !important;
-    margin-bottom: auto !important;
-  }
-
-  .mt-lg-0 {
-    margin-top: 0 !important;
-  }
-
-  .mt-lg-1 {
-    margin-top: 0.25rem !important;
-  }
-
-  .mt-lg-2 {
-    margin-top: 0.5rem !important;
-  }
-
-  .mt-lg-3 {
-    margin-top: 1rem !important;
-  }
-
-  .mt-lg-4 {
-    margin-top: 1.5rem !important;
-  }
-
-  .mt-lg-5 {
-    margin-top: 3rem !important;
-  }
-
-  .mt-lg-auto {
-    margin-top: auto !important;
-  }
-
-  .me-lg-0 {
-    margin-right: 0 !important;
-  }
-
-  .me-lg-1 {
-    margin-right: 0.25rem !important;
-  }
-
-  .me-lg-2 {
-    margin-right: 0.5rem !important;
-  }
-
-  .me-lg-3 {
-    margin-right: 1rem !important;
-  }
-
-  .me-lg-4 {
-    margin-right: 1.5rem !important;
-  }
-
-  .me-lg-5 {
-    margin-right: 3rem !important;
-  }
-
-  .me-lg-auto {
-    margin-right: auto !important;
-  }
-
-  .mb-lg-0 {
-    margin-bottom: 0 !important;
-  }
-
-  .mb-lg-1 {
-    margin-bottom: 0.25rem !important;
-  }
-
-  .mb-lg-2 {
-    margin-bottom: 0.5rem !important;
-  }
-
-  .mb-lg-3 {
-    margin-bottom: 1rem !important;
-  }
-
-  .mb-lg-4 {
-    margin-bottom: 1.5rem !important;
-  }
-
-  .mb-lg-5 {
-    margin-bottom: 3rem !important;
-  }
-
-  .mb-lg-auto {
-    margin-bottom: auto !important;
-  }
-
-  .ms-lg-0 {
-    margin-left: 0 !important;
-  }
-
-  .ms-lg-1 {
-    margin-left: 0.25rem !important;
-  }
-
-  .ms-lg-2 {
-    margin-left: 0.5rem !important;
-  }
-
-  .ms-lg-3 {
-    margin-left: 1rem !important;
-  }
-
-  .ms-lg-4 {
-    margin-left: 1.5rem !important;
-  }
-
-  .ms-lg-5 {
-    margin-left: 3rem !important;
-  }
-
-  .ms-lg-auto {
-    margin-left: auto !important;
-  }
-
-  .p-lg-0 {
-    padding: 0 !important;
-  }
-
-  .p-lg-1 {
-    padding: 0.25rem !important;
-  }
-
-  .p-lg-2 {
-    padding: 0.5rem !important;
-  }
-
-  .p-lg-3 {
-    padding: 1rem !important;
-  }
-
-  .p-lg-4 {
-    padding: 1.5rem !important;
-  }
-
-  .p-lg-5 {
-    padding: 3rem !important;
-  }
-
-  .px-lg-0 {
-    padding-right: 0 !important;
-    padding-left: 0 !important;
-  }
-
-  .px-lg-1 {
-    padding-right: 0.25rem !important;
-    padding-left: 0.25rem !important;
-  }
-
-  .px-lg-2 {
-    padding-right: 0.5rem !important;
-    padding-left: 0.5rem !important;
-  }
-
-  .px-lg-3 {
-    padding-right: 1rem !important;
-    padding-left: 1rem !important;
-  }
-
-  .px-lg-4 {
-    padding-right: 1.5rem !important;
-    padding-left: 1.5rem !important;
-  }
-
-  .px-lg-5 {
-    padding-right: 3rem !important;
-    padding-left: 3rem !important;
-  }
-
-  .py-lg-0 {
-    padding-top: 0 !important;
-    padding-bottom: 0 !important;
-  }
-
-  .py-lg-1 {
-    padding-top: 0.25rem !important;
-    padding-bottom: 0.25rem !important;
-  }
-
-  .py-lg-2 {
-    padding-top: 0.5rem !important;
-    padding-bottom: 0.5rem !important;
-  }
-
-  .py-lg-3 {
-    padding-top: 1rem !important;
-    padding-bottom: 1rem !important;
-  }
-
-  .py-lg-4 {
-    padding-top: 1.5rem !important;
-    padding-bottom: 1.5rem !important;
-  }
-
-  .py-lg-5 {
-    padding-top: 3rem !important;
-    padding-bottom: 3rem !important;
-  }
-
-  .pt-lg-0 {
-    padding-top: 0 !important;
-  }
-
-  .pt-lg-1 {
-    padding-top: 0.25rem !important;
-  }
-
-  .pt-lg-2 {
-    padding-top: 0.5rem !important;
-  }
-
-  .pt-lg-3 {
-    padding-top: 1rem !important;
-  }
-
-  .pt-lg-4 {
-    padding-top: 1.5rem !important;
-  }
-
-  .pt-lg-5 {
-    padding-top: 3rem !important;
-  }
-
-  .pe-lg-0 {
-    padding-right: 0 !important;
-  }
-
-  .pe-lg-1 {
-    padding-right: 0.25rem !important;
-  }
-
-  .pe-lg-2 {
-    padding-right: 0.5rem !important;
-  }
-
-  .pe-lg-3 {
-    padding-right: 1rem !important;
-  }
-
-  .pe-lg-4 {
-    padding-right: 1.5rem !important;
-  }
-
-  .pe-lg-5 {
-    padding-right: 3rem !important;
-  }
-
-  .pb-lg-0 {
-    padding-bottom: 0 !important;
-  }
-
-  .pb-lg-1 {
-    padding-bottom: 0.25rem !important;
-  }
-
-  .pb-lg-2 {
-    padding-bottom: 0.5rem !important;
-  }
-
-  .pb-lg-3 {
-    padding-bottom: 1rem !important;
-  }
-
-  .pb-lg-4 {
-    padding-bottom: 1.5rem !important;
-  }
-
-  .pb-lg-5 {
-    padding-bottom: 3rem !important;
-  }
-
-  .ps-lg-0 {
-    padding-left: 0 !important;
-  }
-
-  .ps-lg-1 {
-    padding-left: 0.25rem !important;
-  }
-
-  .ps-lg-2 {
-    padding-left: 0.5rem !important;
-  }
-
-  .ps-lg-3 {
-    padding-left: 1rem !important;
-  }
-
-  .ps-lg-4 {
-    padding-left: 1.5rem !important;
-  }
-
-  .ps-lg-5 {
-    padding-left: 3rem !important;
-  }
-}
-@media (min-width: 1200px) {
-  .d-xl-inline {
-    display: inline !important;
-  }
-
-  .d-xl-inline-block {
-    display: inline-block !important;
-  }
-
-  .d-xl-block {
-    display: block !important;
-  }
-
-  .d-xl-grid {
-    display: grid !important;
-  }
-
-  .d-xl-table {
-    display: table !important;
-  }
-
-  .d-xl-table-row {
-    display: table-row !important;
-  }
-
-  .d-xl-table-cell {
-    display: table-cell !important;
-  }
-
-  .d-xl-flex {
-    display: flex !important;
-  }
-
-  .d-xl-inline-flex {
-    display: inline-flex !important;
-  }
-
-  .d-xl-none {
-    display: none !important;
-  }
-
-  .flex-xl-fill {
-    flex: 1 1 auto !important;
-  }
-
-  .flex-xl-row {
-    flex-direction: row !important;
-  }
-
-  .flex-xl-column {
-    flex-direction: column !important;
-  }
-
-  .flex-xl-row-reverse {
-    flex-direction: row-reverse !important;
-  }
-
-  .flex-xl-column-reverse {
-    flex-direction: column-reverse !important;
-  }
-
-  .flex-xl-grow-0 {
-    flex-grow: 0 !important;
-  }
-
-  .flex-xl-grow-1 {
-    flex-grow: 1 !important;
-  }
-
-  .flex-xl-shrink-0 {
-    flex-shrink: 0 !important;
-  }
-
-  .flex-xl-shrink-1 {
-    flex-shrink: 1 !important;
-  }
-
-  .flex-xl-wrap {
-    flex-wrap: wrap !important;
-  }
-
-  .flex-xl-nowrap {
-    flex-wrap: nowrap !important;
-  }
-
-  .flex-xl-wrap-reverse {
-    flex-wrap: wrap-reverse !important;
-  }
-
-  .justify-content-xl-start {
-    justify-content: flex-start !important;
-  }
-
-  .justify-content-xl-end {
-    justify-content: flex-end !important;
-  }
-
-  .justify-content-xl-center {
-    justify-content: center !important;
-  }
-
-  .justify-content-xl-between {
-    justify-content: space-between !important;
-  }
-
-  .justify-content-xl-around {
-    justify-content: space-around !important;
-  }
-
-  .justify-content-xl-evenly {
-    justify-content: space-evenly !important;
-  }
-
-  .align-items-xl-start {
-    align-items: flex-start !important;
-  }
-
-  .align-items-xl-end {
-    align-items: flex-end !important;
-  }
-
-  .align-items-xl-center {
-    align-items: center !important;
-  }
-
-  .align-items-xl-baseline {
-    align-items: baseline !important;
-  }
-
-  .align-items-xl-stretch {
-    align-items: stretch !important;
-  }
-
-  .align-content-xl-start {
-    align-content: flex-start !important;
-  }
-
-  .align-content-xl-end {
-    align-content: flex-end !important;
-  }
-
-  .align-content-xl-center {
-    align-content: center !important;
-  }
-
-  .align-content-xl-between {
-    align-content: space-between !important;
-  }
-
-  .align-content-xl-around {
-    align-content: space-around !important;
-  }
-
-  .align-content-xl-stretch {
-    align-content: stretch !important;
-  }
-
-  .align-self-xl-auto {
-    align-self: auto !important;
-  }
-
-  .align-self-xl-start {
-    align-self: flex-start !important;
-  }
-
-  .align-self-xl-end {
-    align-self: flex-end !important;
-  }
-
-  .align-self-xl-center {
-    align-self: center !important;
-  }
-
-  .align-self-xl-baseline {
-    align-self: baseline !important;
-  }
-
-  .align-self-xl-stretch {
-    align-self: stretch !important;
-  }
-
-  .order-xl-first {
-    order: -1 !important;
-  }
-
-  .order-xl-0 {
-    order: 0 !important;
-  }
-
-  .order-xl-1 {
-    order: 1 !important;
-  }
-
-  .order-xl-2 {
-    order: 2 !important;
-  }
-
-  .order-xl-3 {
-    order: 3 !important;
-  }
-
-  .order-xl-4 {
-    order: 4 !important;
-  }
-
-  .order-xl-5 {
-    order: 5 !important;
-  }
-
-  .order-xl-last {
-    order: 6 !important;
-  }
-
-  .m-xl-0 {
-    margin: 0 !important;
-  }
-
-  .m-xl-1 {
-    margin: 0.25rem !important;
-  }
-
-  .m-xl-2 {
-    margin: 0.5rem !important;
-  }
-
-  .m-xl-3 {
-    margin: 1rem !important;
-  }
-
-  .m-xl-4 {
-    margin: 1.5rem !important;
-  }
-
-  .m-xl-5 {
-    margin: 3rem !important;
-  }
-
-  .m-xl-auto {
-    margin: auto !important;
-  }
-
-  .mx-xl-0 {
-    margin-right: 0 !important;
-    margin-left: 0 !important;
-  }
-
-  .mx-xl-1 {
-    margin-right: 0.25rem !important;
-    margin-left: 0.25rem !important;
-  }
-
-  .mx-xl-2 {
-    margin-right: 0.5rem !important;
-    margin-left: 0.5rem !important;
-  }
-
-  .mx-xl-3 {
-    margin-right: 1rem !important;
-    margin-left: 1rem !important;
-  }
-
-  .mx-xl-4 {
-    margin-right: 1.5rem !important;
-    margin-left: 1.5rem !important;
-  }
-
-  .mx-xl-5 {
-    margin-right: 3rem !important;
-    margin-left: 3rem !important;
-  }
-
-  .mx-xl-auto {
-    margin-right: auto !important;
-    margin-left: auto !important;
-  }
-
-  .my-xl-0 {
-    margin-top: 0 !important;
-    margin-bottom: 0 !important;
-  }
-
-  .my-xl-1 {
-    margin-top: 0.25rem !important;
-    margin-bottom: 0.25rem !important;
-  }
-
-  .my-xl-2 {
-    margin-top: 0.5rem !important;
-    margin-bottom: 0.5rem !important;
-  }
-
-  .my-xl-3 {
-    margin-top: 1rem !important;
-    margin-bottom: 1rem !important;
-  }
-
-  .my-xl-4 {
-    margin-top: 1.5rem !important;
-    margin-bottom: 1.5rem !important;
-  }
-
-  .my-xl-5 {
-    margin-top: 3rem !important;
-    margin-bottom: 3rem !important;
-  }
-
-  .my-xl-auto {
-    margin-top: auto !important;
-    margin-bottom: auto !important;
-  }
-
-  .mt-xl-0 {
-    margin-top: 0 !important;
-  }
-
-  .mt-xl-1 {
-    margin-top: 0.25rem !important;
-  }
-
-  .mt-xl-2 {
-    margin-top: 0.5rem !important;
-  }
-
-  .mt-xl-3 {
-    margin-top: 1rem !important;
-  }
-
-  .mt-xl-4 {
-    margin-top: 1.5rem !important;
-  }
-
-  .mt-xl-5 {
-    margin-top: 3rem !important;
-  }
-
-  .mt-xl-auto {
-    margin-top: auto !important;
-  }
-
-  .me-xl-0 {
-    margin-right: 0 !important;
-  }
-
-  .me-xl-1 {
-    margin-right: 0.25rem !important;
-  }
-
-  .me-xl-2 {
-    margin-right: 0.5rem !important;
-  }
-
-  .me-xl-3 {
-    margin-right: 1rem !important;
-  }
-
-  .me-xl-4 {
-    margin-right: 1.5rem !important;
-  }
-
-  .me-xl-5 {
-    margin-right: 3rem !important;
-  }
-
-  .me-xl-auto {
-    margin-right: auto !important;
-  }
-
-  .mb-xl-0 {
-    margin-bottom: 0 !important;
-  }
-
-  .mb-xl-1 {
-    margin-bottom: 0.25rem !important;
-  }
-
-  .mb-xl-2 {
-    margin-bottom: 0.5rem !important;
-  }
-
-  .mb-xl-3 {
-    margin-bottom: 1rem !important;
-  }
-
-  .mb-xl-4 {
-    margin-bottom: 1.5rem !important;
-  }
-
-  .mb-xl-5 {
-    margin-bottom: 3rem !important;
-  }
-
-  .mb-xl-auto {
-    margin-bottom: auto !important;
-  }
-
-  .ms-xl-0 {
-    margin-left: 0 !important;
-  }
-
-  .ms-xl-1 {
-    margin-left: 0.25rem !important;
-  }
-
-  .ms-xl-2 {
-    margin-left: 0.5rem !important;
-  }
-
-  .ms-xl-3 {
-    margin-left: 1rem !important;
-  }
-
-  .ms-xl-4 {
-    margin-left: 1.5rem !important;
-  }
-
-  .ms-xl-5 {
-    margin-left: 3rem !important;
-  }
-
-  .ms-xl-auto {
-    margin-left: auto !important;
-  }
-
-  .p-xl-0 {
-    padding: 0 !important;
-  }
-
-  .p-xl-1 {
-    padding: 0.25rem !important;
-  }
-
-  .p-xl-2 {
-    padding: 0.5rem !important;
-  }
-
-  .p-xl-3 {
-    padding: 1rem !important;
-  }
-
-  .p-xl-4 {
-    padding: 1.5rem !important;
-  }
-
-  .p-xl-5 {
-    padding: 3rem !important;
-  }
-
-  .px-xl-0 {
-    padding-right: 0 !important;
-    padding-left: 0 !important;
-  }
-
-  .px-xl-1 {
-    padding-right: 0.25rem !important;
-    padding-left: 0.25rem !important;
-  }
-
-  .px-xl-2 {
-    padding-right: 0.5rem !important;
-    padding-left: 0.5rem !important;
-  }
-
-  .px-xl-3 {
-    padding-right: 1rem !important;
-    padding-left: 1rem !important;
-  }
-
-  .px-xl-4 {
-    padding-right: 1.5rem !important;
-    padding-left: 1.5rem !important;
-  }
-
-  .px-xl-5 {
-    padding-right: 3rem !important;
-    padding-left: 3rem !important;
-  }
-
-  .py-xl-0 {
-    padding-top: 0 !important;
-    padding-bottom: 0 !important;
-  }
-
-  .py-xl-1 {
-    padding-top: 0.25rem !important;
-    padding-bottom: 0.25rem !important;
-  }
-
-  .py-xl-2 {
-    padding-top: 0.5rem !important;
-    padding-bottom: 0.5rem !important;
-  }
-
-  .py-xl-3 {
-    padding-top: 1rem !important;
-    padding-bottom: 1rem !important;
-  }
-
-  .py-xl-4 {
-    padding-top: 1.5rem !important;
-    padding-bottom: 1.5rem !important;
-  }
-
-  .py-xl-5 {
-    padding-top: 3rem !important;
-    padding-bottom: 3rem !important;
-  }
-
-  .pt-xl-0 {
-    padding-top: 0 !important;
-  }
-
-  .pt-xl-1 {
-    padding-top: 0.25rem !important;
-  }
-
-  .pt-xl-2 {
-    padding-top: 0.5rem !important;
-  }
-
-  .pt-xl-3 {
-    padding-top: 1rem !important;
-  }
-
-  .pt-xl-4 {
-    padding-top: 1.5rem !important;
-  }
-
-  .pt-xl-5 {
-    padding-top: 3rem !important;
-  }
-
-  .pe-xl-0 {
-    padding-right: 0 !important;
-  }
-
-  .pe-xl-1 {
-    padding-right: 0.25rem !important;
-  }
-
-  .pe-xl-2 {
-    padding-right: 0.5rem !important;
-  }
-
-  .pe-xl-3 {
-    padding-right: 1rem !important;
-  }
-
-  .pe-xl-4 {
-    padding-right: 1.5rem !important;
-  }
-
-  .pe-xl-5 {
-    padding-right: 3rem !important;
-  }
-
-  .pb-xl-0 {
-    padding-bottom: 0 !important;
-  }
-
-  .pb-xl-1 {
-    padding-bottom: 0.25rem !important;
-  }
-
-  .pb-xl-2 {
-    padding-bottom: 0.5rem !important;
-  }
-
-  .pb-xl-3 {
-    padding-bottom: 1rem !important;
-  }
-
-  .pb-xl-4 {
-    padding-bottom: 1.5rem !important;
-  }
-
-  .pb-xl-5 {
-    padding-bottom: 3rem !important;
-  }
-
-  .ps-xl-0 {
-    padding-left: 0 !important;
-  }
-
-  .ps-xl-1 {
-    padding-left: 0.25rem !important;
-  }
-
-  .ps-xl-2 {
-    padding-left: 0.5rem !important;
-  }
-
-  .ps-xl-3 {
-    padding-left: 1rem !important;
-  }
-
-  .ps-xl-4 {
-    padding-left: 1.5rem !important;
-  }
-
-  .ps-xl-5 {
-    padding-left: 3rem !important;
-  }
-}
-@media (min-width: 1400px) {
-  .d-xxl-inline {
-    display: inline !important;
-  }
-
-  .d-xxl-inline-block {
-    display: inline-block !important;
-  }
-
-  .d-xxl-block {
-    display: block !important;
-  }
-
-  .d-xxl-grid {
-    display: grid !important;
-  }
-
-  .d-xxl-table {
-    display: table !important;
-  }
-
-  .d-xxl-table-row {
-    display: table-row !important;
-  }
-
-  .d-xxl-table-cell {
-    display: table-cell !important;
-  }
-
-  .d-xxl-flex {
-    display: flex !important;
-  }
-
-  .d-xxl-inline-flex {
-    display: inline-flex !important;
-  }
-
-  .d-xxl-none {
-    display: none !important;
-  }
-
-  .flex-xxl-fill {
-    flex: 1 1 auto !important;
-  }
-
-  .flex-xxl-row {
-    flex-direction: row !important;
-  }
-
-  .flex-xxl-column {
-    flex-direction: column !important;
-  }
-
-  .flex-xxl-row-reverse {
-    flex-direction: row-reverse !important;
-  }
-
-  .flex-xxl-column-reverse {
-    flex-direction: column-reverse !important;
-  }
-
-  .flex-xxl-grow-0 {
-    flex-grow: 0 !important;
-  }
-
-  .flex-xxl-grow-1 {
-    flex-grow: 1 !important;
-  }
-
-  .flex-xxl-shrink-0 {
-    flex-shrink: 0 !important;
-  }
-
-  .flex-xxl-shrink-1 {
-    flex-shrink: 1 !important;
-  }
-
-  .flex-xxl-wrap {
-    flex-wrap: wrap !important;
-  }
-
-  .flex-xxl-nowrap {
-    flex-wrap: nowrap !important;
-  }
-
-  .flex-xxl-wrap-reverse {
-    flex-wrap: wrap-reverse !important;
-  }
-
-  .justify-content-xxl-start {
-    justify-content: flex-start !important;
-  }
-
-  .justify-content-xxl-end {
-    justify-content: flex-end !important;
-  }
-
-  .justify-content-xxl-center {
-    justify-content: center !important;
-  }
-
-  .justify-content-xxl-between {
-    justify-content: space-between !important;
-  }
-
-  .justify-content-xxl-around {
-    justify-content: space-around !important;
-  }
-
-  .justify-content-xxl-evenly {
-    justify-content: space-evenly !important;
-  }
-
-  .align-items-xxl-start {
-    align-items: flex-start !important;
-  }
-
-  .align-items-xxl-end {
-    align-items: flex-end !important;
-  }
-
-  .align-items-xxl-center {
-    align-items: center !important;
-  }
-
-  .align-items-xxl-baseline {
-    align-items: baseline !important;
-  }
-
-  .align-items-xxl-stretch {
-    align-items: stretch !important;
-  }
-
-  .align-content-xxl-start {
-    align-content: flex-start !important;
-  }
-
-  .align-content-xxl-end {
-    align-content: flex-end !important;
-  }
-
-  .align-content-xxl-center {
-    align-content: center !important;
-  }
-
-  .align-content-xxl-between {
-    align-content: space-between !important;
-  }
-
-  .align-content-xxl-around {
-    align-content: space-around !important;
-  }
-
-  .align-content-xxl-stretch {
-    align-content: stretch !important;
-  }
-
-  .align-self-xxl-auto {
-    align-self: auto !important;
-  }
-
-  .align-self-xxl-start {
-    align-self: flex-start !important;
-  }
-
-  .align-self-xxl-end {
-    align-self: flex-end !important;
-  }
-
-  .align-self-xxl-center {
-    align-self: center !important;
-  }
-
-  .align-self-xxl-baseline {
-    align-self: baseline !important;
-  }
-
-  .align-self-xxl-stretch {
-    align-self: stretch !important;
-  }
-
-  .order-xxl-first {
-    order: -1 !important;
-  }
-
-  .order-xxl-0 {
-    order: 0 !important;
-  }
-
-  .order-xxl-1 {
-    order: 1 !important;
-  }
-
-  .order-xxl-2 {
-    order: 2 !important;
-  }
-
-  .order-xxl-3 {
-    order: 3 !important;
-  }
-
-  .order-xxl-4 {
-    order: 4 !important;
-  }
-
-  .order-xxl-5 {
-    order: 5 !important;
-  }
-
-  .order-xxl-last {
-    order: 6 !important;
-  }
-
-  .m-xxl-0 {
-    margin: 0 !important;
-  }
-
-  .m-xxl-1 {
-    margin: 0.25rem !important;
-  }
-
-  .m-xxl-2 {
-    margin: 0.5rem !important;
-  }
-
-  .m-xxl-3 {
-    margin: 1rem !important;
-  }
-
-  .m-xxl-4 {
-    margin: 1.5rem !important;
-  }
-
-  .m-xxl-5 {
-    margin: 3rem !important;
-  }
-
-  .m-xxl-auto {
-    margin: auto !important;
-  }
-
-  .mx-xxl-0 {
-    margin-right: 0 !important;
-    margin-left: 0 !important;
-  }
-
-  .mx-xxl-1 {
-    margin-right: 0.25rem !important;
-    margin-left: 0.25rem !important;
-  }
-
-  .mx-xxl-2 {
-    margin-right: 0.5rem !important;
-    margin-left: 0.5rem !important;
-  }
-
-  .mx-xxl-3 {
-    margin-right: 1rem !important;
-    margin-left: 1rem !important;
-  }
-
-  .mx-xxl-4 {
-    margin-right: 1.5rem !important;
-    margin-left: 1.5rem !important;
-  }
-
-  .mx-xxl-5 {
-    margin-right: 3rem !important;
-    margin-left: 3rem !important;
-  }
-
-  .mx-xxl-auto {
-    margin-right: auto !important;
-    margin-left: auto !important;
-  }
-
-  .my-xxl-0 {
-    margin-top: 0 !important;
-    margin-bottom: 0 !important;
-  }
-
-  .my-xxl-1 {
-    margin-top: 0.25rem !important;
-    margin-bottom: 0.25rem !important;
-  }
-
-  .my-xxl-2 {
-    margin-top: 0.5rem !important;
-    margin-bottom: 0.5rem !important;
-  }
-
-  .my-xxl-3 {
-    margin-top: 1rem !important;
-    margin-bottom: 1rem !important;
-  }
-
-  .my-xxl-4 {
-    margin-top: 1.5rem !important;
-    margin-bottom: 1.5rem !important;
-  }
-
-  .my-xxl-5 {
-    margin-top: 3rem !important;
-    margin-bottom: 3rem !important;
-  }
-
-  .my-xxl-auto {
-    margin-top: auto !important;
-    margin-bottom: auto !important;
-  }
-
-  .mt-xxl-0 {
-    margin-top: 0 !important;
-  }
-
-  .mt-xxl-1 {
-    margin-top: 0.25rem !important;
-  }
-
-  .mt-xxl-2 {
-    margin-top: 0.5rem !important;
-  }
-
-  .mt-xxl-3 {
-    margin-top: 1rem !important;
-  }
-
-  .mt-xxl-4 {
-    margin-top: 1.5rem !important;
-  }
-
-  .mt-xxl-5 {
-    margin-top: 3rem !important;
-  }
-
-  .mt-xxl-auto {
-    margin-top: auto !important;
-  }
-
-  .me-xxl-0 {
-    margin-right: 0 !important;
-  }
-
-  .me-xxl-1 {
-    margin-right: 0.25rem !important;
-  }
-
-  .me-xxl-2 {
-    margin-right: 0.5rem !important;
-  }
-
-  .me-xxl-3 {
-    margin-right: 1rem !important;
-  }
-
-  .me-xxl-4 {
-    margin-right: 1.5rem !important;
-  }
-
-  .me-xxl-5 {
-    margin-right: 3rem !important;
-  }
-
-  .me-xxl-auto {
-    margin-right: auto !important;
-  }
-
-  .mb-xxl-0 {
-    margin-bottom: 0 !important;
-  }
-
-  .mb-xxl-1 {
-    margin-bottom: 0.25rem !important;
-  }
-
-  .mb-xxl-2 {
-    margin-bottom: 0.5rem !important;
-  }
-
-  .mb-xxl-3 {
-    margin-bottom: 1rem !important;
-  }
-
-  .mb-xxl-4 {
-    margin-bottom: 1.5rem !important;
-  }
-
-  .mb-xxl-5 {
-    margin-bottom: 3rem !important;
-  }
-
-  .mb-xxl-auto {
-    margin-bottom: auto !important;
-  }
-
-  .ms-xxl-0 {
-    margin-left: 0 !important;
-  }
-
-  .ms-xxl-1 {
-    margin-left: 0.25rem !important;
-  }
-
-  .ms-xxl-2 {
-    margin-left: 0.5rem !important;
-  }
-
-  .ms-xxl-3 {
-    margin-left: 1rem !important;
-  }
-
-  .ms-xxl-4 {
-    margin-left: 1.5rem !important;
-  }
-
-  .ms-xxl-5 {
-    margin-left: 3rem !important;
-  }
-
-  .ms-xxl-auto {
-    margin-left: auto !important;
-  }
-
-  .p-xxl-0 {
-    padding: 0 !important;
-  }
-
-  .p-xxl-1 {
-    padding: 0.25rem !important;
-  }
-
-  .p-xxl-2 {
-    padding: 0.5rem !important;
-  }
-
-  .p-xxl-3 {
-    padding: 1rem !important;
-  }
-
-  .p-xxl-4 {
-    padding: 1.5rem !important;
-  }
-
-  .p-xxl-5 {
-    padding: 3rem !important;
-  }
-
-  .px-xxl-0 {
-    padding-right: 0 !important;
-    padding-left: 0 !important;
-  }
-
-  .px-xxl-1 {
-    padding-right: 0.25rem !important;
-    padding-left: 0.25rem !important;
-  }
-
-  .px-xxl-2 {
-    padding-right: 0.5rem !important;
-    padding-left: 0.5rem !important;
-  }
-
-  .px-xxl-3 {
-    padding-right: 1rem !important;
-    padding-left: 1rem !important;
-  }
-
-  .px-xxl-4 {
-    padding-right: 1.5rem !important;
-    padding-left: 1.5rem !important;
-  }
-
-  .px-xxl-5 {
-    padding-right: 3rem !important;
-    padding-left: 3rem !important;
-  }
-
-  .py-xxl-0 {
-    padding-top: 0 !important;
-    padding-bottom: 0 !important;
-  }
-
-  .py-xxl-1 {
-    padding-top: 0.25rem !important;
-    padding-bottom: 0.25rem !important;
-  }
-
-  .py-xxl-2 {
-    padding-top: 0.5rem !important;
-    padding-bottom: 0.5rem !important;
-  }
-
-  .py-xxl-3 {
-    padding-top: 1rem !important;
-    padding-bottom: 1rem !important;
-  }
-
-  .py-xxl-4 {
-    padding-top: 1.5rem !important;
-    padding-bottom: 1.5rem !important;
-  }
-
-  .py-xxl-5 {
-    padding-top: 3rem !important;
-    padding-bottom: 3rem !important;
-  }
-
-  .pt-xxl-0 {
-    padding-top: 0 !important;
-  }
-
-  .pt-xxl-1 {
-    padding-top: 0.25rem !important;
-  }
-
-  .pt-xxl-2 {
-    padding-top: 0.5rem !important;
-  }
-
-  .pt-xxl-3 {
-    padding-top: 1rem !important;
-  }
-
-  .pt-xxl-4 {
-    padding-top: 1.5rem !important;
-  }
-
-  .pt-xxl-5 {
-    padding-top: 3rem !important;
-  }
-
-  .pe-xxl-0 {
-    padding-right: 0 !important;
-  }
-
-  .pe-xxl-1 {
-    padding-right: 0.25rem !important;
-  }
-
-  .pe-xxl-2 {
-    padding-right: 0.5rem !important;
-  }
-
-  .pe-xxl-3 {
-    padding-right: 1rem !important;
-  }
-
-  .pe-xxl-4 {
-    padding-right: 1.5rem !important;
-  }
-
-  .pe-xxl-5 {
-    padding-right: 3rem !important;
-  }
-
-  .pb-xxl-0 {
-    padding-bottom: 0 !important;
-  }
-
-  .pb-xxl-1 {
-    padding-bottom: 0.25rem !important;
-  }
-
-  .pb-xxl-2 {
-    padding-bottom: 0.5rem !important;
-  }
-
-  .pb-xxl-3 {
-    padding-bottom: 1rem !important;
-  }
-
-  .pb-xxl-4 {
-    padding-bottom: 1.5rem !important;
-  }
-
-  .pb-xxl-5 {
-    padding-bottom: 3rem !important;
-  }
-
-  .ps-xxl-0 {
-    padding-left: 0 !important;
-  }
-
-  .ps-xxl-1 {
-    padding-left: 0.25rem !important;
-  }
-
-  .ps-xxl-2 {
-    padding-left: 0.5rem !important;
-  }
-
-  .ps-xxl-3 {
-    padding-left: 1rem !important;
-  }
-
-  .ps-xxl-4 {
-    padding-left: 1.5rem !important;
-  }
-
-  .ps-xxl-5 {
-    padding-left: 3rem !important;
-  }
-}
-@media print {
-  .d-print-inline {
-    display: inline !important;
-  }
-
-  .d-print-inline-block {
-    display: inline-block !important;
-  }
-
-  .d-print-block {
-    display: block !important;
-  }
-
-  .d-print-grid {
-    display: grid !important;
-  }
-
-  .d-print-table {
-    display: table !important;
-  }
-
-  .d-print-table-row {
-    display: table-row !important;
-  }
-
-  .d-print-table-cell {
-    display: table-cell !important;
-  }
-
-  .d-print-flex {
-    display: flex !important;
-  }
-
-  .d-print-inline-flex {
-    display: inline-flex !important;
-  }
-
-  .d-print-none {
-    display: none !important;
-  }
-}
-
-/*# sourceMappingURL=bootstrap-grid.css.map */
\ No newline at end of file
diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx
index 2a123a76..125219c3 100644
--- a/FrontEnd/src/views/admin/UserAdmin.tsx
+++ b/FrontEnd/src/views/admin/UserAdmin.tsx
@@ -3,7 +3,7 @@ import classnames from "classnames";
 
 import OperationDialog, {
   OperationDialogBoolInput,
-} from "../common/OperationDialog";
+} from "../common/dailog/OperationDialog";
 
 import { AuthUser } from "@/services/user";
 import {
@@ -45,7 +45,7 @@ const CreateUserDialog: React.FC<DialogProps<undefined, HttpUser>> = ({
           password,
         })
       }
-      close={close}
+      onClose={close}
       open={open}
       onSuccessAndClose={onSuccess}
     />
@@ -61,7 +61,7 @@ const UserDeleteDialog: React.FC<DialogProps<{ username: string }, unknown>> =
     return (
       <OperationDialog
         open={open}
-        close={close}
+        onClose={close}
         title="admin:user.dialog.delete.title"
         themeColor="danger"
         inputPrompt={() => (
@@ -86,7 +86,7 @@ const UserModifyDialog: React.FC<
   return (
     <OperationDialog
       open={open}
-      close={close}
+      onClose={close}
       title="admin:user.dialog.modify.title"
       themeColor="danger"
       inputPrompt={() => (
@@ -137,7 +137,7 @@ const UserPermissionModifyDialog: React.FC<
   return (
     <OperationDialog
       open={open}
-      close={close}
+      onClose={close}
       title="admin:user.dialog.modifyPermissions.title"
       themeColor="danger"
       inputPrompt={() => (
diff --git a/FrontEnd/src/views/center/TimelineCreateDialog.tsx b/FrontEnd/src/views/center/TimelineCreateDialog.tsx
index b4e25ba1..4871a5e0 100644
--- a/FrontEnd/src/views/center/TimelineCreateDialog.tsx
+++ b/FrontEnd/src/views/center/TimelineCreateDialog.tsx
@@ -2,7 +2,7 @@ import React from "react";
 import { useHistory } from "react-router";
 
 import { validateTimelineName } from "@/services/timeline";
-import OperationDialog from "../common/OperationDialog";
+import OperationDialog from "../common/dailog/OperationDialog";
 import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline";
 
 interface TimelineCreateDialogProps {
@@ -16,7 +16,7 @@ const TimelineCreateDialog: React.FC<TimelineCreateDialogProps> = (props) => {
   return (
     <OperationDialog
       open={props.open}
-      close={props.close}
+      onClose={props.close}
       themeColor="success"
       title="home.createDialog.title"
       inputScheme={
diff --git a/FrontEnd/src/views/common/ConfirmDialog.tsx b/FrontEnd/src/views/common/ConfirmDialog.tsx
deleted file mode 100644
index 70dc83f5..00000000
--- a/FrontEnd/src/views/common/ConfirmDialog.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { convertI18nText, I18nText } from "@/common";
-import React from "react";
-import { useTranslation } from "react-i18next";
-
-import Button from "./button/Button";
-
-const ConfirmDialog: React.FC<{
-  onClose: () => void;
-  onConfirm: () => void;
-  title: I18nText;
-  body: I18nText;
-}> = ({ onClose, onConfirm, title, body }) => {
-  const { t } = useTranslation();
-
-  return (
-    <Modal onHide={onClose} show centered>
-      <Modal.Header>
-        <Modal.Title className="text-danger">
-          {convertI18nText(title, t)}
-        </Modal.Title>
-      </Modal.Header>
-      <Modal.Body>{convertI18nText(body, t)}</Modal.Body>
-      <Modal.Footer>
-        <Button
-          text="operationDialog.cancel"
-          color="secondary"
-          onClick={onClose}
-        />
-        <Button
-          text="operationDialog.confirm"
-          color="danger"
-          onClick={() => {
-            onConfirm();
-            onClose();
-          }}
-        />
-      </Modal.Footer>
-    </Modal>
-  );
-};
-
-export default ConfirmDialog;
diff --git a/FrontEnd/src/views/common/LoadingButton.tsx b/FrontEnd/src/views/common/LoadingButton.tsx
deleted file mode 100644
index cd9f1adc..00000000
--- a/FrontEnd/src/views/common/LoadingButton.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from "react";
-import { Button, ButtonProps, Spinner } from "react-bootstrap";
-
-const LoadingButton: React.FC<{ loading?: boolean } & ButtonProps> = ({
-  loading,
-  variant,
-  disabled,
-  ...otherProps
-}) => {
-  return (
-    <Button
-      variant={variant != null ? `outline-${variant}` : "outline-primary"}
-      disabled={disabled || loading}
-      {...otherProps}
-    >
-      {otherProps.children}
-      {loading ? (
-        <Spinner
-          className="ms-1"
-          variant={variant}
-          animation="grow"
-          size="sm"
-        />
-      ) : null}
-    </Button>
-  );
-};
-
-export default LoadingButton;
diff --git a/FrontEnd/src/views/common/OperationDialog.tsx b/FrontEnd/src/views/common/OperationDialog.tsx
deleted file mode 100644
index ac4c51b9..00000000
--- a/FrontEnd/src/views/common/OperationDialog.tsx
+++ /dev/null
@@ -1,471 +0,0 @@
-import React, { useState } from "react";
-import { useTranslation } from "react-i18next";
-import { Form, Button, Modal } from "react-bootstrap";
-import { TwitterPicker } from "react-color";
-import moment from "moment";
-
-import { convertI18nText, I18nText, UiLogicError } from "@/common";
-
-import LoadingButton from "./LoadingButton";
-
-interface DefaultErrorPromptProps {
-  error?: string;
-}
-
-const DefaultErrorPrompt: React.FC<DefaultErrorPromptProps> = (props) => {
-  const { t } = useTranslation();
-
-  let result = <p className="text-danger">{t("operationDialog.error")}</p>;
-
-  if (props.error != null) {
-    result = (
-      <>
-        {result}
-        <p className="text-danger">{props.error}</p>
-      </>
-    );
-  }
-
-  return result;
-};
-
-export interface OperationDialogTextInput {
-  type: "text";
-  label?: I18nText;
-  password?: boolean;
-  initValue?: string;
-  textFieldProps?: Omit<
-    React.InputHTMLAttributes<HTMLInputElement>,
-    "type" | "value" | "onChange" | "aria-relevant"
-  >;
-  helperText?: string;
-}
-
-export interface OperationDialogBoolInput {
-  type: "bool";
-  label: I18nText;
-  initValue?: boolean;
-}
-
-export interface OperationDialogSelectInputOption {
-  value: string;
-  label: I18nText;
-  icon?: React.ReactElement;
-}
-
-export interface OperationDialogSelectInput {
-  type: "select";
-  label: I18nText;
-  options: OperationDialogSelectInputOption[];
-  initValue?: string;
-}
-
-export interface OperationDialogColorInput {
-  type: "color";
-  label?: I18nText;
-  initValue?: string | null;
-  canBeNull?: boolean;
-}
-
-export interface OperationDialogDateTimeInput {
-  type: "datetime";
-  label?: I18nText;
-  initValue?: string;
-}
-
-export type OperationDialogInput =
-  | OperationDialogTextInput
-  | OperationDialogBoolInput
-  | OperationDialogSelectInput
-  | OperationDialogColorInput
-  | OperationDialogDateTimeInput;
-
-interface OperationInputTypeStringToValueTypeMap {
-  text: string;
-  bool: boolean;
-  select: string;
-  color: string | null;
-  datetime: string;
-}
-
-type MapOperationInputTypeStringToValueType<Type> =
-  Type extends keyof OperationInputTypeStringToValueTypeMap
-    ? OperationInputTypeStringToValueTypeMap[Type]
-    : never;
-
-type MapOperationInputInfoValueType<T> = T extends OperationDialogInput
-  ? MapOperationInputTypeStringToValueType<T["type"]>
-  : T;
-
-const initValueMapperMap: {
-  [T in OperationDialogInput as T["type"]]: (
-    item: T
-  ) => MapOperationInputInfoValueType<T>;
-} = {
-  bool: (item) => item.initValue ?? false,
-  color: (item) => item.initValue ?? null,
-  datetime: (item) => {
-    if (item.initValue != null) {
-      return moment(item.initValue).format("YYYY-MM-DDTHH:mm:ss");
-    } else {
-      return "";
-    }
-  },
-  select: (item) => item.initValue ?? item.options[0].value,
-  text: (item) => item.initValue ?? "",
-};
-
-type MapOperationInputInfoValueTypeList<
-  Tuple extends readonly OperationDialogInput[]
-> = {
-  [Index in keyof Tuple]: MapOperationInputInfoValueType<Tuple[Index]>;
-} & { length: Tuple["length"] };
-
-export type OperationInputError =
-  | {
-      [index: number]: I18nText | null | undefined;
-    }
-  | null
-  | undefined;
-
-const isNoError = (error: OperationInputError): boolean => {
-  if (error == null) return true;
-  for (const key in error) {
-    if (error[key] != null) return false;
-  }
-  return true;
-};
-
-export interface OperationDialogProps<
-  TData,
-  OperationInputInfoList extends readonly OperationDialogInput[]
-> {
-  open: boolean;
-  close: () => void;
-  title: I18nText | (() => React.ReactNode);
-  themeColor?: "danger" | "success" | string;
-  onProcess: (
-    inputs: MapOperationInputInfoValueTypeList<OperationInputInfoList>
-  ) => Promise<TData>;
-  inputScheme?: OperationInputInfoList;
-  inputValidator?: (
-    inputs: MapOperationInputInfoValueTypeList<OperationInputInfoList>
-  ) => OperationInputError;
-  inputPrompt?: I18nText | (() => React.ReactNode);
-  processPrompt?: () => React.ReactNode;
-  successPrompt?: (data: TData) => React.ReactNode;
-  failurePrompt?: (error: unknown) => React.ReactNode;
-  onSuccessAndClose?: (data: TData) => void;
-}
-
-const OperationDialog = <
-  TData,
-  OperationInputInfoList extends readonly OperationDialogInput[]
->(
-  props: OperationDialogProps<TData, OperationInputInfoList>
-): React.ReactElement => {
-  const inputScheme = (props.inputScheme ??
-    []) as readonly OperationDialogInput[];
-
-  const { t } = useTranslation();
-
-  type Step =
-    | { type: "input" }
-    | { type: "process" }
-    | {
-        type: "success";
-        data: TData;
-      }
-    | {
-        type: "failure";
-        data: unknown;
-      };
-  const [step, setStep] = useState<Step>({ type: "input" });
-
-  type ValueType = boolean | string | null | undefined;
-
-  const [values, setValues] = useState<ValueType[]>(
-    inputScheme.map((item) => {
-      if (item.type in initValueMapperMap) {
-        return (
-          initValueMapperMap[item.type] as (
-            i: OperationDialogInput
-          ) => ValueType
-        )(item);
-      } else {
-        throw new UiLogicError("Unknown input scheme.");
-      }
-    })
-  );
-  const [dirtyList, setDirtyList] = useState<boolean[]>(() =>
-    inputScheme.map(() => false)
-  );
-  const [inputError, setInputError] = useState<OperationInputError>();
-
-  const close = (): void => {
-    if (step.type !== "process") {
-      props.close();
-      if (step.type === "success" && props.onSuccessAndClose) {
-        props.onSuccessAndClose(step.data);
-      }
-    } else {
-      console.log("Attempt to close modal when processing.");
-    }
-  };
-
-  const onConfirm = (): void => {
-    setStep({ type: "process" });
-    props
-      .onProcess(
-        values.map((v, index) => {
-          if (inputScheme[index].type === "datetime" && v !== "")
-            return new Date(v as string).toISOString();
-          else return v;
-        }) as unknown as MapOperationInputInfoValueTypeList<OperationInputInfoList>
-      )
-      .then(
-        (d) => {
-          setStep({
-            type: "success",
-            data: d,
-          });
-        },
-        (e: unknown) => {
-          setStep({
-            type: "failure",
-            data: e,
-          });
-        }
-      );
-  };
-
-  let body: React.ReactNode;
-  if (step.type === "input" || step.type === "process") {
-    const process = step.type === "process";
-
-    let inputPrompt =
-      typeof props.inputPrompt === "function"
-        ? props.inputPrompt()
-        : convertI18nText(props.inputPrompt, t);
-    inputPrompt = <h6>{inputPrompt}</h6>;
-
-    const validate = (values: ValueType[]): boolean => {
-      const { inputValidator } = props;
-      if (inputValidator != null) {
-        const result = inputValidator(
-          values as unknown as MapOperationInputInfoValueTypeList<OperationInputInfoList>
-        );
-        setInputError(result);
-        return isNoError(result);
-      }
-      return true;
-    };
-
-    const updateValue = (index: number, newValue: ValueType): void => {
-      const oldValues = values;
-      const newValues = oldValues.slice();
-      newValues[index] = newValue;
-      setValues(newValues);
-      if (dirtyList[index] === false) {
-        const newDirtyList = dirtyList.slice();
-        newDirtyList[index] = true;
-        setDirtyList(newDirtyList);
-      }
-      validate(newValues);
-    };
-
-    const canProcess = isNoError(inputError);
-
-    body = (
-      <>
-        <Modal.Body>
-          {inputPrompt}
-          {inputScheme.map((item, index) => {
-            const value = values[index];
-            const error: string | null =
-              dirtyList[index] && inputError != null
-                ? convertI18nText(inputError[index], t)
-                : null;
-
-            if (item.type === "text") {
-              return (
-                <Form.Group key={index}>
-                  {item.label && (
-                    <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
-                  )}
-                  <Form.Control
-                    type={item.password === true ? "password" : "text"}
-                    value={value as string}
-                    onChange={(e) => {
-                      const v = e.target.value;
-                      updateValue(index, v);
-                    }}
-                    isInvalid={error != null}
-                    disabled={process}
-                  />
-                  {error != null && (
-                    <Form.Control.Feedback type="invalid">
-                      {error}
-                    </Form.Control.Feedback>
-                  )}
-                  {item.helperText && (
-                    <Form.Text>{t(item.helperText)}</Form.Text>
-                  )}
-                </Form.Group>
-              );
-            } else if (item.type === "bool") {
-              return (
-                <Form.Group key={index}>
-                  <Form.Check<"input">
-                    type="checkbox"
-                    checked={value as boolean}
-                    onChange={(event) => {
-                      updateValue(index, event.currentTarget.checked);
-                    }}
-                    label={convertI18nText(item.label, t)}
-                    disabled={process}
-                  />
-                </Form.Group>
-              );
-            } else if (item.type === "select") {
-              return (
-                <Form.Group key={index}>
-                  <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
-                  <Form.Control
-                    as="select"
-                    value={value as string}
-                    onChange={(event) => {
-                      updateValue(index, event.target.value);
-                    }}
-                    disabled={process}
-                  >
-                    {item.options.map((option, i) => {
-                      return (
-                        <option value={option.value} key={i}>
-                          {option.icon}
-                          {convertI18nText(option.label, t)}
-                        </option>
-                      );
-                    })}
-                  </Form.Control>
-                </Form.Group>
-              );
-            } else if (item.type === "color") {
-              return (
-                <Form.Group key={index}>
-                  {item.canBeNull ? (
-                    <Form.Check<"input">
-                      type="checkbox"
-                      checked={value !== null}
-                      onChange={(event) => {
-                        if (event.currentTarget.checked) {
-                          updateValue(index, "#007bff");
-                        } else {
-                          updateValue(index, null);
-                        }
-                      }}
-                      label={convertI18nText(item.label, t)}
-                      disabled={process}
-                    />
-                  ) : (
-                    <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
-                  )}
-                  {value !== null && (
-                    <TwitterPicker
-                      color={value as string}
-                      onChange={(result) => updateValue(index, result.hex)}
-                    />
-                  )}
-                </Form.Group>
-              );
-            } else if (item.type === "datetime") {
-              return (
-                <Form.Group key={index}>
-                  {item.label && (
-                    <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
-                  )}
-                  <Form.Control
-                    type="datetime-local"
-                    value={value as string}
-                    onChange={(e) => {
-                      const v = e.target.value;
-                      updateValue(index, v);
-                    }}
-                    isInvalid={error != null}
-                    disabled={process}
-                  />
-                  {error != null && (
-                    <Form.Control.Feedback type="invalid">
-                      {error}
-                    </Form.Control.Feedback>
-                  )}
-                </Form.Group>
-              );
-            }
-          })}
-        </Modal.Body>
-        <Modal.Footer>
-          <Button variant="outline-secondary" onClick={close}>
-            {t("operationDialog.cancel")}
-          </Button>
-          <LoadingButton
-            variant={props.themeColor}
-            loading={process}
-            disabled={!canProcess}
-            onClick={() => {
-              setDirtyList(inputScheme.map(() => true));
-              if (validate(values)) {
-                onConfirm();
-              }
-            }}
-          >
-            {t("operationDialog.confirm")}
-          </LoadingButton>
-        </Modal.Footer>
-      </>
-    );
-  } else {
-    let content: React.ReactNode;
-    const result = step;
-    if (result.type === "success") {
-      content =
-        props.successPrompt?.(result.data) ?? t("operationDialog.success");
-      if (typeof content === "string")
-        content = <p className="text-success">{content}</p>;
-    } else {
-      content = props.failurePrompt?.(result.data) ?? <DefaultErrorPrompt />;
-      if (typeof content === "string")
-        content = <DefaultErrorPrompt error={content} />;
-    }
-    body = (
-      <>
-        <Modal.Body>{content}</Modal.Body>
-        <Modal.Footer>
-          <Button variant="primary" onClick={close}>
-            {t("operationDialog.ok")}
-          </Button>
-        </Modal.Footer>
-      </>
-    );
-  }
-
-  const title =
-    typeof props.title === "function"
-      ? props.title()
-      : convertI18nText(props.title, t);
-
-  return (
-    <Modal show={props.open} onHide={close}>
-      <Modal.Header
-        className={
-          props.themeColor != null ? "text-" + props.themeColor : undefined
-        }
-      >
-        {title}
-      </Modal.Header>
-      {body}
-    </Modal>
-  );
-};
-
-export default OperationDialog;
diff --git a/FrontEnd/src/views/common/ToggleIconButton.tsx b/FrontEnd/src/views/common/ToggleIconButton.tsx
deleted file mode 100644
index c4d2d132..00000000
--- a/FrontEnd/src/views/common/ToggleIconButton.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from "react";
-import classnames from "classnames";
-
-export interface ToggleIconButtonProps
-  extends React.HTMLAttributes<HTMLElement> {
-  state: boolean;
-  trueIconClassName: string;
-  falseIconClassName: string;
-}
-
-const ToggleIconButton: React.FC<ToggleIconButtonProps> = ({
-  state,
-  className,
-  trueIconClassName,
-  falseIconClassName,
-  ...otherProps
-}) => {
-  return (
-    <i
-      className={classnames(
-        state ? trueIconClassName : falseIconClassName,
-        "icon-button",
-        className
-      )}
-      {...otherProps}
-    />
-  );
-};
-
-export default ToggleIconButton;
diff --git a/FrontEnd/src/views/common/button/LoadingButton.tsx b/FrontEnd/src/views/common/button/LoadingButton.tsx
new file mode 100644
index 00000000..fd1c19b3
--- /dev/null
+++ b/FrontEnd/src/views/common/button/LoadingButton.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+const LoadingButton: React.FC<{ loading?: boolean } & ButtonProps> = ({
+  loading,
+  variant,
+  disabled,
+  ...otherProps
+}) => {
+  return (
+    <Button
+      variant={variant != null ? `outline-${variant}` : "outline-primary"}
+      disabled={disabled || loading}
+      {...otherProps}
+    >
+      {otherProps.children}
+      {loading ? (
+        <Spinner
+          className="ms-1"
+          variant={variant}
+          animation="grow"
+          size="sm"
+        />
+      ) : null}
+    </Button>
+  );
+};
+
+export default LoadingButton;
diff --git a/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx b/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx
new file mode 100644
index 00000000..1ad52350
--- /dev/null
+++ b/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx
@@ -0,0 +1,40 @@
+import { convertI18nText, I18nText } from "@/common";
+import React from "react";
+import { useTranslation } from "react-i18next";
+
+import Button from "../button/Button";
+import Dialog from "./Dialog";
+
+const ConfirmDialog: React.FC<{
+  open?: boolean;
+  onClose: () => void;
+  onConfirm: () => void;
+  title: I18nText;
+  body: I18nText;
+}> = ({ open, onClose, onConfirm, title, body }) => {
+  const { t } = useTranslation();
+
+  return (
+    <Dialog onClose={onClose} open={open}>
+      <h3 className="text-danger">{convertI18nText(title, t)}</h3>
+      <p>{convertI18nText(body, t)}</p>
+      <div>
+        <Button
+          text="operationDialog.cancel"
+          color="secondary"
+          onClick={onClose}
+        />
+        <Button
+          text="operationDialog.confirm"
+          color="danger"
+          onClick={() => {
+            onConfirm();
+            onClose();
+          }}
+        />
+      </div>
+    </Dialog>
+  );
+};
+
+export default ConfirmDialog;
diff --git a/FrontEnd/src/views/common/dailog/Dialog.css b/FrontEnd/src/views/common/dailog/Dialog.css
new file mode 100644
index 00000000..e69de29b
diff --git a/FrontEnd/src/views/common/dailog/Dialog.tsx b/FrontEnd/src/views/common/dailog/Dialog.tsx
new file mode 100644
index 00000000..5a3902c4
--- /dev/null
+++ b/FrontEnd/src/views/common/dailog/Dialog.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+
+export interface DialogProps {
+  onClose: () => void;
+  open?: boolean;
+  children?: React.ReactNode;
+}
+
+export default function Dialog(props: DialogProps): React.ReactElement | null {
+  const { open, onClose, children } = props;
+
+  return <div>{children}</div>;
+}
diff --git a/FrontEnd/src/views/common/dailog/OperationDialog.tsx b/FrontEnd/src/views/common/dailog/OperationDialog.tsx
new file mode 100644
index 00000000..129e85d5
--- /dev/null
+++ b/FrontEnd/src/views/common/dailog/OperationDialog.tsx
@@ -0,0 +1,472 @@
+import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { TwitterPicker } from "react-color";
+import moment from "moment";
+
+import { convertI18nText, I18nText, UiLogicError } from "@/common";
+
+import Button from "../button/Button";
+import LoadingButton from "../button/LoadingButton";
+import Dialog from "./Dialog";
+
+interface DefaultErrorPromptProps {
+  error?: string;
+}
+
+const DefaultErrorPrompt: React.FC<DefaultErrorPromptProps> = (props) => {
+  const { t } = useTranslation();
+
+  let result = <p className="text-danger">{t("operationDialog.error")}</p>;
+
+  if (props.error != null) {
+    result = (
+      <>
+        {result}
+        <p className="text-danger">{props.error}</p>
+      </>
+    );
+  }
+
+  return result;
+};
+
+export interface OperationDialogTextInput {
+  type: "text";
+  label?: I18nText;
+  password?: boolean;
+  initValue?: string;
+  textFieldProps?: Omit<
+    React.InputHTMLAttributes<HTMLInputElement>,
+    "type" | "value" | "onChange" | "aria-relevant"
+  >;
+  helperText?: string;
+}
+
+export interface OperationDialogBoolInput {
+  type: "bool";
+  label: I18nText;
+  initValue?: boolean;
+}
+
+export interface OperationDialogSelectInputOption {
+  value: string;
+  label: I18nText;
+  icon?: React.ReactElement;
+}
+
+export interface OperationDialogSelectInput {
+  type: "select";
+  label: I18nText;
+  options: OperationDialogSelectInputOption[];
+  initValue?: string;
+}
+
+export interface OperationDialogColorInput {
+  type: "color";
+  label?: I18nText;
+  initValue?: string | null;
+  canBeNull?: boolean;
+}
+
+export interface OperationDialogDateTimeInput {
+  type: "datetime";
+  label?: I18nText;
+  initValue?: string;
+}
+
+export type OperationDialogInput =
+  | OperationDialogTextInput
+  | OperationDialogBoolInput
+  | OperationDialogSelectInput
+  | OperationDialogColorInput
+  | OperationDialogDateTimeInput;
+
+interface OperationInputTypeStringToValueTypeMap {
+  text: string;
+  bool: boolean;
+  select: string;
+  color: string | null;
+  datetime: string;
+}
+
+type MapOperationInputTypeStringToValueType<Type> =
+  Type extends keyof OperationInputTypeStringToValueTypeMap
+    ? OperationInputTypeStringToValueTypeMap[Type]
+    : never;
+
+type MapOperationInputInfoValueType<T> = T extends OperationDialogInput
+  ? MapOperationInputTypeStringToValueType<T["type"]>
+  : T;
+
+const initValueMapperMap: {
+  [T in OperationDialogInput as T["type"]]: (
+    item: T
+  ) => MapOperationInputInfoValueType<T>;
+} = {
+  bool: (item) => item.initValue ?? false,
+  color: (item) => item.initValue ?? null,
+  datetime: (item) => {
+    if (item.initValue != null) {
+      return moment(item.initValue).format("YYYY-MM-DDTHH:mm:ss");
+    } else {
+      return "";
+    }
+  },
+  select: (item) => item.initValue ?? item.options[0].value,
+  text: (item) => item.initValue ?? "",
+};
+
+type MapOperationInputInfoValueTypeList<
+  Tuple extends readonly OperationDialogInput[]
+> = {
+  [Index in keyof Tuple]: MapOperationInputInfoValueType<Tuple[Index]>;
+} & { length: Tuple["length"] };
+
+export type OperationInputError =
+  | {
+      [index: number]: I18nText | null | undefined;
+    }
+  | null
+  | undefined;
+
+const isNoError = (error: OperationInputError): boolean => {
+  if (error == null) return true;
+  for (const key in error) {
+    if (error[key] != null) return false;
+  }
+  return true;
+};
+
+export interface OperationDialogProps<
+  TData,
+  OperationInputInfoList extends readonly OperationDialogInput[]
+> {
+  open: boolean;
+  onClose: () => void;
+  title: I18nText | (() => React.ReactNode);
+  themeColor?: "danger" | "success" | string;
+  onProcess: (
+    inputs: MapOperationInputInfoValueTypeList<OperationInputInfoList>
+  ) => Promise<TData>;
+  inputScheme?: OperationInputInfoList;
+  inputValidator?: (
+    inputs: MapOperationInputInfoValueTypeList<OperationInputInfoList>
+  ) => OperationInputError;
+  inputPrompt?: I18nText | (() => React.ReactNode);
+  processPrompt?: () => React.ReactNode;
+  successPrompt?: (data: TData) => React.ReactNode;
+  failurePrompt?: (error: unknown) => React.ReactNode;
+  onSuccessAndClose?: (data: TData) => void;
+}
+
+const OperationDialog = <
+  TData,
+  OperationInputInfoList extends readonly OperationDialogInput[]
+>(
+  props: OperationDialogProps<TData, OperationInputInfoList>
+): React.ReactElement => {
+  const inputScheme = (props.inputScheme ??
+    []) as readonly OperationDialogInput[];
+
+  const { t } = useTranslation();
+
+  type Step =
+    | { type: "input" }
+    | { type: "process" }
+    | {
+        type: "success";
+        data: TData;
+      }
+    | {
+        type: "failure";
+        data: unknown;
+      };
+  const [step, setStep] = useState<Step>({ type: "input" });
+
+  type ValueType = boolean | string | null | undefined;
+
+  const [values, setValues] = useState<ValueType[]>(
+    inputScheme.map((item) => {
+      if (item.type in initValueMapperMap) {
+        return (
+          initValueMapperMap[item.type] as (
+            i: OperationDialogInput
+          ) => ValueType
+        )(item);
+      } else {
+        throw new UiLogicError("Unknown input scheme.");
+      }
+    })
+  );
+  const [dirtyList, setDirtyList] = useState<boolean[]>(() =>
+    inputScheme.map(() => false)
+  );
+  const [inputError, setInputError] = useState<OperationInputError>();
+
+  const close = (): void => {
+    if (step.type !== "process") {
+      props.onClose();
+      if (step.type === "success" && props.onSuccessAndClose) {
+        props.onSuccessAndClose(step.data);
+      }
+    } else {
+      console.log("Attempt to close modal when processing.");
+    }
+  };
+
+  const onConfirm = (): void => {
+    setStep({ type: "process" });
+    props
+      .onProcess(
+        values.map((v, index) => {
+          if (inputScheme[index].type === "datetime" && v !== "")
+            return new Date(v as string).toISOString();
+          else return v;
+        }) as unknown as MapOperationInputInfoValueTypeList<OperationInputInfoList>
+      )
+      .then(
+        (d) => {
+          setStep({
+            type: "success",
+            data: d,
+          });
+        },
+        (e: unknown) => {
+          setStep({
+            type: "failure",
+            data: e,
+          });
+        }
+      );
+  };
+
+  let body: React.ReactNode;
+  if (step.type === "input" || step.type === "process") {
+    const process = step.type === "process";
+
+    let inputPrompt =
+      typeof props.inputPrompt === "function"
+        ? props.inputPrompt()
+        : convertI18nText(props.inputPrompt, t);
+    inputPrompt = <h6>{inputPrompt}</h6>;
+
+    const validate = (values: ValueType[]): boolean => {
+      const { inputValidator } = props;
+      if (inputValidator != null) {
+        const result = inputValidator(
+          values as unknown as MapOperationInputInfoValueTypeList<OperationInputInfoList>
+        );
+        setInputError(result);
+        return isNoError(result);
+      }
+      return true;
+    };
+
+    const updateValue = (index: number, newValue: ValueType): void => {
+      const oldValues = values;
+      const newValues = oldValues.slice();
+      newValues[index] = newValue;
+      setValues(newValues);
+      if (dirtyList[index] === false) {
+        const newDirtyList = dirtyList.slice();
+        newDirtyList[index] = true;
+        setDirtyList(newDirtyList);
+      }
+      validate(newValues);
+    };
+
+    const canProcess = isNoError(inputError);
+
+    body = (
+      <>
+        <div>
+          {inputPrompt}
+          {inputScheme.map((item, index) => {
+            const value = values[index];
+            const error: string | null =
+              dirtyList[index] && inputError != null
+                ? convertI18nText(inputError[index], t)
+                : null;
+
+            if (item.type === "text") {
+              return (
+                <Form.Group key={index}>
+                  {item.label && (
+                    <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
+                  )}
+                  <Form.Control
+                    type={item.password === true ? "password" : "text"}
+                    value={value as string}
+                    onChange={(e) => {
+                      const v = e.target.value;
+                      updateValue(index, v);
+                    }}
+                    isInvalid={error != null}
+                    disabled={process}
+                  />
+                  {error != null && (
+                    <Form.Control.Feedback type="invalid">
+                      {error}
+                    </Form.Control.Feedback>
+                  )}
+                  {item.helperText && (
+                    <Form.Text>{t(item.helperText)}</Form.Text>
+                  )}
+                </Form.Group>
+              );
+            } else if (item.type === "bool") {
+              return (
+                <Form.Group key={index}>
+                  <Form.Check<"input">
+                    type="checkbox"
+                    checked={value as boolean}
+                    onChange={(event) => {
+                      updateValue(index, event.currentTarget.checked);
+                    }}
+                    label={convertI18nText(item.label, t)}
+                    disabled={process}
+                  />
+                </Form.Group>
+              );
+            } else if (item.type === "select") {
+              return (
+                <Form.Group key={index}>
+                  <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
+                  <Form.Control
+                    as="select"
+                    value={value as string}
+                    onChange={(event) => {
+                      updateValue(index, event.target.value);
+                    }}
+                    disabled={process}
+                  >
+                    {item.options.map((option, i) => {
+                      return (
+                        <option value={option.value} key={i}>
+                          {option.icon}
+                          {convertI18nText(option.label, t)}
+                        </option>
+                      );
+                    })}
+                  </Form.Control>
+                </Form.Group>
+              );
+            } else if (item.type === "color") {
+              return (
+                <Form.Group key={index}>
+                  {item.canBeNull ? (
+                    <Form.Check<"input">
+                      type="checkbox"
+                      checked={value !== null}
+                      onChange={(event) => {
+                        if (event.currentTarget.checked) {
+                          updateValue(index, "#007bff");
+                        } else {
+                          updateValue(index, null);
+                        }
+                      }}
+                      label={convertI18nText(item.label, t)}
+                      disabled={process}
+                    />
+                  ) : (
+                    <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
+                  )}
+                  {value !== null && (
+                    <TwitterPicker
+                      color={value as string}
+                      onChange={(result) => updateValue(index, result.hex)}
+                    />
+                  )}
+                </Form.Group>
+              );
+            } else if (item.type === "datetime") {
+              return (
+                <Form.Group key={index}>
+                  {item.label && (
+                    <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
+                  )}
+                  <Form.Control
+                    type="datetime-local"
+                    value={value as string}
+                    onChange={(e) => {
+                      const v = e.target.value;
+                      updateValue(index, v);
+                    }}
+                    isInvalid={error != null}
+                    disabled={process}
+                  />
+                  {error != null && (
+                    <Form.Control.Feedback type="invalid">
+                      {error}
+                    </Form.Control.Feedback>
+                  )}
+                </Form.Group>
+              );
+            }
+          })}
+        </div>
+        <div>
+          <Button
+            text="operationDialog.cancel"
+            color="secondary"
+            onClick={close}
+          />
+          <LoadingButton
+            variant={props.themeColor}
+            loading={process}
+            disabled={!canProcess}
+            onClick={() => {
+              setDirtyList(inputScheme.map(() => true));
+              if (validate(values)) {
+                onConfirm();
+              }
+            }}
+          >
+            {t("operationDialog.confirm")}
+          </LoadingButton>
+        </div>
+      </>
+    );
+  } else {
+    let content: React.ReactNode;
+    const result = step;
+    if (result.type === "success") {
+      content =
+        props.successPrompt?.(result.data) ?? t("operationDialog.success");
+      if (typeof content === "string")
+        content = <p className="text-success">{content}</p>;
+    } else {
+      content = props.failurePrompt?.(result.data) ?? <DefaultErrorPrompt />;
+      if (typeof content === "string")
+        content = <DefaultErrorPrompt error={content} />;
+    }
+    body = (
+      <>
+        <div>{content}</div>
+        <div>
+          <Button text="operationDialog.ok" color="primary" onClick={close} />
+        </div>
+      </>
+    );
+  }
+
+  const title =
+    typeof props.title === "function"
+      ? props.title()
+      : convertI18nText(props.title, t);
+
+  return (
+    <Dialog open={props.open} onClose={close}>
+      <h3
+        className={
+          props.themeColor != null ? "text-" + props.themeColor : undefined
+        }
+      >
+        {title}
+      </h3>
+      {body}
+    </Dialog>
+  );
+};
+
+export default OperationDialog;
diff --git a/FrontEnd/src/views/login/index.tsx b/FrontEnd/src/views/login/index.tsx
index 55bd2f8c..89aeb47a 100644
--- a/FrontEnd/src/views/login/index.tsx
+++ b/FrontEnd/src/views/login/index.tsx
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
 import { useUser, userService } from "@/services/user";
 
 import AppBar from "../common/AppBar";
-import LoadingButton from "../common/LoadingButton";
+import LoadingButton from "../common/button/LoadingButton";
 
 import "./index.css";
 
diff --git a/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx b/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx
index 1baab1cc..0bf51c21 100644
--- a/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx
+++ b/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx
@@ -10,6 +10,7 @@ import { getHttpUserClient } from "@/http/user";
 
 import ImageCropper, { Clip, applyClipToImage } from "../common/ImageCropper";
 import Button from "../common/button/Button";
+import Dialog from "../common/dailog/Dialog";
 
 export interface ChangeAvatarDialogProps {
   open: boolean;
@@ -159,29 +160,27 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
   };
 
   return (
-    <Modal show={props.open} onHide={close}>
-      <Modal.Header>
-        <Modal.Title> {t("settings.dialogChangeAvatar.title")}</Modal.Title>
-      </Modal.Header>
+    <Dialog open={props.open} onClose={close}>
+      <h3>{t("settings.dialogChangeAvatar.title")}</h3>
       {(() => {
         if (state === "select") {
           return (
             <>
-              <Modal.Body className="container">
+              <div className="container">
                 <div className="row">
                   {t("settings.dialogChangeAvatar.prompt.select")}
                 </div>
                 <div className="row">
                   <input type="file" accept="image/*" onChange={onSelectFile} />
                 </div>
-              </Modal.Body>
-              <Modal.Footer>
+              </div>
+              <div>
                 <Button
                   text="operationDialog.cancel"
                   color="secondary"
                   onClick={close}
                 />
-              </Modal.Footer>
+              </div>
             </>
           );
         } else if (state === "crop") {
@@ -190,7 +189,7 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
           }
           return (
             <>
-              <Modal.Body className="container">
+              <div className="container">
                 <div className="row justify-content-center">
                   <ImageCropper
                     clip={clip}
@@ -202,8 +201,8 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
                 <div className="row">
                   {t("settings.dialogChangeAvatar.prompt.crop")}
                 </div>
-              </Modal.Body>
-              <Modal.Footer>
+              </div>
+              <div>
                 <Button
                   text="operationDialog.cancel"
                   color="secondary"
@@ -222,18 +221,18 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
                     cropImgElement == null || clip == null || clip.width === 0
                   }
                 />
-              </Modal.Footer>
+              </div>
             </>
           );
         } else if (state === "processcrop") {
           return (
             <>
-              <Modal.Body className="container">
+              <div className="container">
                 <div className="row">
                   {t("settings.dialogChangeAvatar.prompt.processingCrop")}
                 </div>
-              </Modal.Body>
-              <Modal.Footer>
+              </div>
+              <div>
                 <Button
                   text="operationDialog.cancel"
                   color="secondary"
@@ -244,19 +243,19 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
                   color="secondary"
                   onClick={onPreviewPrevious}
                 />
-              </Modal.Footer>
+              </div>
             </>
           );
         } else if (state === "preview") {
           return (
             <>
-              <Modal.Body className="container">
+              <div className="container">
                 {createPreviewRow()}
                 <div className="row">
                   {t("settings.dialogChangeAvatar.prompt.preview")}
                 </div>
-              </Modal.Body>
-              <Modal.Footer>
+              </div>
+              <div>
                 <Button
                   text="operationDialog.cancel"
                   color="secondary"
@@ -272,46 +271,46 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
                   color="primary"
                   onClick={upload}
                 />
-              </Modal.Footer>
+              </div>
             </>
           );
         } else if (state === "uploading") {
           return (
             <>
-              <Modal.Body className="container">
+              <div className="container">
                 {createPreviewRow()}
                 <div className="row">
                   {t("settings.dialogChangeAvatar.prompt.uploading")}
                 </div>
-              </Modal.Body>
-              <Modal.Footer></Modal.Footer>
+              </div>
+              <div></div>
             </>
           );
         } else if (state === "success") {
           return (
             <>
-              <Modal.Body className="container">
+              <div className="container">
                 <div className="row p-4 text-success">
                   {t("operationDialog.success")}
                 </div>
-              </Modal.Body>
-              <Modal.Footer>
+              </div>
+              <div>
                 <Button
                   text="operationDialog.ok"
                   color="success"
                   onClick={close}
                 />
-              </Modal.Footer>
+              </div>
             </>
           );
         } else {
           return (
             <>
-              <Modal.Body className="container">
+              <div className="container">
                 {createPreviewRow()}
                 <div className="row text-danger">{trueMessage}</div>
-              </Modal.Body>
-              <Modal.Footer>
+              </div>
+              <div>
                 <Button
                   text="operationDialog.cancel"
                   color="secondary"
@@ -322,12 +321,12 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
                   color="primary"
                   onClick={upload}
                 />
-              </Modal.Footer>
+              </div>
             </>
           );
         }
       })()}
-    </Modal>
+    </Dialog>
   );
 };
 
diff --git a/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx b/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx
index 4b44cdd6..605796ca 100644
--- a/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx
+++ b/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx
@@ -2,7 +2,7 @@ import { getHttpUserClient } from "@/http/user";
 import { useUserLoggedIn } from "@/services/user";
 import React from "react";
 
-import OperationDialog from "../common/OperationDialog";
+import OperationDialog from "../common/dailog/OperationDialog";
 
 export interface ChangeNicknameDialogProps {
   open: boolean;
@@ -24,7 +24,7 @@ const ChangeNicknameDialog: React.FC<ChangeNicknameDialogProps> = (props) => {
           nickname: newNickname,
         });
       }}
-      close={props.close}
+      onClose={props.close}
     />
   );
 };
diff --git a/FrontEnd/src/views/settings/ChangePasswordDialog.tsx b/FrontEnd/src/views/settings/ChangePasswordDialog.tsx
index 21eeeb09..944fdaed 100644
--- a/FrontEnd/src/views/settings/ChangePasswordDialog.tsx
+++ b/FrontEnd/src/views/settings/ChangePasswordDialog.tsx
@@ -3,7 +3,7 @@ import { useHistory } from "react-router";
 
 import { userService } from "@/services/user";
 
-import OperationDialog from "../common/OperationDialog";
+import OperationDialog from "../common/dailog/OperationDialog";
 
 export interface ChangePasswordDialogProps {
   open: boolean;
@@ -55,7 +55,7 @@ const ChangePasswordDialog: React.FC<ChangePasswordDialogProps> = (props) => {
         await userService.changePassword(oldPassword, newPassword);
         setRedirect(true);
       }}
-      close={() => {
+      onClose={() => {
         props.close();
         if (redirect) {
           history.push("/login");
diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx
index 005da933..0e43cb6e 100644
--- a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx
+++ b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx
@@ -9,7 +9,7 @@ import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline";
 import FlatButton from "../common/button/FlatButton";
 import TabPages from "../common/TabPages";
 import TimelinePostBuilder from "@/services/TimelinePostBuilder";
-import ConfirmDialog from "../common/ConfirmDialog";
+import ConfirmDialog from "../common/dailog/ConfirmDialog";
 
 export interface MarkdownPostEditProps {
   timeline: string;
diff --git a/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx b/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx
index 001e52d7..988124b6 100644
--- a/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx
+++ b/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx
@@ -2,7 +2,7 @@ import React from "react";
 
 import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline";
 
-import OperationDialog from "../common/OperationDialog";
+import OperationDialog from "../common/dailog/OperationDialog";
 
 function PostPropertyChangeDialog(props: {
   onClose: () => void;
@@ -14,7 +14,7 @@ function PostPropertyChangeDialog(props: {
   return (
     <OperationDialog
       title="timeline.changePostPropertyDialog.title"
-      close={onClose}
+      onClose={onClose}
       open
       inputScheme={[
         {
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx
index b522f288..06e508e6 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx
+++ b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx
@@ -15,7 +15,7 @@ import { pushAlert } from "@/services/alert";
 import { base64 } from "@/http/common";
 
 import BlobImage from "../common/BlobImage";
-import LoadingButton from "../common/LoadingButton";
+import LoadingButton from "../common/button/LoadingButton";
 import { PopupMenu } from "../common/Menu";
 import Card from "../common/Card";
 import MarkdownPostEdit from "./MarkdownPostEdit";
diff --git a/FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx b/FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx
index 70f72025..64daa19b 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx
+++ b/FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx
@@ -8,7 +8,7 @@ import {
   TimelineVisibility,
 } from "@/http/timeline";
 
-import OperationDialog from "../common/OperationDialog";
+import OperationDialog from "../common/dailog/OperationDialog";
 
 export interface TimelinePropertyChangeDialogProps {
   open: boolean;
@@ -60,7 +60,7 @@ const TimelinePropertyChangeDialog: React.FC<TimelinePropertyChangeDialogProps>
           ] as const
         }
         open={props.open}
-        close={props.close}
+        onClose={props.close}
         onProcess={([newTitle, newVisibility, newDescription, newColor]) => {
           const req: HttpTimelinePatchRequest = {};
           if (newTitle !== timeline.title) {
diff --git a/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx b/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx
index dbca62ca..aedf4f29 100644
--- a/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx
+++ b/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx
@@ -4,7 +4,7 @@ import { Trans } from "react-i18next";
 
 import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline";
 
-import OperationDialog from "../common/OperationDialog";
+import OperationDialog from "../common/dailog/OperationDialog";
 
 interface TimelineDeleteDialog {
   timeline: HttpTimelineInfo;
@@ -20,7 +20,7 @@ const TimelineDeleteDialog: React.FC<TimelineDeleteDialog> = (props) => {
   return (
     <OperationDialog
       open={props.open}
-      close={props.close}
+      onClose={props.close}
       title="timeline.deleteDialog.title"
       themeColor="danger"
       inputPrompt={() => {
-- 
cgit v1.2.3


From a168336c0761b263ee5371218cbf6da236c0acce Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Sat, 26 Jun 2021 19:13:19 +0800
Subject: ...

---
 FrontEnd/src/views/admin/UserAdmin.tsx             |   9 +-
 FrontEnd/src/views/common/Menu.tsx                 |  26 ++---
 FrontEnd/src/views/common/SearchInput.tsx          |  13 +--
 FrontEnd/src/views/common/Spinner.tsx              |  13 +++
 FrontEnd/src/views/common/TabPages.tsx             |  20 +---
 FrontEnd/src/views/common/alert/AlertHost.tsx      |  11 +-
 FrontEnd/src/views/common/button/LoadingButton.tsx |  23 ++---
 .../src/views/common/dailog/OperationDialog.tsx    |  71 ++++++-------
 FrontEnd/src/views/login/index.tsx                 | 114 ++++++++++-----------
 .../src/views/timeline-common/MarkdownPostEdit.tsx |   8 +-
 .../src/views/timeline-common/TimelineMember.tsx   |  21 ++--
 .../views/timeline-common/TimelinePageTemplate.tsx |   1 -
 .../TimelinePostDeleteConfirmDialog.tsx            |  40 --------
 .../src/views/timeline-common/TimelinePostEdit.tsx |   1 -
 .../src/views/timeline-common/TimelinePostView.tsx |   6 +-
 15 files changed, 142 insertions(+), 235 deletions(-)
 create mode 100644 FrontEnd/src/views/common/Spinner.tsx
 delete mode 100644 FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx

(limited to 'FrontEnd/src/views/admin/UserAdmin.tsx')

diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx
index 125219c3..481db1cc 100644
--- a/FrontEnd/src/views/admin/UserAdmin.tsx
+++ b/FrontEnd/src/views/admin/UserAdmin.tsx
@@ -15,6 +15,7 @@ import {
 import { Trans, useTranslation } from "react-i18next";
 import Button from "../common/button/Button";
 import TextButton from "../common/button/TextButton";
+import Spinner from "../common/Spinner";
 
 interface DialogProps<TData = undefined, TReturn = undefined> {
   open: boolean;
@@ -203,7 +204,7 @@ const UserItem: React.FC<UserItemProps> = ({ user, on }) => {
   const [editMaskVisible, setEditMaskVisible] = React.useState<boolean>(false);
 
   return (
-    <ListGroup.Item className="admin-user-item">
+    <div className="admin-user-item">
       <i
         className="bi-pencil-square float-end icon-button text-warning"
         onClick={() => setEditMaskVisible(true)}
@@ -242,7 +243,7 @@ const UserItem: React.FC<UserItemProps> = ({ user, on }) => {
           onClick={on[kDelete]}
         />
       </div>
-    </ListGroup.Item>
+    </div>
   );
 };
 
@@ -251,8 +252,6 @@ interface UserAdminProps {
 }
 
 const UserAdmin: React.FC<UserAdminProps> = () => {
-  const { t } = useTranslation();
-
   type DialogInfo =
     | null
     | {
@@ -390,7 +389,7 @@ const UserAdmin: React.FC<UserAdminProps> = () => {
       </>
     );
   } else {
-    return <Spinner animation="border" />;
+    return <Spinner />;
   }
 };
 
diff --git a/FrontEnd/src/views/common/Menu.tsx b/FrontEnd/src/views/common/Menu.tsx
index ae73a331..a5d2ec2c 100644
--- a/FrontEnd/src/views/common/Menu.tsx
+++ b/FrontEnd/src/views/common/Menu.tsx
@@ -1,9 +1,9 @@
 import React from "react";
 import classnames from "classnames";
-import { OverlayTrigger, OverlayTriggerProps, Popover } from "react-bootstrap";
 import { useTranslation } from "react-i18next";
 
-import { BootstrapThemeColor, convertI18nText, I18nText } from "@/common";
+import { convertI18nText, I18nText } from "@/common";
+import { PaletteColorType } from "@/palette";
 
 export type MenuItem =
   | {
@@ -13,7 +13,7 @@ export type MenuItem =
       type: "button";
       text: I18nText;
       iconClassName?: string;
-      color?: BootstrapThemeColor;
+      color?: PaletteColorType;
       onClick: () => void;
     };
 
@@ -67,26 +67,14 @@ export default Menu;
 
 export interface PopupMenuProps {
   items: MenuItems;
-  children: OverlayTriggerProps["children"];
+  children: React.ReactElement;
 }
 
 export const PopupMenu: React.FC<PopupMenuProps> = ({ items, children }) => {
   const [show, setShow] = React.useState<boolean>(false);
   const toggle = (): void => setShow(!show);
 
-  return (
-    <OverlayTrigger
-      trigger="click"
-      rootClose
-      overlay={
-        <Popover id="menu-popover">
-          <Menu items={items} onItemClicked={() => setShow(false)} />
-        </Popover>
-      }
-      show={show}
-      onToggle={toggle}
-    >
-      {children}
-    </OverlayTrigger>
-  );
+  // TODO:
+
+  return <Menu items={items} onItemClicked={() => setShow(false)} />;
 };
diff --git a/FrontEnd/src/views/common/SearchInput.tsx b/FrontEnd/src/views/common/SearchInput.tsx
index 79eb2732..fff11b95 100644
--- a/FrontEnd/src/views/common/SearchInput.tsx
+++ b/FrontEnd/src/views/common/SearchInput.tsx
@@ -1,7 +1,8 @@
 import React, { useCallback } from "react";
 import classnames from "classnames";
 import { useTranslation } from "react-i18next";
-import { Spinner, Form, Button } from "react-bootstrap";
+
+import LoadingButton from "./button/LoadingButton";
 
 export interface SearchInputProps {
   value: string;
@@ -64,13 +65,9 @@ const SearchInput: React.FC<SearchInputProps> = (props) => {
           "flex-shrink-0"
         )}
       >
-        {props.loading ? (
-          <Spinner variant="primary" animation="border" />
-        ) : (
-          <Button variant="outline-primary" onClick={props.onButtonClick}>
-            {props.buttonText ?? t("search")}
-          </Button>
-        )}
+        <LoadingButton loading={props.loading} onClick={props.onButtonClick}>
+          {props.buttonText ?? t("search")}
+        </LoadingButton>
       </div>
     </div>
   );
diff --git a/FrontEnd/src/views/common/Spinner.tsx b/FrontEnd/src/views/common/Spinner.tsx
new file mode 100644
index 00000000..783c9be2
--- /dev/null
+++ b/FrontEnd/src/views/common/Spinner.tsx
@@ -0,0 +1,13 @@
+import { PaletteColorType } from "@/palette";
+import React from "react";
+
+export interface SpinnerProps {
+  size?: "sm" | "md" | "lg" | number;
+  color?: PaletteColorType;
+}
+
+export default function Spinner(
+  props: SpinnerProps
+): React.ReactElement | null {
+  return <span />;
+}
diff --git a/FrontEnd/src/views/common/TabPages.tsx b/FrontEnd/src/views/common/TabPages.tsx
index 2b1d91cb..b7a9fb36 100644
--- a/FrontEnd/src/views/common/TabPages.tsx
+++ b/FrontEnd/src/views/common/TabPages.tsx
@@ -1,5 +1,4 @@
 import React from "react";
-import { Nav } from "react-bootstrap";
 import { useTranslation } from "react-i18next";
 
 import { convertI18nText, I18nText, UiLogicError } from "@/common";
@@ -31,6 +30,8 @@ const TabPages: React.FC<TabPagesProps> = ({
   pageContainerClassName,
   pageContainerStyle,
 }) => {
+  // TODO:
+
   if (pages.length === 0) {
     throw new UiLogicError("Page list can't be empty.");
   }
@@ -47,23 +48,6 @@ const TabPages: React.FC<TabPagesProps> = ({
 
   return (
     <div className={className} style={style}>
-      <Nav variant="tabs" className={navClassName} style={navStyle}>
-        {pages.map((page) => (
-          <Nav.Item key={page.id}>
-            <Nav.Link
-              active={tab === page.id}
-              onClick={() => {
-                setTab(page.id);
-              }}
-            >
-              {convertI18nText(page.tabText, t)}
-            </Nav.Link>
-          </Nav.Item>
-        ))}
-        {actions != null && (
-          <div className="ms-auto cru-tab-pages-action-area">{actions}</div>
-        )}
-      </Nav>
       <div className={pageContainerClassName} style={pageContainerStyle}>
         {currentPage.page}
       </div>
diff --git a/FrontEnd/src/views/common/alert/AlertHost.tsx b/FrontEnd/src/views/common/alert/AlertHost.tsx
index 949be7ed..21b9882d 100644
--- a/FrontEnd/src/views/common/alert/AlertHost.tsx
+++ b/FrontEnd/src/views/common/alert/AlertHost.tsx
@@ -1,7 +1,6 @@
 import React from "react";
 import without from "lodash/without";
 import { useTranslation } from "react-i18next";
-import { Alert } from "react-bootstrap";
 
 import {
   alertService,
@@ -52,13 +51,7 @@ export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
   };
 
   return (
-    <Alert
-      className="m-3"
-      variant={alert.type ?? "primary"}
-      onClick={cancelTimer}
-      onClose={close}
-      dismissible
-    >
+    <div className="m-3" onClick={cancelTimer}>
       {(() => {
         const { message } = alert;
         if (typeof message === "function") {
@@ -66,7 +59,7 @@ export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
           return <Message />;
         } else return convertI18nText(message, t);
       })()}
-    </Alert>
+    </div>
   );
 };
 
diff --git a/FrontEnd/src/views/common/button/LoadingButton.tsx b/FrontEnd/src/views/common/button/LoadingButton.tsx
index fd1c19b3..aee83aa2 100644
--- a/FrontEnd/src/views/common/button/LoadingButton.tsx
+++ b/FrontEnd/src/views/common/button/LoadingButton.tsx
@@ -1,26 +1,19 @@
 import React from "react";
 
-const LoadingButton: React.FC<{ loading?: boolean } & ButtonProps> = ({
+import { CommonButtonProps } from "./common";
+import Button from "./Button";
+import Spinner from "../Spinner";
+
+const LoadingButton: React.FC<{ loading?: boolean } & CommonButtonProps> = ({
   loading,
-  variant,
   disabled,
+  color,
   ...otherProps
 }) => {
   return (
-    <Button
-      variant={variant != null ? `outline-${variant}` : "outline-primary"}
-      disabled={disabled || loading}
-      {...otherProps}
-    >
+    <Button color={color} disabled={disabled || loading} {...otherProps}>
       {otherProps.children}
-      {loading ? (
-        <Spinner
-          className="ms-1"
-          variant={variant}
-          animation="grow"
-          size="sm"
-        />
-      ) : null}
+      {loading ? <Spinner color={color} /> : null}
     </Button>
   );
 };
diff --git a/FrontEnd/src/views/common/dailog/OperationDialog.tsx b/FrontEnd/src/views/common/dailog/OperationDialog.tsx
index 129e85d5..1e765276 100644
--- a/FrontEnd/src/views/common/dailog/OperationDialog.tsx
+++ b/FrontEnd/src/views/common/dailog/OperationDialog.tsx
@@ -5,6 +5,8 @@ import moment from "moment";
 
 import { convertI18nText, I18nText, UiLogicError } from "@/common";
 
+import { PaletteColorType } from "@/palette";
+
 import Button from "../button/Button";
 import LoadingButton from "../button/LoadingButton";
 import Dialog from "./Dialog";
@@ -144,7 +146,7 @@ export interface OperationDialogProps<
   open: boolean;
   onClose: () => void;
   title: I18nText | (() => React.ReactNode);
-  themeColor?: "danger" | "success" | string;
+  themeColor?: PaletteColorType;
   onProcess: (
     inputs: MapOperationInputInfoValueTypeList<OperationInputInfoList>
   ) => Promise<TData>;
@@ -290,50 +292,42 @@ const OperationDialog = <
 
             if (item.type === "text") {
               return (
-                <Form.Group key={index}>
+                <div key={index}>
                   {item.label && (
-                    <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
+                    <label>{convertI18nText(item.label, t)}</label>
                   )}
-                  <Form.Control
+                  <input
                     type={item.password === true ? "password" : "text"}
                     value={value as string}
                     onChange={(e) => {
                       const v = e.target.value;
                       updateValue(index, v);
                     }}
-                    isInvalid={error != null}
                     disabled={process}
                   />
-                  {error != null && (
-                    <Form.Control.Feedback type="invalid">
-                      {error}
-                    </Form.Control.Feedback>
-                  )}
-                  {item.helperText && (
-                    <Form.Text>{t(item.helperText)}</Form.Text>
-                  )}
-                </Form.Group>
+                  {error != null && <div>{error}</div>}
+                  {item.helperText && <div>{t(item.helperText)}</div>}
+                </div>
               );
             } else if (item.type === "bool") {
               return (
-                <Form.Group key={index}>
-                  <Form.Check<"input">
+                <div key={index}>
+                  <input
                     type="checkbox"
                     checked={value as boolean}
                     onChange={(event) => {
                       updateValue(index, event.currentTarget.checked);
                     }}
-                    label={convertI18nText(item.label, t)}
                     disabled={process}
                   />
-                </Form.Group>
+                  <label>{convertI18nText(item.label, t)}</label>
+                </div>
               );
             } else if (item.type === "select") {
               return (
-                <Form.Group key={index}>
-                  <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
-                  <Form.Control
-                    as="select"
+                <div key={index}>
+                  <label>{convertI18nText(item.label, t)}</label>
+                  <select
                     value={value as string}
                     onChange={(event) => {
                       updateValue(index, event.target.value);
@@ -348,14 +342,14 @@ const OperationDialog = <
                         </option>
                       );
                     })}
-                  </Form.Control>
-                </Form.Group>
+                  </select>
+                </div>
               );
             } else if (item.type === "color") {
               return (
-                <Form.Group key={index}>
+                <div key={index}>
                   {item.canBeNull ? (
-                    <Form.Check<"input">
+                    <input
                       type="checkbox"
                       checked={value !== null}
                       onChange={(event) => {
@@ -365,42 +359,35 @@ const OperationDialog = <
                           updateValue(index, null);
                         }
                       }}
-                      label={convertI18nText(item.label, t)}
                       disabled={process}
                     />
-                  ) : (
-                    <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
-                  )}
+                  ) : null}
+                  <label>{convertI18nText(item.label, t)}</label>
                   {value !== null && (
                     <TwitterPicker
                       color={value as string}
                       onChange={(result) => updateValue(index, result.hex)}
                     />
                   )}
-                </Form.Group>
+                </div>
               );
             } else if (item.type === "datetime") {
               return (
-                <Form.Group key={index}>
+                <div key={index}>
                   {item.label && (
-                    <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
+                    <label>{convertI18nText(item.label, t)}</label>
                   )}
-                  <Form.Control
+                  <input
                     type="datetime-local"
                     value={value as string}
                     onChange={(e) => {
                       const v = e.target.value;
                       updateValue(index, v);
                     }}
-                    isInvalid={error != null}
                     disabled={process}
                   />
-                  {error != null && (
-                    <Form.Control.Feedback type="invalid">
-                      {error}
-                    </Form.Control.Feedback>
-                  )}
-                </Form.Group>
+                  {error != null && <div>{error}</div>}
+                </div>
               );
             }
           })}
@@ -412,7 +399,7 @@ const OperationDialog = <
             onClick={close}
           />
           <LoadingButton
-            variant={props.themeColor}
+            color={props.themeColor}
             loading={process}
             disabled={!canProcess}
             onClick={() => {
diff --git a/FrontEnd/src/views/login/index.tsx b/FrontEnd/src/views/login/index.tsx
index 6c0aaf67..6d70c64a 100644
--- a/FrontEnd/src/views/login/index.tsx
+++ b/FrontEnd/src/views/login/index.tsx
@@ -81,67 +81,59 @@ const LoginPage: React.FC = (_) => {
     <div className="login-container container-fluid mt-2">
       <h1 className="text-center">{t("welcome")}</h1>
       <div>
-          <Form.Label htmlFor="username">{t("user.username")}</Form.Label>
-          <Form.Control
-            id="username"
-            disabled={process}
-            onChange={(e) => {
-              setUsername(e.target.value);
-              setUsernameDirty(true);
-            }}
-            value={username}
-            isInvalid={usernameDirty && username === ""}
-          />
-          {usernameDirty && username === "" && (
-            <Form.Control.Feedback type="invalid">
-              {t("login.emptyUsername")}
-            </Form.Control.Feedback>
-          )}
-          <Form.Label htmlFor="password">{t("user.password")}</Form.Label>
-          <Form.Control
-            id="password"
-            type="password"
-            disabled={process}
-            onChange={(e) => {
-              setPassword(e.target.value);
-              setPasswordDirty(true);
-            }}
-            value={password}
-            onKeyDown={onEnterPressInPassword}
-            isInvalid={passwordDirty && password === ""}
-          />
-          {passwordDirty && password === "" && (
-            <Form.Control.Feedback type="invalid">
-              {t("login.emptyPassword")}
-            </Form.Control.Feedback>
-          )}
-        </Form.Group>
-        <Form.Group>
-          <Form.Check<"input">
-            id="remember-me"
-            type="checkbox"
-            checked={rememberMe}
-            onChange={(e) => {
-              setRememberMe(e.currentTarget.checked);
-            }}
-            label={t("user.rememberMe")}
-          />
-        </Form.Group>
-        {error ? <p className="text-danger">{t(error)}</p> : null}
-        <div className="text-end">
-          <LoadingButton
-            loading={process}
-            variant="primary"
-            onClick={(e) => {
-              submit();
-              e.preventDefault();
-            }}
-            disabled={username === "" || password === "" ? true : undefined}
-          >
-            {t("user.login")}
-          </LoadingButton>
-        </div>
-      </Form>
+        <label htmlFor="username">{t("user.username")}</label>
+        <input
+          id="username"
+          disabled={process}
+          onChange={(e) => {
+            setUsername(e.target.value);
+            setUsernameDirty(true);
+          }}
+          value={username}
+        />
+        {usernameDirty && username === "" && (
+          <div>{t("login.emptyUsername")}</div>
+        )}
+        <label htmlFor="password">{t("user.password")}</label>
+        <input
+          id="password"
+          type="password"
+          disabled={process}
+          onChange={(e) => {
+            setPassword(e.target.value);
+            setPasswordDirty(true);
+          }}
+          value={password}
+          onKeyDown={onEnterPressInPassword}
+        />
+        {passwordDirty && password === "" && (
+          <div>{t("login.emptyPassword")}</div>
+        )}
+      </div>
+      <div>
+        <input
+          id="remember-me"
+          type="checkbox"
+          checked={rememberMe}
+          onChange={(e) => {
+            setRememberMe(e.currentTarget.checked);
+          }}
+        />
+        <label>{t("user.rememberMe")}</label>
+      </div>
+      {error ? <p className="text-danger">{t(error)}</p> : null}
+      <div className="text-end">
+        <LoadingButton
+          loading={process}
+          onClick={(e) => {
+            submit();
+            e.preventDefault();
+          }}
+          disabled={username === "" || password === "" ? true : undefined}
+        >
+          {t("user.login")}
+        </LoadingButton>
+      </div>
     </div>
   );
 };
diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx
index 6cb64dd3..9f0753b9 100644
--- a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx
+++ b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx
@@ -1,15 +1,17 @@
+/* eslint-disable react/jsx-no-undef */
 import React from "react";
 import classnames from "classnames";
-import { Form, Spinner } from "react-bootstrap";
 import { useTranslation } from "react-i18next";
 import { Prompt } from "react-router";
 
 import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline";
 
+import TimelinePostBuilder from "@/services/TimelinePostBuilder";
+
 import FlatButton from "../common/button/FlatButton";
 import TabPages from "../common/TabPages";
-import TimelinePostBuilder from "@/services/TimelinePostBuilder";
 import ConfirmDialog from "../common/dailog/ConfirmDialog";
+import Spinner from "../common/Spinner";
 
 export interface MarkdownPostEditProps {
   timeline: string;
@@ -102,7 +104,7 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
         pageContainerClassName="py-2"
         actions={
           process ? (
-            <Spinner variant="primary" animation="border" size="sm" />
+            <Spinner />
           ) : (
             <>
               <FlatButton
diff --git a/FrontEnd/src/views/timeline-common/TimelineMember.tsx b/FrontEnd/src/views/timeline-common/TimelineMember.tsx
index 830ecd45..c8f26da7 100644
--- a/FrontEnd/src/views/timeline-common/TimelineMember.tsx
+++ b/FrontEnd/src/views/timeline-common/TimelineMember.tsx
@@ -5,21 +5,20 @@ import { convertI18nText, I18nText } from "@/common";
 
 import { HttpUser } from "@/http/user";
 import { getHttpSearchClient } from "@/http/search";
+import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline";
 
 import SearchInput from "../common/SearchInput";
 import UserAvatar from "../common/user/UserAvatar";
-import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline";
 import Button from "../common/button/Button";
+import Dialog from "../common/dailog/Dialog";
 
 const TimelineMemberItem: React.FC<{
   user: HttpUser;
   add?: boolean;
   onAction?: (username: string) => void;
 }> = ({ user, add, onAction }) => {
-  const { t } = useTranslation();
-
   return (
-    <ListGroup.Item className="container">
+    <div className="container">
       <div className="row">
         <div className="col col-auto">
           <UserAvatar username={user.username} className="avatar small" />
@@ -42,7 +41,7 @@ const TimelineMemberItem: React.FC<{
           </div>
         ) : null}
       </div>
-    </ListGroup.Item>
+    </div>
   );
 };
 
@@ -109,7 +108,7 @@ const TimelineMemberUserSearch: React.FC<{
             return <div>{t("timeline.member.noUserAvailableToAdd")}</div>;
           } else {
             return (
-              <ListGroup className="mt-2">
+              <div className="mt-2">
                 {users.map((user) => (
                   <TimelineMemberItem
                     key={user.username}
@@ -126,7 +125,7 @@ const TimelineMemberUserSearch: React.FC<{
                     }}
                   />
                 ))}
-              </ListGroup>
+              </div>
             );
           }
         } else if (userSearchState.type === "error") {
@@ -152,7 +151,7 @@ const TimelineMember: React.FC<TimelineMemberProps> = (props) => {
 
   return (
     <div className="container px-4 py-3">
-      <ListGroup>
+      <div>
         {members.map((member, index) => (
           <TimelineMemberItem
             key={member.username}
@@ -168,7 +167,7 @@ const TimelineMember: React.FC<TimelineMemberProps> = (props) => {
             }
           />
         ))}
-      </ListGroup>
+      </div>
       {timeline.manageable ? (
         <TimelineMemberUserSearch timeline={timeline} onChange={onChange} />
       ) : null}
@@ -187,8 +186,8 @@ export const TimelineMemberDialog: React.FC<TimelineMemberDialogProps> = (
   props
 ) => {
   return (
-    <Modal show centered onHide={props.onClose}>
+    <Dialog open onClose={props.onClose}>
       <TimelineMember {...props} />
-    </Modal>
+    </Dialog>
   );
 };
diff --git a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx
index d05f18d4..ea6e8d40 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx
+++ b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx
@@ -1,5 +1,4 @@
 import React from "react";
-import { Container } from "react-bootstrap";
 import { HubConnectionState } from "@microsoft/signalr";
 
 import { HttpTimelineInfo } from "@/http/timeline";
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx b/FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx
deleted file mode 100644
index e04bb7e1..00000000
--- a/FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from "react";
-import { useTranslation } from "react-i18next";
-
-import Button from "../common/button/Button";
-
-const TimelinePostDeleteConfirmDialog: React.FC<{
-  onClose: () => void;
-  onConfirm: () => void;
-}> = ({ onClose, onConfirm }) => {
-  const { t } = useTranslation();
-
-  return (
-    <Modal onHide={onClose} show centered>
-      <Modal.Header>
-        <Modal.Title className="text-danger">
-          {t("timeline.post.deleteDialog.title")}
-        </Modal.Title>
-      </Modal.Header>
-      <Modal.Body>{t("timeline.post.deleteDialog.prompt")}</Modal.Body>
-      <Modal.Footer>
-        <Button
-          text="operationDialog.cancel"
-          color="secondary"
-          onClick={onClose}
-        />
-        <Button
-          variant="danger"
-          onClick={() => {
-            onConfirm();
-            onClose();
-          }}
-        >
-          {t("operationDialog.confirm")}
-        </Button>
-      </Modal.Footer>
-    </Modal>
-  );
-};
-
-export default TimelinePostDeleteConfirmDialog;
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx
index 5e59bee4..13aacb54 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx
+++ b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx
@@ -265,7 +265,6 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
                 </PopupMenu>
               </div>
               <LoadingButton
-                variant="primary"
                 onClick={onSend}
                 disabled={!canSend}
                 loading={process}
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx b/FrontEnd/src/views/timeline-common/TimelinePostView.tsx
index 7b16e898..995c43df 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx
+++ b/FrontEnd/src/views/timeline-common/TimelinePostView.tsx
@@ -9,9 +9,9 @@ import { pushAlert } from "@/services/alert";
 import UserAvatar from "../common/user/UserAvatar";
 import Card from "../common/Card";
 import FlatButton from "../common/button/FlatButton";
+import ConfirmDialog from "../common/dailog/ConfirmDialog";
 import TimelineLine from "./TimelineLine";
 import TimelinePostContentView from "./TimelinePostContentView";
-import TimelinePostDeleteConfirmDialog from "./TimelinePostDeleteConfirmDialog";
 import PostPropertyChangeDialog from "./PostPropertyChangeDialog";
 
 export interface TimelinePostViewProps {
@@ -116,7 +116,9 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => {
         ) : null}
       </Card>
       {dialog === "delete" ? (
-        <TimelinePostDeleteConfirmDialog
+        <ConfirmDialog
+          title="timeline.post.deleteDialog.title"
+          body="timeline.post.deleteDialog.prompt"
           onClose={() => {
             setDialog(null);
             setOperationMaskVisible(false);
-- 
cgit v1.2.3


From 81e54efaae0ec1d5f1f90c3ca40448f8c61f0e80 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Thu, 1 Jul 2021 16:43:59 +0800
Subject: ...

---
 FrontEnd/src/views/admin/UserAdmin.tsx                  | 12 ++++++------
 FrontEnd/src/views/center/TimelineBoard.tsx             |  6 +++---
 FrontEnd/src/views/common/button/Button.css             | 16 +++++++++-------
 FrontEnd/src/views/common/dailog/ConfirmDialog.tsx      |  7 +++++--
 FrontEnd/src/views/common/dailog/Dialog.css             |  9 +++++++++
 FrontEnd/src/views/common/dailog/OperationDialog.css    |  0
 FrontEnd/src/views/common/dailog/OperationDialog.tsx    | 16 +++++++++++-----
 FrontEnd/src/views/common/index.css                     |  3 ++-
 FrontEnd/src/views/common/menu/Menu.css                 |  2 +-
 FrontEnd/src/views/login/index.tsx                      |  2 +-
 FrontEnd/src/views/settings/index.tsx                   | 11 ++++++-----
 FrontEnd/src/views/timeline-common/TimelinePostView.tsx |  2 +-
 12 files changed, 54 insertions(+), 32 deletions(-)
 create mode 100644 FrontEnd/src/views/common/dailog/OperationDialog.css

(limited to 'FrontEnd/src/views/admin/UserAdmin.tsx')

diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx
index 481db1cc..6d2760f2 100644
--- a/FrontEnd/src/views/admin/UserAdmin.tsx
+++ b/FrontEnd/src/views/admin/UserAdmin.tsx
@@ -206,23 +206,23 @@ const UserItem: React.FC<UserItemProps> = ({ user, on }) => {
   return (
     <div className="admin-user-item">
       <i
-        className="bi-pencil-square float-end icon-button text-warning"
+        className="bi-pencil-square float-end icon-button cru-color-warning"
         onClick={() => setEditMaskVisible(true)}
       />
-      <h4 className="text-primary">{user.username}</h4>
-      <div className="text-secondary">
+      <h4 className="cru-color-primary">{user.username}</h4>
+      <div className="cru-color-secondary">
         {t("admin:user.nickname")}
         {user.nickname}
       </div>
-      <div className="text-secondary">
+      <div className="cru-color-secondary">
         {t("admin:user.uniqueId")}
         {user.uniqueId}
       </div>
-      <div className="text-secondary">
+      <div className="cru-color-secondary">
         {t("admin:user.permissions")}
         {user.permissions.map((permission) => {
           return (
-            <span key={permission} className="text-danger">
+            <span key={permission} className="cru-color-danger">
               {permission}{" "}
             </span>
           );
diff --git a/FrontEnd/src/views/center/TimelineBoard.tsx b/FrontEnd/src/views/center/TimelineBoard.tsx
index 3961a7bc..8c1f5fac 100644
--- a/FrontEnd/src/views/center/TimelineBoard.tsx
+++ b/FrontEnd/src/views/center/TimelineBoard.tsx
@@ -48,16 +48,16 @@ const TimelineBoardItem: React.FC<TimelineBoardItemProps> = ({
         <TimelineLogo className="icon" />
       )}
       <span className="title">{title}</span>
-      <small className="ms-2 text-secondary">{name}</small>
+      <small className="ms-2 cru-color-secondary">{name}</small>
       <span className="flex-grow-1"></span>
       {actions != null ? (
         <div className="right">
           <i
-            className="bi-trash icon-button text-danger px-2"
+            className="bi-trash icon-button cru-color-danger px-2"
             onClick={actions.onDelete}
           />
           <i
-            className="bi-grip-vertical icon-button text-gray px-2 touch-action-none"
+            className="bi-grip-vertical icon-button px-2 touch-action-none"
             onPointerDown={(e) => {
               e.currentTarget.setPointerCapture(e.pointerId);
               actions.onMove.start(e);
diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css
index ec2dd798..54127f05 100644
--- a/FrontEnd/src/views/common/button/Button.css
+++ b/FrontEnd/src/views/common/button/Button.css
@@ -57,7 +57,8 @@
 }
 
 .cru-button:not(.outline):disabled {
-  background-color: var(--cru-button-f3-color);
+  background-color: var(--cru-disable-color);
+  cursor: auto;
 }
 
 .cru-button.outline {
@@ -67,23 +68,24 @@
   padding: 0.2em 0.5em;
   border-radius: 0.2em;
   transition: all 0.6s;
-  background-color: var(--cru-background-color);
+  background-color: white;
 }
 
 .cru-button.outline:hover {
   color: var(--cru-button-f1-color);
   border-color: var(--cru-button-f1-color);
-  background-color: var(--cru-background-1-color);
+  background-color: var(--cru-background-color);
 }
 
 .cru-button.outline:active {
   color: var(--cru-button-f2-color);
   border-color: var(--cru-button-f2-color);
-  background-color: var(--cru-background-2-color);
+  background-color: var(--cru-background-1-color);
 }
 
 .cru-button.outline:disabled {
-  color: var(--cru-button-f3-color);
-  border-color: var(--cru-button-f3-color);
-  background-color: var(--cru-background-3-color);
+  color: var(--cru-disable-color);
+  border-color: var(--cru-disable-color);
+  background-color: white;
+  cursor: auto;
 }
diff --git a/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx b/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx
index 1ad52350..c10b1cdb 100644
--- a/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx
+++ b/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx
@@ -16,12 +16,15 @@ const ConfirmDialog: React.FC<{
 
   return (
     <Dialog onClose={onClose} open={open}>
-      <h3 className="text-danger">{convertI18nText(title, t)}</h3>
+      <h3 className="cru-color-danger">{convertI18nText(title, t)}</h3>
+      <hr />
       <p>{convertI18nText(body, t)}</p>
-      <div>
+      <hr />
+      <div className="cru-dialog-bottom-area">
         <Button
           text="operationDialog.cancel"
           color="secondary"
+          outline
           onClick={onClose}
         />
         <Button
diff --git a/FrontEnd/src/views/common/dailog/Dialog.css b/FrontEnd/src/views/common/dailog/Dialog.css
index abce04fd..c1209d99 100644
--- a/FrontEnd/src/views/common/dailog/Dialog.css
+++ b/FrontEnd/src/views/common/dailog/Dialog.css
@@ -20,3 +20,12 @@
   padding: 1.5em;
   background-color: white;
 }
+
+.cru-dialog-bottom-area {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.cru-dialog-bottom-area > * {
+  margin: 0 0.5em
+}
diff --git a/FrontEnd/src/views/common/dailog/OperationDialog.css b/FrontEnd/src/views/common/dailog/OperationDialog.css
new file mode 100644
index 00000000..e69de29b
diff --git a/FrontEnd/src/views/common/dailog/OperationDialog.tsx b/FrontEnd/src/views/common/dailog/OperationDialog.tsx
index 1e765276..f394a3d3 100644
--- a/FrontEnd/src/views/common/dailog/OperationDialog.tsx
+++ b/FrontEnd/src/views/common/dailog/OperationDialog.tsx
@@ -11,6 +11,8 @@ import Button from "../button/Button";
 import LoadingButton from "../button/LoadingButton";
 import Dialog from "./Dialog";
 
+import "./OperationDialog.css";
+
 interface DefaultErrorPromptProps {
   error?: string;
 }
@@ -18,13 +20,13 @@ interface DefaultErrorPromptProps {
 const DefaultErrorPrompt: React.FC<DefaultErrorPromptProps> = (props) => {
   const { t } = useTranslation();
 
-  let result = <p className="text-danger">{t("operationDialog.error")}</p>;
+  let result = <p className="cru-color-danger">{t("operationDialog.error")}</p>;
 
   if (props.error != null) {
     result = (
       <>
         {result}
-        <p className="text-danger">{props.error}</p>
+        <p className="cru-color-danger">{props.error}</p>
       </>
     );
   }
@@ -392,10 +394,12 @@ const OperationDialog = <
             }
           })}
         </div>
-        <div>
+        <hr />
+        <div className="cru-dialog-bottom-area">
           <Button
             text="operationDialog.cancel"
             color="secondary"
+            outline
             onClick={close}
           />
           <LoadingButton
@@ -421,7 +425,7 @@ const OperationDialog = <
       content =
         props.successPrompt?.(result.data) ?? t("operationDialog.success");
       if (typeof content === "string")
-        content = <p className="text-success">{content}</p>;
+        content = <p className="cru-color-success">{content}</p>;
     } else {
       content = props.failurePrompt?.(result.data) ?? <DefaultErrorPrompt />;
       if (typeof content === "string")
@@ -430,6 +434,7 @@ const OperationDialog = <
     body = (
       <>
         <div>{content}</div>
+        <hr />
         <div>
           <Button text="operationDialog.ok" color="primary" onClick={close} />
         </div>
@@ -446,11 +451,12 @@ const OperationDialog = <
     <Dialog open={props.open} onClose={close}>
       <h3
         className={
-          props.themeColor != null ? "text-" + props.themeColor : undefined
+          props.themeColor != null ? "cru-color-" + props.themeColor : undefined
         }
       >
         {title}
       </h3>
+      <hr />
       {body}
     </Dialog>
   );
diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css
index 02eed6d9..62167cfc 100644
--- a/FrontEnd/src/views/common/index.css
+++ b/FrontEnd/src/views/common/index.css
@@ -2,7 +2,8 @@
   --cru-background-color: #f8f9fa;
   --cru-background-1-color: #e9ecef;
   --cru-background-2-color: #dee2e6;
-  --cru-background-3-color: #ced4da;
+
+  --cru-disable-color: #ced4da;
 
   --cru-primary-color: rgb(0, 123, 255);
   --cru-primary-l1-color: rgb(26, 136, 255);
diff --git a/FrontEnd/src/views/common/menu/Menu.css b/FrontEnd/src/views/common/menu/Menu.css
index c933b34f..a30eb5c6 100644
--- a/FrontEnd/src/views/common/menu/Menu.css
+++ b/FrontEnd/src/views/common/menu/Menu.css
@@ -22,7 +22,7 @@
 }
 
 .cru-menu-item.color-secondary:hover {
-  color: var(--cru-text-on-secondary-color);
+  color: var(--cru-secondary-t-color);
   background-color: var(--cru-secondary-color);
 }
 
diff --git a/FrontEnd/src/views/login/index.tsx b/FrontEnd/src/views/login/index.tsx
index 6d70c64a..782acdaa 100644
--- a/FrontEnd/src/views/login/index.tsx
+++ b/FrontEnd/src/views/login/index.tsx
@@ -122,7 +122,7 @@ const LoginPage: React.FC = (_) => {
         <label>{t("user.rememberMe")}</label>
       </div>
       {error ? <p className="text-danger">{t(error)}</p> : null}
-      <div className="text-end">
+      <div className="cru-text-end">
         <LoadingButton
           loading={process}
           onClick={(e) => {
diff --git a/FrontEnd/src/views/settings/index.tsx b/FrontEnd/src/views/settings/index.tsx
index f25911d7..69a74327 100644
--- a/FrontEnd/src/views/settings/index.tsx
+++ b/FrontEnd/src/views/settings/index.tsx
@@ -28,7 +28,7 @@ const SettingsPage: React.FC = (_) => {
       <div className="container">
         {user ? (
           <Card className="my-3 py-3">
-            <h3 className="px-3 mb-3 text-primary">
+            <h3 className="px-3 mb-3 cru-color-primary">
               {t("settings.subheaders.account")}
             </h3>
             <div
@@ -44,13 +44,13 @@ const SettingsPage: React.FC = (_) => {
               {t("settings.changeNickname")}
             </div>
             <div
-              className="settings-item clickable text-danger"
+              className="settings-item clickable cru-color-danger"
               onClick={() => setDialog("changepassword")}
             >
               {t("settings.changePassword")}
             </div>
             <div
-              className="settings-item clickable text-danger"
+              className="settings-item clickable cru-color-danger"
               onClick={() => {
                 setDialog("logout");
               }}
@@ -60,13 +60,13 @@ const SettingsPage: React.FC = (_) => {
           </Card>
         ) : null}
         <Card className="my-3 py-3">
-          <h3 className="px-3 mb-3 text-primary">
+          <h3 className="px-3 mb-3 cru-color-primary">
             {t("settings.subheaders.customization")}
           </h3>
           <div className="row settings-item first mx-0">
             <div className="col col-12 col-sm-auto">
               <div>{t("settings.languagePrimary")}</div>
-              <small className="d-block text-secondary">
+              <small className="d-block cru-color-secondary">
                 {t("settings.languageSecondary")}
               </small>
             </div>
@@ -94,6 +94,7 @@ const SettingsPage: React.FC = (_) => {
                 title="settings.dialogConfirmLogout.title"
                 body="settings.dialogConfirmLogout.prompt"
                 onClose={() => setDialog(null)}
+                open
                 onConfirm={() => {
                   void userService.logout().then(() => {
                     history.push("/");
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx b/FrontEnd/src/views/timeline-common/TimelinePostView.tsx
index 995c43df..652ff9c9 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx
+++ b/FrontEnd/src/views/timeline-common/TimelinePostView.tsx
@@ -64,7 +64,7 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => {
       >
         {post.editable ? (
           <i
-            className="bi-chevron-down icon-button primary-enhance float-end"
+            className="bi-chevron-down icon-button primary-enhance cru-float-right"
             onClick={(e) => {
               setOperationMaskVisible(true);
               e.stopPropagation();
-- 
cgit v1.2.3


From 47175cf9afcd86b0b92f7121ce0c3d85b948b6dd Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Thu, 1 Jul 2021 19:59:22 +0800
Subject: ...

---
 FrontEnd/src/services/alert.ts                  |  14 ++--
 FrontEnd/src/views/admin/UserAdmin.tsx          |   8 +-
 FrontEnd/src/views/common/alert/AlertHost.tsx   |  45 +++++++----
 FrontEnd/src/views/common/alert/alert.css       |  28 +++++++
 FrontEnd/src/views/common/button/Button.css     |  60 +++-----------
 FrontEnd/src/views/common/button/FlatButton.css |  38 +--------
 FrontEnd/src/views/common/button/TextButton.css |  36 ---------
 FrontEnd/src/views/common/button/TextButton.tsx |  18 -----
 FrontEnd/src/views/common/button/common.ts      |   6 +-
 FrontEnd/src/views/common/index.css             | 100 ++++++++++++++++++++++++
 FrontEnd/src/views/common/menu/Menu.css         |  47 +----------
 FrontEnd/src/views/common/menu/Menu.tsx         |   2 +-
 12 files changed, 189 insertions(+), 213 deletions(-)
 delete mode 100644 FrontEnd/src/views/common/button/TextButton.css
 delete mode 100644 FrontEnd/src/views/common/button/TextButton.tsx

(limited to 'FrontEnd/src/views/admin/UserAdmin.tsx')

diff --git a/FrontEnd/src/services/alert.ts b/FrontEnd/src/services/alert.ts
index 48d482ea..abe56315 100644
--- a/FrontEnd/src/services/alert.ts
+++ b/FrontEnd/src/services/alert.ts
@@ -1,11 +1,13 @@
 import React from "react";
 import pull from "lodash/pull";
 
-import { BootstrapThemeColor, I18nText } from "@/common";
+import { I18nText } from "@/common";
+import { PaletteColorType } from "@/palette";
 
 export interface AlertInfo {
-  type?: BootstrapThemeColor;
-  message: React.FC<unknown> | I18nText;
+  type?: PaletteColorType;
+  message?: I18nText;
+  customMessage?: React.ReactElement;
   dismissTime?: number | "never";
 }
 
@@ -55,9 +57,3 @@ export const alertService = new AlertService();
 export function pushAlert(alert: AlertInfo): void {
   alertService.push(alert);
 }
-
-export const kAlertHostId = "alert-host";
-
-export function getAlertHost(): HTMLElement | null {
-  return document.getElementById(kAlertHostId);
-}
diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx
index 6d2760f2..4ceff8ab 100644
--- a/FrontEnd/src/views/admin/UserAdmin.tsx
+++ b/FrontEnd/src/views/admin/UserAdmin.tsx
@@ -14,8 +14,8 @@ import {
 } from "@/http/user";
 import { Trans, useTranslation } from "react-i18next";
 import Button from "../common/button/Button";
-import TextButton from "../common/button/TextButton";
 import Spinner from "../common/Spinner";
+import FlatButton from "../common/button/FlatButton";
 
 interface DialogProps<TData = undefined, TReturn = undefined> {
   open: boolean;
@@ -232,12 +232,12 @@ const UserItem: React.FC<UserItemProps> = ({ user, on }) => {
         className={classnames("edit-mask", !editMaskVisible && "d-none")}
         onClick={() => setEditMaskVisible(false)}
       >
-        <TextButton text="admin:user.modify" onClick={on[kModify]} />
-        <TextButton
+        <FlatButton text="admin:user.modify" onClick={on[kModify]} />
+        <FlatButton
           text="admin:user.modifyPermissions"
           onClick={on[kModifyPermission]}
         />
-        <TextButton
+        <FlatButton
           text="admin:user.delete"
           color="danger"
           onClick={on[kDelete]}
diff --git a/FrontEnd/src/views/common/alert/AlertHost.tsx b/FrontEnd/src/views/common/alert/AlertHost.tsx
index 21b9882d..ba6d6a0f 100644
--- a/FrontEnd/src/views/common/alert/AlertHost.tsx
+++ b/FrontEnd/src/views/common/alert/AlertHost.tsx
@@ -1,15 +1,13 @@
 import React from "react";
 import without from "lodash/without";
 import { useTranslation } from "react-i18next";
+import classNames from "classnames";
 
-import {
-  alertService,
-  AlertInfoEx,
-  kAlertHostId,
-  AlertInfo,
-} from "@/services/alert";
+import { alertService, AlertInfoEx, AlertInfo } from "@/services/alert";
 import { convertI18nText } from "@/common";
 
+import "./alert.css";
+
 interface AutoCloseAlertProps {
   alert: AlertInfo;
   close: () => void;
@@ -51,14 +49,29 @@ export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
   };
 
   return (
-    <div className="m-3" onClick={cancelTimer}>
-      {(() => {
-        const { message } = alert;
-        if (typeof message === "function") {
-          const Message = message;
-          return <Message />;
-        } else return convertI18nText(message, t);
-      })()}
+    <div
+      className={classNames(
+        "m-3 cru-alert",
+        "cru-" + (alert.type ?? "primary")
+      )}
+      onClick={cancelTimer}
+    >
+      <div className="cru-alert-content">
+        {(() => {
+          const { message, customMessage } = alert;
+          if (customMessage != null) {
+            return customMessage;
+          } else {
+            return convertI18nText(message, t);
+          }
+        })()}
+      </div>
+      <div className="cru-alert-close-button-container">
+        <i
+          className={classNames("icon-button bi-x cru-alert-close-button")}
+          onClick={close}
+        />
+      </div>
     </div>
   );
 };
@@ -66,8 +79,6 @@ export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
 const AlertHost: React.FC = () => {
   const [alerts, setAlerts] = React.useState<AlertInfoEx[]>([]);
 
-  // react guarantee that state setters are stable, so we don't need to add it to dependency list
-
   React.useEffect(() => {
     const consume = (alert: AlertInfoEx): void => {
       setAlerts((old) => [...old, alert]);
@@ -80,7 +91,7 @@ const AlertHost: React.FC = () => {
   }, []);
 
   return (
-    <div id={kAlertHostId} className="alert-container">
+    <div className="alert-container">
       {alerts.map((alert) => {
         return (
           <AutoCloseAlert
diff --git a/FrontEnd/src/views/common/alert/alert.css b/FrontEnd/src/views/common/alert/alert.css
index 12ce294e..328f5f9b 100644
--- a/FrontEnd/src/views/common/alert/alert.css
+++ b/FrontEnd/src/views/common/alert/alert.css
@@ -2,3 +2,31 @@
   position: fixed;
   z-index: 1040;
 }
+
+.cru-alert {
+  border-radius: 5px;
+  border: var(--cru-theme-color) 1px solid;
+  color: var(--cru-theme-t-color);
+  background-color: var(--cru-theme-r1-color);
+
+  display: flex;
+  overflow: hidden;
+}
+
+.cru-alert-content {
+  padding: 0.5em 2em;
+}
+
+.cru-alert-close-button-container {
+  margin-left: auto;
+  width: 2em;
+  text-align: center;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: var(--cru-theme-t-color);
+}
+
+.cru-alert-close-button {
+  color: var(--cru-theme-color);
+}
diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css
index 54127f05..c34176f6 100644
--- a/FrontEnd/src/views/common/button/Button.css
+++ b/FrontEnd/src/views/common/button/Button.css
@@ -1,59 +1,19 @@
-.cru-button.primary {
-  --cru-button-color: var(--cru-primary-color);
-  --cru-button-t-color: var(--cru-primary-t-color);
-  --cru-button-f1-color: var(--cru-primary-f1-color);
-  --cru-button-f2-color: var(--cru-primary-f2-color);
-  --cru-button-f3-color: var(--cru-primary-f3-color);
-}
-
-.cru-button.primary-enhance {
-  --cru-button-color: var(--cru-primary-enhance-color);
-  --cru-button-t-color: var(--cru-primary-enhance-t-color);
-  --cru-button-f1-color: var(--cru-primary-enhance-f1-color);
-  --cru-button-f2-color: var(--cru-primary-enhance-f2-color);
-  --cru-button-f3-color: var(--cru-primary-enhance-f3-color);
-}
-
-.cru-button.secondary {
-  --cru-button-color: var(--cru-secondary-color);
-  --cru-button-t-color: var(--cru-secondary-t-color);
-  --cru-button-f1-color: var(--cru-secondary-f1-color);
-  --cru-button-f2-color: var(--cru-secondary-f2-color);
-  --cru-button-f3-color: var(--cru-secondary-f3-color);
-}
-
-.cru-button.success {
-  --cru-button-color: var(--cru-success-color);
-  --cru-button-t-color: var(--cru-success-t-color);
-  --cru-button-f1-color: var(--cru-success-f1-color);
-  --cru-button-f2-color: var(--cru-success-f2-color);
-  --cru-button-f3-color: var(--cru-success-f3-color);
-}
-
-.cru-button.danger {
-  --cru-button-color: var(--cru-danger-color);
-  --cru-button-t-color: var(--cru-danger-t-color);
-  --cru-button-f1-color: var(--cru-danger-f1-color);
-  --cru-button-f2-color: var(--cru-danger-f2-color);
-  --cru-button-f3-color: var(--cru-danger-f3-color);
-}
-
 .cru-button:not(.outline) {
-  color: var(--cru-button-t-color);
+  color: var(--cru-theme-t-color);
   cursor: pointer;
   padding: 0.2em 0.5em;
   border-radius: 0.2em;
   border: none;
   transition: all 0.5s;
-  background-color: var(--cru-button-color);
+  background-color: var(--cru-theme-color);
 }
 
 .cru-button:not(.outline):hover {
-  background-color: var(--cru-button-f1-color);
+  background-color: var(--cru-theme-f1-color);
 }
 
 .cru-button:not(.outline):active {
-  background-color: var(--cru-button-f2-color);
+  background-color: var(--cru-theme-f2-color);
 }
 
 .cru-button:not(.outline):disabled {
@@ -62,8 +22,8 @@
 }
 
 .cru-button.outline {
-  color: var(--cru-button-color);
-  border: var(--cru-button-color) 1px solid;
+  color: var(--cru-theme-color);
+  border: var(--cru-theme-color) 1px solid;
   cursor: pointer;
   padding: 0.2em 0.5em;
   border-radius: 0.2em;
@@ -72,14 +32,14 @@
 }
 
 .cru-button.outline:hover {
-  color: var(--cru-button-f1-color);
-  border-color: var(--cru-button-f1-color);
+  color: var(--cru-theme-f1-color);
+  border-color: var(--cru-theme-f1-color);
   background-color: var(--cru-background-color);
 }
 
 .cru-button.outline:active {
-  color: var(--cru-button-f2-color);
-  border-color: var(--cru-button-f2-color);
+  color: var(--cru-theme-f2-color);
+  border-color: var(--cru-theme-f2-color);
   background-color: var(--cru-background-1-color);
 }
 
diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css
index 986fb061..f0d33153 100644
--- a/FrontEnd/src/views/common/button/FlatButton.css
+++ b/FrontEnd/src/views/common/button/FlatButton.css
@@ -5,44 +5,14 @@
   border: none;
   background-color: transparent;
   transition: all 0.6s;
-}
-
-.cru-flat-button:hover:not(.disabled) {
-  background-color: #e9ecef;
+  color: var(--cru-theme-color);
 }
 
 .cru-flat-button.disabled {
+  color: var(--cru-theme-l1-color);
   cursor: default;
 }
 
-.cru-flat-button.primary {
-  color: var(--cru-primary-color);
-}
-
-.cru-flat-button.primary.disabled {
-  color: var(--cru-primary-l1-color);
-}
-
-.cru-flat-button.secondary {
-  color: var(--cru-secondary-color);
-}
-
-.cru-flat-button.secondary.disabled {
-  color: var(--cru-secondary-l1-color);
-}
-
-.cru-flat-button.success {
-  color: var(--cru-success-color);
-}
-
-.cru-flat-button.success.disabled {
-  color: var(--cru-success-l1-color);
-}
-
-.cru-flat-button.danger {
-  color: var(--cru-danger-color);
-}
-
-.cru-flat-button.danger.disabled {
-  color: var(--cru-danger-l1-color);
+.cru-flat-button:hover:not(.disabled) {
+  background-color: #e9ecef;
 }
diff --git a/FrontEnd/src/views/common/button/TextButton.css b/FrontEnd/src/views/common/button/TextButton.css
deleted file mode 100644
index f5c8aefa..00000000
--- a/FrontEnd/src/views/common/button/TextButton.css
+++ /dev/null
@@ -1,36 +0,0 @@
-.cru-text-button {
-  background: transparent;
-  border: none;
-}
-
-.cru-text-button.primary {
-  color: var(--cru-primary-color);
-}
-
-.cru-text-button.primary:hover {
-  color: var(--cru-primary-l1-color);
-}
-
-.cru-text-button.secondary {
-  color: var(--cru-secondary-color);
-}
-
-.cru-text-button.secondary:hover {
-  color: var(--cru-secondary-l1-color);
-}
-
-.cru-text-button.success {
-  color: var(--cru-success-color);
-}
-
-.cru-text-button.success:hover {
-  color: var(--cru-success-l1-color);
-}
-
-.cru-text-button.danger {
-  color: var(--cru-danger-color);
-}
-
-.cru-text-button.danger:hover {
-  color: var(--cru-danger-l1-color);
-}
diff --git a/FrontEnd/src/views/common/button/TextButton.tsx b/FrontEnd/src/views/common/button/TextButton.tsx
deleted file mode 100644
index 1d8e7a4b..00000000
--- a/FrontEnd/src/views/common/button/TextButton.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from "react";
-
-import { CommonButtonProps } from "./common";
-import Button from "./Button";
-
-import "./TextButton.css";
-
-function _TextButton(
-  props: CommonButtonProps,
-  ref: React.ForwardedRef<HTMLButtonElement>
-): React.ReactElement | null {
-  return (
-    <Button ref={ref} customButtonClassName="cru-flat-button" {...props} />
-  );
-}
-
-const TextButton = React.forwardRef(_TextButton);
-export default TextButton;
diff --git a/FrontEnd/src/views/common/button/common.ts b/FrontEnd/src/views/common/button/common.ts
index a9db959e..0d84bae0 100644
--- a/FrontEnd/src/views/common/button/common.ts
+++ b/FrontEnd/src/views/common/button/common.ts
@@ -20,7 +20,11 @@ export function calculateProps(
 } {
   const { text, color, className, children, ...otherProps } = props;
   const newProps = {
-    className: classNames(buttonClassName, color ?? "primary", className),
+    className: classNames(
+      buttonClassName,
+      color != null ? "cru-" + color : "cru-primary",
+      className
+    ),
     ...otherProps,
   };
 
diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css
index 529e0e51..a4ce8cf3 100644
--- a/FrontEnd/src/views/common/index.css
+++ b/FrontEnd/src/views/common/index.css
@@ -92,6 +92,106 @@
   --cru-success-t3-color: rgb(179, 179, 179);
 }
 
+.cru-primary {
+  --cru-theme-color: var(--cru-primary-color);
+  --cru-theme-l1-color: var(--cru-primary-l1-color);
+  --cru-theme-l2-color: var(--cru-primary-l2-color);
+  --cru-theme-l3-color: var(--cru-primary-l3-color);
+  --cru-theme-d1-color: var(--cru-primary-d1-color);
+  --cru-theme-d2-color: var(--cru-primary-d2-color);
+  --cru-theme-d3-color: var(--cru-primary-d3-color);
+  --cru-theme-f1-color: var(--cru-primary-f1-color);
+  --cru-theme-f2-color: var(--cru-primary-f2-color);
+  --cru-theme-f3-color: var(--cru-primary-f3-color);
+  --cru-theme-r1-color: var(--cru-primary-r1-color);
+  --cru-theme-r2-color: var(--cru-primary-r2-color);
+  --cru-theme-r3-color: var(--cru-primary-r3-color);
+  --cru-theme-t-color: var(--cru-primary-t-color);
+  --cru-theme-t1-color: var(--cru-primary-t1-color);
+  --cru-theme-t2-color: var(--cru-primary-t2-color);
+  --cru-theme-t3-color: var(--cru-primary-t3-color);
+}
+
+.cru-primary-enhance {
+  --cru-theme-color: var(--cru-primary-enhance-color);
+  --cru-theme-l1-color: var(--cru-primary-enhance-l1-color);
+  --cru-theme-l2-color: var(--cru-primary-enhance-l2-color);
+  --cru-theme-l3-color: var(--cru-primary-enhance-l3-color);
+  --cru-theme-d1-color: var(--cru-primary-enhance-d1-color);
+  --cru-theme-d2-color: var(--cru-primary-enhance-d2-color);
+  --cru-theme-d3-color: var(--cru-primary-enhance-d3-color);
+  --cru-theme-f1-color: var(--cru-primary-enhance-f1-color);
+  --cru-theme-f2-color: var(--cru-primary-enhance-f2-color);
+  --cru-theme-f3-color: var(--cru-primary-enhance-f3-color);
+  --cru-theme-r1-color: var(--cru-primary-enhance-r1-color);
+  --cru-theme-r2-color: var(--cru-primary-enhance-r2-color);
+  --cru-theme-r3-color: var(--cru-primary-enhance-r3-color);
+  --cru-theme-t-color: var(--cru-primary-enhance-t-color);
+  --cru-theme-t1-color: var(--cru-primary-enhance-t1-color);
+  --cru-theme-t2-color: var(--cru-primary-enhance-t2-color);
+  --cru-theme-t3-color: var(--cru-primary-enhance-t3-color);
+}
+
+.cru-secondary {
+  --cru-theme-color: var(--cru-secondary-color);
+  --cru-theme-l1-color: var(--cru-secondary-l1-color);
+  --cru-theme-l2-color: var(--cru-secondary-l2-color);
+  --cru-theme-l3-color: var(--cru-secondary-l3-color);
+  --cru-theme-d1-color: var(--cru-secondary-d1-color);
+  --cru-theme-d2-color: var(--cru-secondary-d2-color);
+  --cru-theme-d3-color: var(--cru-secondary-d3-color);
+  --cru-theme-f1-color: var(--cru-secondary-f1-color);
+  --cru-theme-f2-color: var(--cru-secondary-f2-color);
+  --cru-theme-f3-color: var(--cru-secondary-f3-color);
+  --cru-theme-r1-color: var(--cru-secondary-r1-color);
+  --cru-theme-r2-color: var(--cru-secondary-r2-color);
+  --cru-theme-r3-color: var(--cru-secondary-r3-color);
+  --cru-theme-t-color: var(--cru-secondary-t-color);
+  --cru-theme-t1-color: var(--cru-secondary-t1-color);
+  --cru-theme-t2-color: var(--cru-secondary-t2-color);
+  --cru-theme-t3-color: var(--cru-secondary-t3-color);
+}
+
+.cru-success {
+  --cru-theme-color: var(--cru-success-color);
+  --cru-theme-l1-color: var(--cru-success-l1-color);
+  --cru-theme-l2-color: var(--cru-success-l2-color);
+  --cru-theme-l3-color: var(--cru-success-l3-color);
+  --cru-theme-d1-color: var(--cru-success-d1-color);
+  --cru-theme-d2-color: var(--cru-success-d2-color);
+  --cru-theme-d3-color: var(--cru-success-d3-color);
+  --cru-theme-f1-color: var(--cru-success-f1-color);
+  --cru-theme-f2-color: var(--cru-success-f2-color);
+  --cru-theme-f3-color: var(--cru-success-f3-color);
+  --cru-theme-r1-color: var(--cru-success-r1-color);
+  --cru-theme-r2-color: var(--cru-success-r2-color);
+  --cru-theme-r3-color: var(--cru-success-r3-color);
+  --cru-theme-t-color: var(--cru-success-t-color);
+  --cru-theme-t1-color: var(--cru-success-t1-color);
+  --cru-theme-t2-color: var(--cru-success-t2-color);
+  --cru-theme-t3-color: var(--cru-success-t3-color);
+}
+
+.cru-danger {
+  --cru-theme-color: var(--cru-danger-color);
+  --cru-theme-l1-color: var(--cru-danger-l1-color);
+  --cru-theme-l2-color: var(--cru-danger-l2-color);
+  --cru-theme-l3-color: var(--cru-danger-l3-color);
+  --cru-theme-d1-color: var(--cru-danger-d1-color);
+  --cru-theme-d2-color: var(--cru-danger-d2-color);
+  --cru-theme-d3-color: var(--cru-danger-d3-color);
+  --cru-theme-f1-color: var(--cru-danger-f1-color);
+  --cru-theme-f2-color: var(--cru-danger-f2-color);
+  --cru-theme-f3-color: var(--cru-danger-f3-color);
+  --cru-theme-r1-color: var(--cru-danger-r1-color);
+  --cru-theme-r2-color: var(--cru-danger-r2-color);
+  --cru-theme-r3-color: var(--cru-danger-r3-color);
+  --cru-theme-t-color: var(--cru-danger-t-color);
+  --cru-theme-t1-color: var(--cru-danger-t1-color);
+  --cru-theme-t2-color: var(--cru-danger-t2-color);
+  --cru-theme-t3-color: var(--cru-danger-t3-color);
+}
+
 .cru-color-primary {
   color: var(--cru-primary-color);
 }
diff --git a/FrontEnd/src/views/common/menu/Menu.css b/FrontEnd/src/views/common/menu/Menu.css
index 923c7d3c..c3fa82c4 100644
--- a/FrontEnd/src/views/common/menu/Menu.css
+++ b/FrontEnd/src/views/common/menu/Menu.css
@@ -7,51 +7,12 @@
   padding: 0.5em 1.5em;
   cursor: pointer;
   transition: all 0.5s;
+  color: var(--cru-theme-color);
 }
 
-.cru-menu-item.color-primary {
-  color: var(--cru-primary-color);
-}
-
-.cru-menu-item.color-primary:hover {
-  color: var(--cru-primary-t-color);
-  background-color: var(--cru-primary-color);
-}
-
-.cru-menu-item.color-secondary {
-  color: var(--cru-secondary-color);
-}
-
-.cru-menu-item.color-secondary:hover {
-  color: var(--cru-secondary-t-color);
-  background-color: var(--cru-secondary-color);
-}
-
-.cru-menu-item.color-primary-enhance {
-  color: var(--cru-primary-enhance-color);
-}
-
-.cru-menu-item.color-primary-enhance:hover {
-  color: var(--cru-primary-enhance-t-color);
-  background-color: var(--cru-primary-enhance-color);
-}
-
-.cru-menu-item.color-success {
-  color: var(--cru-success-color);
-}
-
-.cru-menu-item.color-success:hover {
-  color: var(--cru-success-t-color);
-  background-color: var(--cru-success-color);
-}
-
-.cru-menu-item.color-danger {
-  color: var(--cru-danger-color);
-}
-
-.cru-menu-item.color-danger:hover {
-  color: var(--cru-danger-t-color);
-  background-color: var(--cru-danger-color);
+.cru-menu-item:hover {
+  color: var(--cru-theme-t-color);
+  background-color: var(--cru-theme-color);
 }
 
 .cru-menu-item-icon {
diff --git a/FrontEnd/src/views/common/menu/Menu.tsx b/FrontEnd/src/views/common/menu/Menu.tsx
index 04243fa3..d2f65391 100644
--- a/FrontEnd/src/views/common/menu/Menu.tsx
+++ b/FrontEnd/src/views/common/menu/Menu.tsx
@@ -47,7 +47,7 @@ export default function _Menu({
               key={index}
               className={classnames(
                 "cru-menu-item",
-                `color-${item.color ?? "primary"}`
+                `cru-${item.color ?? "primary"}`
               )}
               onClick={() => {
                 item.onClick();
-- 
cgit v1.2.3

