From f5dfd52f6efece2f4cad227044ecf4dd66301bbc Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Sat, 26 Aug 2023 21:36:58 +0800
Subject: ...

---
 FrontEnd/src/components/alert/AlertHost.tsx | 113 ++++++++++++++++++++++++++++
 1 file changed, 113 insertions(+)
 create mode 100644 FrontEnd/src/components/alert/AlertHost.tsx

(limited to 'FrontEnd/src/components/alert/AlertHost.tsx')

diff --git a/FrontEnd/src/components/alert/AlertHost.tsx b/FrontEnd/src/components/alert/AlertHost.tsx
new file mode 100644
index 00000000..b234ac03
--- /dev/null
+++ b/FrontEnd/src/components/alert/AlertHost.tsx
@@ -0,0 +1,113 @@
+import * as React from "react";
+import without from "lodash/without";
+import { useTranslation } from "react-i18next";
+import classNames from "classnames";
+
+import { alertService, AlertInfoEx, AlertInfo } from "~src/services/alert";
+import { convertI18nText } from "~src/common";
+
+import IconButton from "../button/IconButton";
+
+import "./alert.css";
+
+interface AutoCloseAlertProps {
+  alert: AlertInfo;
+  close: () => void;
+}
+
+export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
+  const { alert, close } = props;
+  const { dismissTime } = alert;
+
+  const { t } = useTranslation();
+
+  const timerTag = React.useRef<number | null>(null);
+  const closeHandler = React.useRef<(() => void) | null>(null);
+
+  React.useEffect(() => {
+    closeHandler.current = close;
+  }, [close]);
+
+  React.useEffect(() => {
+    const tag =
+      dismissTime === "never"
+        ? null
+        : typeof dismissTime === "number"
+        ? window.setTimeout(() => closeHandler.current?.(), dismissTime)
+        : window.setTimeout(() => closeHandler.current?.(), 5000);
+    timerTag.current = tag;
+    return () => {
+      if (tag != null) {
+        window.clearTimeout(tag);
+      }
+    };
+  }, [dismissTime]);
+
+  const cancelTimer = (): void => {
+    const { current: tag } = timerTag;
+    if (tag != null) {
+      window.clearTimeout(tag);
+    }
+  };
+
+  return (
+    <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">
+        <IconButton
+          icon="x"
+          className="cru-alert-close-button"
+          onClick={close}
+        />
+      </div>
+    </div>
+  );
+};
+
+const AlertHost: React.FC = () => {
+  const [alerts, setAlerts] = React.useState<AlertInfoEx[]>([]);
+
+  React.useEffect(() => {
+    const consume = (alert: AlertInfoEx): void => {
+      setAlerts((old) => [...old, alert]);
+    };
+
+    alertService.registerConsumer(consume);
+    return () => {
+      alertService.unregisterConsumer(consume);
+    };
+  }, []);
+
+  return (
+    <div className="alert-container">
+      {alerts.map((alert) => {
+        return (
+          <AutoCloseAlert
+            key={alert.id}
+            alert={alert}
+            close={() => {
+              setAlerts((old) => without(old, alert));
+            }}
+          />
+        );
+      })}
+    </div>
+  );
+};
+
+export default AlertHost;
-- 
cgit v1.2.3


From fb64a8f3d4a7dd4035f50ccf2601ae0a683bd1b8 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Thu, 31 Aug 2023 01:50:40 +0800
Subject: ...

---
 FrontEnd/src/App.tsx                               |   2 +-
 FrontEnd/src/components/alert/AlertHost.tsx        | 126 ++++++++-------------
 FrontEnd/src/components/alert/AlertService.ts      | 111 ++++++++++++++++++
 FrontEnd/src/components/alert/alert.css            |  25 ++--
 FrontEnd/src/components/alert/index.ts             |   8 ++
 FrontEnd/src/pages/setting/index.tsx               |   4 +-
 FrontEnd/src/pages/timeline/TimelineCard.tsx       |   4 +-
 .../src/pages/timeline/TimelinePostCreateView.tsx  |   5 +-
 FrontEnd/src/pages/timeline/TimelinePostView.tsx   |   5 +-
 FrontEnd/src/services/user.ts                      |  15 ++-
 10 files changed, 190 insertions(+), 115 deletions(-)
 create mode 100644 FrontEnd/src/components/alert/AlertService.ts
 create mode 100644 FrontEnd/src/components/alert/index.ts

(limited to 'FrontEnd/src/components/alert/AlertHost.tsx')

diff --git a/FrontEnd/src/App.tsx b/FrontEnd/src/App.tsx
index b5b076ec..58463d08 100644
--- a/FrontEnd/src/App.tsx
+++ b/FrontEnd/src/App.tsx
@@ -10,7 +10,7 @@ import LoginPage from "./pages/login";
 import RegisterPage from "./pages/register";
 import TimelinePage from "./pages/timeline";
 import LoadingPage from "./pages/loading";
-import AlertHost from "./components/alert/AlertHost";
+import { AlertHost } from "./components/alert";
 
 export default function App() {
   return (
diff --git a/FrontEnd/src/components/alert/AlertHost.tsx b/FrontEnd/src/components/alert/AlertHost.tsx
index b234ac03..23f62472 100644
--- a/FrontEnd/src/components/alert/AlertHost.tsx
+++ b/FrontEnd/src/components/alert/AlertHost.tsx
@@ -1,95 +1,56 @@
-import * as React from "react";
-import without from "lodash/without";
-import { useTranslation } from "react-i18next";
+import { useEffect, useState } from "react";
 import classNames from "classnames";
 
-import { alertService, AlertInfoEx, AlertInfo } from "~src/services/alert";
-import { convertI18nText } from "~src/common";
-
+import { ThemeColor, useC, Text } from "../common";
 import IconButton from "../button/IconButton";
 
+import { alertService, AlertInfoWithId } from "./AlertService";
+
 import "./alert.css";
 
 interface AutoCloseAlertProps {
-  alert: AlertInfo;
-  close: () => void;
+  color: ThemeColor;
+  message: Text;
+  onDismiss?: () => void;
+  onIn?: () => void;
+  onOut?: () => void;
 }
 
-export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
-  const { alert, close } = props;
-  const { dismissTime } = alert;
-
-  const { t } = useTranslation();
-
-  const timerTag = React.useRef<number | null>(null);
-  const closeHandler = React.useRef<(() => void) | null>(null);
-
-  React.useEffect(() => {
-    closeHandler.current = close;
-  }, [close]);
-
-  React.useEffect(() => {
-    const tag =
-      dismissTime === "never"
-        ? null
-        : typeof dismissTime === "number"
-        ? window.setTimeout(() => closeHandler.current?.(), dismissTime)
-        : window.setTimeout(() => closeHandler.current?.(), 5000);
-    timerTag.current = tag;
-    return () => {
-      if (tag != null) {
-        window.clearTimeout(tag);
-      }
-    };
-  }, [dismissTime]);
-
-  const cancelTimer = (): void => {
-    const { current: tag } = timerTag;
-    if (tag != null) {
-      window.clearTimeout(tag);
-    }
-  };
+function Alert({
+  color,
+  message,
+  onDismiss,
+  onIn,
+  onOut,
+}: AutoCloseAlertProps) {
+  const c = useC();
 
   return (
     <div
-      className={classNames(
-        "m-3 cru-alert",
-        "cru-" + (alert.type ?? "primary")
-      )}
-      onClick={cancelTimer}
+      className={classNames("cru-alert", `cru-theme-${color}`)}
+      onPointerEnter={onIn}
+      onPointerLeave={onOut}
     >
-      <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">
-        <IconButton
-          icon="x"
-          className="cru-alert-close-button"
-          onClick={close}
-        />
-      </div>
+      <div className="cru-alert-message">{c(message)}</div>
+      <IconButton
+        icon="x"
+        color="danger"
+        className="cru-alert-close-button"
+        onClick={onDismiss}
+      />
     </div>
   );
-};
+}
 
-const AlertHost: React.FC = () => {
-  const [alerts, setAlerts] = React.useState<AlertInfoEx[]>([]);
+export default function AlertHost() {
+  const [alerts, setAlerts] = useState<AlertInfoWithId[]>([]);
 
-  React.useEffect(() => {
-    const consume = (alert: AlertInfoEx): void => {
-      setAlerts((old) => [...old, alert]);
-    };
+  useEffect(() => {
+    alertService.registerListener(setAlerts);
 
-    alertService.registerConsumer(consume);
     return () => {
-      alertService.unregisterConsumer(consume);
+      alertService.unregisterListener(setAlerts);
+      alert;
     };
   }, []);
 
@@ -97,17 +58,22 @@ const AlertHost: React.FC = () => {
     <div className="alert-container">
       {alerts.map((alert) => {
         return (
-          <AutoCloseAlert
+          <Alert
             key={alert.id}
-            alert={alert}
-            close={() => {
-              setAlerts((old) => without(old, alert));
+            message={alert.message}
+            color={alert.color ?? "primary"}
+            onIn={() => {
+              alertService.clearDismissTimer(alert.id);
+            }}
+            onOut={() => {
+              alertService.resetDismissTimer(alert.id);
+            }}
+            onDismiss={() => {
+              alertService.dismiss(alert.id);
             }}
           />
         );
       })}
     </div>
   );
-};
-
-export default AlertHost;
+}
diff --git a/FrontEnd/src/components/alert/AlertService.ts b/FrontEnd/src/components/alert/AlertService.ts
new file mode 100644
index 00000000..b3565b8a
--- /dev/null
+++ b/FrontEnd/src/components/alert/AlertService.ts
@@ -0,0 +1,111 @@
+import { ThemeColor, Text } from "../common";
+
+export interface AlertInfo {
+  color?: ThemeColor;
+  message: Text;
+  dismissTime?: number | "never";
+}
+
+export interface AlertInfoWithId extends AlertInfo {
+  id: number;
+}
+
+interface AlertServiceAlert extends AlertInfoWithId {
+  timerId: number | null;
+}
+
+export type AlertsListener = (alerts: AlertInfoWithId[]) => void;
+
+export class AlertService {
+  private listeners: AlertsListener[] = [];
+  private alerts: AlertServiceAlert[] = [];
+  private currentId = 1;
+
+  getAlert(alertId?: number | null | undefined): AlertServiceAlert | null {
+    for (const alert of this.alerts) {
+      if (alert.id === alertId) return alert;
+    }
+    return null;
+  }
+
+  registerListener(listener: AlertsListener): void {
+    this.listeners.push(listener);
+    listener(this.alerts);
+  }
+
+  unregisterListener(listener: AlertsListener): void {
+    this.listeners = this.listeners.filter((l) => l !== listener);
+  }
+
+  notify() {
+    for (const listener of this.listeners) {
+      listener(this.alerts);
+    }
+  }
+
+  push(alert: AlertInfo): void {
+    const newAlert: AlertServiceAlert = {
+      ...alert,
+      id: this.currentId++,
+      timerId: null,
+    };
+
+    this.alerts.push(newAlert);
+    this._resetDismissTimer(newAlert);
+
+    this.notify();
+  }
+
+  private _dismiss(alert: AlertServiceAlert) {
+    if (alert.timerId != null) {
+      window.clearTimeout(alert.timerId);
+    }
+    this.alerts = this.alerts.filter((a) => a !== alert);
+    this.notify();
+  }
+
+  dismiss(alertId?: number | null | undefined) {
+    const alert = this.getAlert(alertId);
+    if (alert != null) {
+      this._dismiss(alert);
+    }
+  }
+
+  private _clearDismissTimer(alert: AlertServiceAlert) {
+    if (alert.timerId != null) {
+      window.clearTimeout(alert.timerId);
+      alert.timerId = null;
+    }
+  }
+
+  clearDismissTimer(alertId?: number | null | undefined) {
+    const alert = this.getAlert(alertId);
+    if (alert != null) {
+      this._clearDismissTimer(alert);
+    }
+  }
+
+  private _resetDismissTimer(
+    alert: AlertServiceAlert,
+    dismissTime?: number | null | undefined,
+  ) {
+    this._clearDismissTimer(alert);
+
+    const realDismissTime = dismissTime ?? alert.dismissTime;
+
+    if (typeof realDismissTime === "number") {
+      alert.timerId = window.setTimeout(() => {
+        this._dismiss(alert);
+      }, realDismissTime);
+    }
+  }
+
+  resetDismissTimer(alertId?: number | null | undefined) {
+    const alert = this.getAlert(alertId);
+    if (alert != null) {
+      this._resetDismissTimer(alert);
+    }
+  }
+}
+
+export const alertService = new AlertService();
diff --git a/FrontEnd/src/components/alert/alert.css b/FrontEnd/src/components/alert/alert.css
index 54c2b87f..063af933 100644
--- a/FrontEnd/src/components/alert/alert.css
+++ b/FrontEnd/src/components/alert/alert.css
@@ -5,29 +5,18 @@
 
 .cru-alert {
   border-radius: 5px;
-  border: var(--cru-key-color) 1px solid;
-  color: var(--cru-key-t-color);
-  background-color: var(--cru-key-b1-color);
+  border: var(--cru-theme-color) 1px solid;
+  color: var(--cru-text-primary-color);
+  background-color: var(--cru-container-background-color);
 
-  display: flex;
-  overflow: hidden;
-}
-
-.cru-alert-content {
   padding: 0.5em 2em;
-}
 
-.cru-alert-close-button-container {
-  flex-shrink: 0;
-  margin-left: auto;
-  width: 2em;
-  text-align: center;
   display: flex;
   align-items: center;
-  justify-content: center;
-  background-color: var(--cru-key-t-color);
 }
 
+.cru-alert-message {}
+
 .cru-alert-close-button {
-  color: var(--cru-key-color);
-}
+  margin-left: auto;
+}
\ No newline at end of file
diff --git a/FrontEnd/src/components/alert/index.ts b/FrontEnd/src/components/alert/index.ts
new file mode 100644
index 00000000..1be0c2ec
--- /dev/null
+++ b/FrontEnd/src/components/alert/index.ts
@@ -0,0 +1,8 @@
+import { alertService, AlertInfo } from "./AlertService";
+import { default as AlertHost } from "./AlertHost";
+
+export { alertService, AlertHost };
+
+export function pushAlert(alert: AlertInfo): void {
+  alertService.push(alert);
+}
diff --git a/FrontEnd/src/pages/setting/index.tsx b/FrontEnd/src/pages/setting/index.tsx
index d2333134..25434339 100644
--- a/FrontEnd/src/pages/setting/index.tsx
+++ b/FrontEnd/src/pages/setting/index.tsx
@@ -10,10 +10,10 @@ import classNames from "classnames";
 
 import { useUser, userService } from "~src/services/user";
 import { getHttpUserClient } from "~src/http/user";
-import { pushAlert } from "~src/services/alert";
 
 import { useC, Text } from "~src/common";
 
+import { pushAlert } from "~src/components/alert";
 import {
   useDialog,
   DialogProvider,
@@ -192,7 +192,7 @@ function RegisterCodeSettingItem() {
           onClick={(event) => {
             void navigator.clipboard.writeText(registerCode).then(() => {
               pushAlert({
-                type: "create",
+                color: "create",
                 message: "settings.myRegisterCodeCopied",
               });
             });
diff --git a/FrontEnd/src/pages/timeline/TimelineCard.tsx b/FrontEnd/src/pages/timeline/TimelineCard.tsx
index 44dfbf9e..1e0e9b75 100644
--- a/FrontEnd/src/pages/timeline/TimelineCard.tsx
+++ b/FrontEnd/src/pages/timeline/TimelineCard.tsx
@@ -2,11 +2,11 @@ import { useState } from "react";
 import { HubConnectionState } from "@microsoft/signalr";
 
 import { useUser } from "~src/services/user";
-import { pushAlert } from "~src/services/alert";
 
 import { HttpTimelineInfo } from "~src/http/timeline";
 import { getHttpBookmarkClient } from "~src/http/bookmark";
 
+import { pushAlert } from "~src/components/alert";
 import { useMobile } from "~src/components/hooks";
 import { Dialog, DialogProvider, useDialog } from "~src/components/dialog";
 import UserAvatar from "~src/components/user/UserAvatar";
@@ -89,7 +89,7 @@ export default function TimelineCard(props: TimelinePageCardProps) {
                     message: timeline.isBookmark
                       ? "timeline.removeBookmarkFail"
                       : "timeline.addBookmarkFail",
-                    type: "danger",
+                    color: "danger",
                   });
                 });
             }}
diff --git a/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx b/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx
index 3bc4dab3..70925cd9 100644
--- a/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx
+++ b/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx
@@ -10,10 +10,9 @@ import {
   HttpTimelinePostPostRequestData,
 } from "~src/http/timeline";
 
-import { pushAlert } from "~src/services/alert";
-
 import base64 from "~src/utilities/base64";
 
+import { pushAlert } from "~src/components/alert";
 import BlobImage from "~src/components/BlobImage";
 import LoadingButton from "~src/components/button/LoadingButton";
 import PopupMenu from "~src/components/menu/PopupMenu";
@@ -141,7 +140,7 @@ function TimelinePostEdit(props: TimelinePostEditProps) {
 
   const onPostError = (): void => {
     pushAlert({
-      type: "danger",
+      color: "danger",
       message: "timeline.sendPostFailed",
     });
   };
diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.tsx b/FrontEnd/src/pages/timeline/TimelinePostView.tsx
index 5de09b28..b09fe6f8 100644
--- a/FrontEnd/src/pages/timeline/TimelinePostView.tsx
+++ b/FrontEnd/src/pages/timeline/TimelinePostView.tsx
@@ -5,8 +5,7 @@ import {
   HttpTimelinePostInfo,
 } from "~src/http/timeline";
 
-import { pushAlert } from "~src/services/alert";
-
+import { pushAlert } from "~src/components/alert";
 import { useClickOutside } from "~src/components/hooks";
 import UserAvatar from "~src/components/user/UserAvatar";
 import { DialogProvider, useDialog } from "~src/components/dialog";
@@ -44,7 +43,7 @@ export default function TimelinePostView(props: TimelinePostViewProps) {
               .deletePost(post.timelineOwnerV2, post.timelineNameV2, post.id)
               .then(onDeleted, () => {
                 pushAlert({
-                  type: "danger",
+                  color: "danger",
                   message: "timeline.deletePostFailed",
                 });
               });
diff --git a/FrontEnd/src/services/user.ts b/FrontEnd/src/services/user.ts
index ddba4dab..5f682a36 100644
--- a/FrontEnd/src/services/user.ts
+++ b/FrontEnd/src/services/user.ts
@@ -8,14 +8,17 @@ import { setHttpToken, axios, HttpBadRequestError } from "~src/http/common";
 import { getHttpTokenClient } from "~src/http/token";
 import { getHttpUserClient, HttpUser, UserPermission } from "~src/http/user";
 
-import { pushAlert } from "./alert";
+import { pushAlert } from "~src/components/alert";
 
 interface IAuthUser extends HttpUser {
   token: string;
 }
 
 export class AuthUser implements IAuthUser {
-  constructor(user: HttpUser, public token: string) {
+  constructor(
+    user: HttpUser,
+    public token: string,
+  ) {
     this.uniqueId = user.uniqueId;
     this.username = user.username;
     this.permissions = user.permissions;
@@ -61,7 +64,7 @@ export class UserService {
       if (e.isAxiosError && e.response && e.response.status === 401) {
         this.userSubject.next(null);
         pushAlert({
-          type: "danger",
+          color: "danger",
           message: "user.tokenInvalid",
         });
       } else {
@@ -97,11 +100,11 @@ export class UserService {
               localStorage.removeItem(USER_STORAGE_KEY);
               this.userSubject.next(null);
               pushAlert({
-                type: "danger",
+                color: "danger",
                 message: "user.tokenInvalid",
               });
             }
-          }
+          },
         );
     }
   }
@@ -118,7 +121,7 @@ export class UserService {
 
   async login(
     credentials: LoginCredentials,
-    rememberMe: boolean
+    rememberMe: boolean,
   ): Promise<void> {
     if (this.currentUser) {
       throw new UiLogicError("Already login.");
-- 
cgit v1.2.3


From 4a6d8c606fab0215065ce65d2f105fafd8db7169 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Thu, 31 Aug 2023 02:19:01 +0800
Subject: ...

---
 FrontEnd/src/components/alert/AlertHost.tsx   |  9 +++--
 FrontEnd/src/components/alert/AlertService.ts |  7 +++-
 FrontEnd/src/components/alert/alert.css       |  7 ++--
 FrontEnd/src/pages/setting/index.tsx          | 54 ++++++++++++++-------------
 4 files changed, 42 insertions(+), 35 deletions(-)

(limited to 'FrontEnd/src/components/alert/AlertHost.tsx')

diff --git a/FrontEnd/src/components/alert/AlertHost.tsx b/FrontEnd/src/components/alert/AlertHost.tsx
index 23f62472..59f8f27c 100644
--- a/FrontEnd/src/components/alert/AlertHost.tsx
+++ b/FrontEnd/src/components/alert/AlertHost.tsx
@@ -46,11 +46,14 @@ export default function AlertHost() {
   const [alerts, setAlerts] = useState<AlertInfoWithId[]>([]);
 
   useEffect(() => {
-    alertService.registerListener(setAlerts);
+    const listener = (alerts: AlertInfoWithId[]) => {
+      setAlerts(alerts);
+    };
+
+    alertService.registerListener(listener);
 
     return () => {
-      alertService.unregisterListener(setAlerts);
-      alert;
+      alertService.unregisterListener(listener);
     };
   }, []);
 
diff --git a/FrontEnd/src/components/alert/AlertService.ts b/FrontEnd/src/components/alert/AlertService.ts
index b3565b8a..b9cda752 100644
--- a/FrontEnd/src/components/alert/AlertService.ts
+++ b/FrontEnd/src/components/alert/AlertService.ts
@@ -1,5 +1,7 @@
 import { ThemeColor, Text } from "../common";
 
+const defaultDismissTime = 5000;
+
 export interface AlertInfo {
   color?: ThemeColor;
   message: Text;
@@ -50,7 +52,7 @@ export class AlertService {
       timerId: null,
     };
 
-    this.alerts.push(newAlert);
+    this.alerts = [...this.alerts, newAlert];
     this._resetDismissTimer(newAlert);
 
     this.notify();
@@ -91,7 +93,8 @@ export class AlertService {
   ) {
     this._clearDismissTimer(alert);
 
-    const realDismissTime = dismissTime ?? alert.dismissTime;
+    const realDismissTime =
+      dismissTime ?? alert.dismissTime ?? defaultDismissTime;
 
     if (typeof realDismissTime === "number") {
       alert.timerId = window.setTimeout(() => {
diff --git a/FrontEnd/src/components/alert/alert.css b/FrontEnd/src/components/alert/alert.css
index 063af933..948256de 100644
--- a/FrontEnd/src/components/alert/alert.css
+++ b/FrontEnd/src/components/alert/alert.css
@@ -5,18 +5,17 @@
 
 .cru-alert {
   border-radius: 5px;
-  border: var(--cru-theme-color) 1px solid;
+  border: var(--cru-theme-color) 2px solid;
   color: var(--cru-text-primary-color);
   background-color: var(--cru-container-background-color);
 
-  padding: 0.5em 2em;
+  margin: 1em;
+  padding: 0.5em 1em;
 
   display: flex;
   align-items: center;
 }
 
-.cru-alert-message {}
-
 .cru-alert-close-button {
   margin-left: auto;
 }
\ No newline at end of file
diff --git a/FrontEnd/src/pages/setting/index.tsx b/FrontEnd/src/pages/setting/index.tsx
index 25434339..88ab5cb2 100644
--- a/FrontEnd/src/pages/setting/index.tsx
+++ b/FrontEnd/src/pages/setting/index.tsx
@@ -176,34 +176,36 @@ function RegisterCodeSettingItem() {
   }, [user, registerCode]);
 
   return (
-    <SettingItemContainer
-      title="settings.myRegisterCode"
-      description="settings.myRegisterCodeDesc"
-      className="register-code-setting-item"
-      onClick={createDialogSwitch("confirm")}
-    >
-      {registerCode === undefined ? (
-        <Spinner />
-      ) : registerCode === null ? (
-        <span>Noop</span>
-      ) : (
-        <code
-          className="register-code"
-          onClick={(event) => {
-            void navigator.clipboard.writeText(registerCode).then(() => {
-              pushAlert({
-                color: "create",
-                message: "settings.myRegisterCodeCopied",
+    <>
+      <SettingItemContainer
+        title="settings.myRegisterCode"
+        description="settings.myRegisterCodeDesc"
+        className="register-code-setting-item"
+        onClick={createDialogSwitch("confirm")}
+      >
+        {registerCode === undefined ? (
+          <Spinner />
+        ) : registerCode === null ? (
+          <span>Noop</span>
+        ) : (
+          <code
+            className="register-code"
+            onClick={(event) => {
+              void navigator.clipboard.writeText(registerCode).then(() => {
+                pushAlert({
+                  color: "create",
+                  message: "settings.myRegisterCodeCopied",
+                });
               });
-            });
-            event.stopPropagation();
-          }}
-        >
-          {registerCode}
-        </code>
-      )}
+              event.stopPropagation();
+            }}
+          >
+            {registerCode}
+          </code>
+        )}
+      </SettingItemContainer>
       <DialogProvider controller={controller} />
-    </SettingItemContainer>
+    </>
   );
 }
 
-- 
cgit v1.2.3

