aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/views
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/views')
-rw-r--r--FrontEnd/src/views/common/Card.css8
-rw-r--r--FrontEnd/src/views/common/Card.tsx8
-rw-r--r--FrontEnd/src/views/common/Page.tsx15
-rw-r--r--FrontEnd/src/views/common/index.css8
-rw-r--r--FrontEnd/src/views/common/theme.css5
-rw-r--r--FrontEnd/src/views/settings/ChangeAvatarDialog.tsx354
-rw-r--r--FrontEnd/src/views/settings/ChangeNicknameDialog.tsx34
-rw-r--r--FrontEnd/src/views/settings/ChangePasswordDialog.tsx69
-rw-r--r--FrontEnd/src/views/settings/index.css31
-rw-r--r--FrontEnd/src/views/settings/index.tsx338
10 files changed, 38 insertions, 832 deletions
diff --git a/FrontEnd/src/views/common/Card.css b/FrontEnd/src/views/common/Card.css
index 98cb4cdd..5b3dbbe9 100644
--- a/FrontEnd/src/views/common/Card.css
+++ b/FrontEnd/src/views/common/Card.css
@@ -1,9 +1,11 @@
.cru-card {
+ border: solid 1px;
border-radius: var(--cru-card-border-radius);
- background-color: var(--cru-primary-container-color);
+ background-color: var(--cru-key-container-color);
+ border-color: var(--cru-key-container-color);
transition: all 0.3s;
}
.cru-card:hover {
- border-color: var(--cru-primary-1-color);
-}
+ border-color: var(--cru-key-1-color);
+} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/Card.tsx b/FrontEnd/src/views/common/Card.tsx
index 5ff89b61..35e605af 100644
--- a/FrontEnd/src/views/common/Card.tsx
+++ b/FrontEnd/src/views/common/Card.tsx
@@ -1,22 +1,26 @@
import { ComponentPropsWithoutRef, Ref } from "react";
import classNames from "classnames";
+import { ThemeColor } from "./common";
import "./Card.css";
interface CardProps extends ComponentPropsWithoutRef<"div"> {
- containerRef?: Ref<HTMLDivElement> | null;
+ containerRef?: Ref<HTMLDivElement>;
+ color?: ThemeColor;
}
export default function Card({
+ color,
className,
children,
containerRef,
...otherProps
}: CardProps) {
+ color = color ?? "primary";
return (
<div
ref={containerRef}
- className={classNames("cru-card", className)}
+ className={classNames("cru-card", `cru-${color}`, className)}
{...otherProps}
>
{children}
diff --git a/FrontEnd/src/views/common/Page.tsx b/FrontEnd/src/views/common/Page.tsx
new file mode 100644
index 00000000..86fdb2f5
--- /dev/null
+++ b/FrontEnd/src/views/common/Page.tsx
@@ -0,0 +1,15 @@
+import { ComponentPropsWithoutRef, Ref } from "react";
+import classNames from "classnames";
+
+interface PageProps extends ComponentPropsWithoutRef<"div"> {
+ noTopPadding?: boolean;
+ pageRef?: Ref<HTMLDivElement>;
+}
+
+export default function Page({ noTopPadding, pageRef, className, children }: PageProps) {
+ return (
+ <div ref={pageRef} className={classNames(className, "cru-page", noTopPadding && "cru-page-no-top-padding")}>
+ {children}
+ </div>
+ );
+}
diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css
index eb82c4bf..1f9b5086 100644
--- a/FrontEnd/src/views/common/index.css
+++ b/FrontEnd/src/views/common/index.css
@@ -13,6 +13,14 @@ body {
line-height: 1.2;
}
+.cru-page {
+ padding: var(--cru-page-padding);
+}
+
+.cru-page-no-top-padding {
+ padding-top: 0;
+}
+
.cru-text-center {
text-align: center;
}
diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css
index 3ad45996..6478ed7a 100644
--- a/FrontEnd/src/views/common/theme.css
+++ b/FrontEnd/src/views/common/theme.css
@@ -2,5 +2,8 @@
:root {
--cru-default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ --cru-page-padding: 1em 2em;
--cru-card-border-radius: 4px;
-} \ No newline at end of file
+
+ --cru-secondary-text-color: var(--cru-surface-on-color);
+}
diff --git a/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx b/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx
deleted file mode 100644
index 44bd2c68..00000000
--- a/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx
+++ /dev/null
@@ -1,354 +0,0 @@
-import { useState, useEffect } from "react";
-import * as React from "react";
-import { useTranslation } from "react-i18next";
-import { AxiosError } from "axios";
-
-import { convertI18nText, I18nText, UiLogicError } from "@/common";
-
-import { useUser } from "@/services/user";
-
-import { getHttpUserClient } from "@/http/user";
-
-import ImageCropper, { Clip, applyClipToImage } from "../common/ImageCropper";
-import Button from "../common/button/Button";
-import Dialog from "../common/dialog/Dialog";
-
-export interface ChangeAvatarDialogProps {
- open: boolean;
- close: () => void;
-}
-
-const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
- const { t } = useTranslation();
-
- const user = useUser();
-
- const [file, setFile] = React.useState<File | null>(null);
- const [fileUrl, setFileUrl] = React.useState<string | null>(null);
- const [clip, setClip] = React.useState<Clip | null>(null);
- const [cropImgElement, setCropImgElement] =
- React.useState<HTMLImageElement | null>(null);
- const [resultBlob, setResultBlob] = React.useState<Blob | null>(null);
- const [resultUrl, setResultUrl] = React.useState<string | null>(null);
-
- const [state, setState] = React.useState<
- | "select"
- | "crop"
- | "processcrop"
- | "preview"
- | "uploading"
- | "success"
- | "error"
- >("select");
-
- const [message, setMessage] = useState<I18nText>(
- "settings.dialogChangeAvatar.prompt.select"
- );
-
- const trueMessage = convertI18nText(message, t);
-
- const closeDialog = props.close;
-
- const close = React.useCallback((): void => {
- if (!(state === "uploading")) {
- closeDialog();
- }
- }, [state, closeDialog]);
-
- useEffect(() => {
- if (file != null) {
- const url = URL.createObjectURL(file);
- setClip(null);
- setFileUrl(url);
- setState("crop");
- return () => {
- URL.revokeObjectURL(url);
- };
- } else {
- setFileUrl(null);
- setState("select");
- }
- }, [file]);
-
- React.useEffect(() => {
- if (resultBlob != null) {
- const url = URL.createObjectURL(resultBlob);
- setResultUrl(url);
- setState("preview");
- return () => {
- URL.revokeObjectURL(url);
- };
- } else {
- setResultUrl(null);
- }
- }, [resultBlob]);
-
- const onSelectFile = React.useCallback(
- (e: React.ChangeEvent<HTMLInputElement>): void => {
- const files = e.target.files;
- if (files == null || files.length === 0) {
- setFile(null);
- } else {
- setFile(files[0]);
- }
- },
- []
- );
-
- const onCropNext = React.useCallback(() => {
- if (
- cropImgElement == null ||
- clip == null ||
- clip.width === 0 ||
- file == null
- ) {
- throw new UiLogicError();
- }
-
- setState("processcrop");
- void applyClipToImage(cropImgElement, clip, file.type).then((b) => {
- setResultBlob(b);
- });
- }, [cropImgElement, clip, file]);
-
- const onCropPrevious = React.useCallback(() => {
- setFile(null);
- setState("select");
- }, []);
-
- const onPreviewPrevious = React.useCallback(() => {
- setResultBlob(null);
- setState("crop");
- }, []);
-
- const upload = React.useCallback(() => {
- if (resultBlob == null) {
- throw new UiLogicError();
- }
-
- if (user == null) {
- throw new UiLogicError();
- }
-
- setState("uploading");
- getHttpUserClient()
- .putAvatar(user.username, resultBlob)
- .then(
- () => {
- setState("success");
- },
- (e: unknown) => {
- setState("error");
- setMessage({ type: "custom", value: (e as AxiosError).message });
- }
- );
- }, [user, resultBlob]);
-
- const createPreviewRow = (): React.ReactElement => {
- if (resultUrl == null) {
- throw new UiLogicError();
- }
- return (
- <div className="row justify-content-center">
- <div className="col col-auto">
- <img
- className="change-avatar-img"
- src={resultUrl}
- alt={t("settings.dialogChangeAvatar.previewImgAlt") ?? undefined}
- />
- </div>
- </div>
- );
- };
-
- return (
- <Dialog open={props.open} onClose={close}>
- <h3 className="cru-color-primary">
- {t("settings.dialogChangeAvatar.title")}
- </h3>
- <hr />
- {(() => {
- if (state === "select") {
- return (
- <>
- <div className="container">
- <div className="row">
- {t("settings.dialogChangeAvatar.prompt.select")}
- </div>
- <div className="row">
- <input
- className="px-0"
- type="file"
- accept="image/*"
- onChange={onSelectFile}
- />
- </div>
- </div>
- <hr />
- <div className="cru-dialog-bottom-area">
- <Button
- text="operationDialog.cancel"
- color="secondary"
- onClick={close}
- />
- </div>
- </>
- );
- } else if (state === "crop") {
- if (fileUrl == null) {
- throw new UiLogicError();
- }
- return (
- <>
- <div className="container">
- <div className="row justify-content-center">
- <ImageCropper
- clip={clip}
- onChange={setClip}
- imageUrl={fileUrl}
- imageElementCallback={setCropImgElement}
- />
- </div>
- <div className="row">
- {t("settings.dialogChangeAvatar.prompt.crop")}
- </div>
- </div>
- <hr />
- <div className="cru-dialog-bottom-area">
- <Button
- text="operationDialog.cancel"
- color="secondary"
- outline
- onClick={close}
- />
- <Button
- text="operationDialog.previousStep"
- color="secondary"
- outline
- onClick={onCropPrevious}
- />
- <Button
- text="operationDialog.nextStep"
- color="primary"
- onClick={onCropNext}
- disabled={
- cropImgElement == null || clip == null || clip.width === 0
- }
- />
- </div>
- </>
- );
- } else if (state === "processcrop") {
- return (
- <>
- <div className="container">
- <div className="row">
- {t("settings.dialogChangeAvatar.prompt.processingCrop")}
- </div>
- </div>
- <hr />
- <div className="cru-dialog-bottom-area">
- <Button
- text="operationDialog.cancel"
- color="secondary"
- onClick={close}
- outline
- />
- <Button
- text="operationDialog.previousStep"
- color="secondary"
- onClick={onPreviewPrevious}
- outline
- />
- </div>
- </>
- );
- } else if (state === "preview") {
- return (
- <>
- <div className="container">
- {createPreviewRow()}
- <div className="row">
- {t("settings.dialogChangeAvatar.prompt.preview")}
- </div>
- </div>
- <hr />
- <div className="cru-dialog-bottom-area">
- <Button
- text="operationDialog.cancel"
- color="secondary"
- outline
- onClick={close}
- />
- <Button
- text="operationDialog.previousStep"
- color="secondary"
- outline
- onClick={onPreviewPrevious}
- />
- <Button
- text="settings.dialogChangeAvatar.upload"
- color="primary"
- onClick={upload}
- />
- </div>
- </>
- );
- } else if (state === "uploading") {
- return (
- <>
- <div className="container">
- {createPreviewRow()}
- <div className="row">
- {t("settings.dialogChangeAvatar.prompt.uploading")}
- </div>
- </div>
- </>
- );
- } else if (state === "success") {
- return (
- <>
- <div className="container">
- <div className="row p-4 text-success">
- {t("operationDialog.success")}
- </div>
- </div>
- <hr />
- <div className="cru-dialog-bottom-area">
- <Button
- text="operationDialog.ok"
- color="success"
- onClick={close}
- />
- </div>
- </>
- );
- } else {
- return (
- <>
- <div className="container">
- {createPreviewRow()}
- <div className="row text-danger">{trueMessage}</div>
- </div>
- <hr />
- <div>
- <Button
- text="operationDialog.cancel"
- color="secondary"
- onClick={close}
- />
- <Button
- text="operationDialog.retry"
- color="primary"
- onClick={upload}
- />
- </div>
- </>
- );
- }
- })()}
- </Dialog>
- );
-};
-
-export default ChangeAvatarDialog;
diff --git a/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx b/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx
deleted file mode 100644
index 7ba12de8..00000000
--- a/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { getHttpUserClient } from "@/http/user";
-import { useUser } from "@/services/user";
-import * as React from "react";
-
-import OperationDialog from "../common/dialog/OperationDialog";
-
-export interface ChangeNicknameDialogProps {
- open: boolean;
- close: () => void;
-}
-
-const ChangeNicknameDialog: React.FC<ChangeNicknameDialogProps> = (props) => {
- const user = useUser();
-
- if (user == null) return null;
-
- return (
- <OperationDialog
- open={props.open}
- title="settings.dialogChangeNickname.title"
- inputScheme={[
- { type: "text", label: "settings.dialogChangeNickname.inputLabel" },
- ]}
- onProcess={([newNickname]) => {
- return getHttpUserClient().patch(user.username, {
- nickname: newNickname,
- });
- }}
- onClose={props.close}
- />
- );
-};
-
-export default ChangeNicknameDialog;
diff --git a/FrontEnd/src/views/settings/ChangePasswordDialog.tsx b/FrontEnd/src/views/settings/ChangePasswordDialog.tsx
deleted file mode 100644
index a34ca4a7..00000000
--- a/FrontEnd/src/views/settings/ChangePasswordDialog.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { useState } from "react";
-import * as React from "react";
-import { useNavigate } from "react-router-dom";
-
-import { userService } from "@/services/user";
-
-import OperationDialog from "../common/dialog/OperationDialog";
-
-export interface ChangePasswordDialogProps {
- open: boolean;
- close: () => void;
-}
-
-const ChangePasswordDialog: React.FC<ChangePasswordDialogProps> = (props) => {
- const navigate = useNavigate();
-
- const [redirect, setRedirect] = useState<boolean>(false);
-
- return (
- <OperationDialog
- open={props.open}
- title="settings.dialogChangePassword.title"
- themeColor="danger"
- inputPrompt="settings.dialogChangePassword.prompt"
- inputScheme={[
- {
- type: "text",
- label: "settings.dialogChangePassword.inputOldPassword",
- password: true,
- },
- {
- type: "text",
- label: "settings.dialogChangePassword.inputNewPassword",
- password: true,
- },
- {
- type: "text",
- label: "settings.dialogChangePassword.inputRetypeNewPassword",
- password: true,
- },
- ]}
- inputValidator={([oldPassword, newPassword, retypedNewPassword]) => {
- const result: Record<number, string> = {};
- if (oldPassword === "") {
- result[0] = "settings.dialogChangePassword.errorEmptyOldPassword";
- }
- if (newPassword === "") {
- result[1] = "settings.dialogChangePassword.errorEmptyNewPassword";
- }
- if (retypedNewPassword !== newPassword) {
- result[2] = "settings.dialogChangePassword.errorRetypeNotMatch";
- }
- return result;
- }}
- onProcess={async ([oldPassword, newPassword]) => {
- await userService.changePassword(oldPassword, newPassword);
- setRedirect(true);
- }}
- onClose={() => {
- props.close();
- if (redirect) {
- navigate("/login");
- }
- }}
- />
- );
-};
-
-export default ChangePasswordDialog;
diff --git a/FrontEnd/src/views/settings/index.css b/FrontEnd/src/views/settings/index.css
deleted file mode 100644
index ccf7a97a..00000000
--- a/FrontEnd/src/views/settings/index.css
+++ /dev/null
@@ -1,31 +0,0 @@
-.change-avatar-cropper-row {
- max-height: 400px;
-}
-
-.change-avatar-img {
- min-width: 50%;
- max-width: 100%;
- max-height: 400px;
-}
-
-.settings-item {
- padding: 0.5em 1em;
- transition: background 0.3s;
- border-bottom: 1px solid #e9ecef;
- align-items: center;
-}
-.settings-item.first {
- border-top: 1px solid #e9ecef;
-}
-.settings-item.clickable {
- cursor: pointer;
-}
-.settings-item:hover {
- background: #dee2e6;
-}
-
-.register-code {
- border: 1px solid black;
- border-radius: 3px;
- padding: 0.2em;
-} \ No newline at end of file
diff --git a/FrontEnd/src/views/settings/index.tsx b/FrontEnd/src/views/settings/index.tsx
deleted file mode 100644
index 6647826f..00000000
--- a/FrontEnd/src/views/settings/index.tsx
+++ /dev/null
@@ -1,338 +0,0 @@
-import { useState } from "react";
-import * as React from "react";
-import { useNavigate } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import classNames from "classnames";
-
-import { convertI18nText, I18nText, UiLogicError } from "@/common";
-import { useUser, userService } from "@/services/user";
-import { getHttpUserClient } from "@/http/user";
-import { TimelineVisibility } from "@/http/timeline";
-
-import ConfirmDialog from "../common/dialog/ConfirmDialog";
-import Card from "../common/Card";
-import Spinner from "../common/Spinner";
-import ChangePasswordDialog from "./ChangePasswordDialog";
-import ChangeAvatarDialog from "./ChangeAvatarDialog";
-import ChangeNicknameDialog from "./ChangeNicknameDialog";
-
-import "./index.css";
-import { pushAlert } from "@/services/alert";
-
-interface SettingSectionProps {
- title: I18nText;
- children: React.ReactNode;
-}
-
-const SettingSection: React.FC<SettingSectionProps> = ({ title, children }) => {
- const { t } = useTranslation();
-
- return (
- <Card className="my-3 py-3">
- <h3 className="px-3 mb-3 cru-color-primary">
- {convertI18nText(title, t)}
- </h3>
- {children}
- </Card>
- );
-};
-
-interface SettingItemContainerWithoutChildrenProps {
- title: I18nText;
- subtext?: I18nText;
- first?: boolean;
- danger?: boolean;
- style?: React.CSSProperties;
- className?: string;
- onClick?: () => void;
-}
-
-interface SettingItemContainerProps
- extends SettingItemContainerWithoutChildrenProps {
- children?: React.ReactNode;
-}
-
-function SettingItemContainer({
- title,
- subtext,
- first,
- danger,
- children,
- style,
- className,
- onClick,
-}: SettingItemContainerProps): JSX.Element {
- const { t } = useTranslation();
-
- return (
- <div
- style={style}
- className={classNames(
- "row settings-item mx-0",
- first && "first",
- onClick && "clickable",
- className,
- )}
- onClick={onClick}
- >
- <div className="px-0 col col-auto">
- <div className={classNames(danger && "cru-color-danger")}>
- {convertI18nText(title, t)}
- </div>
- <small className="d-block cru-color-secondary">
- {convertI18nText(subtext, t)}
- </small>
- </div>
- <div className="col col-auto">{children}</div>
- </div>
- );
-}
-
-type ButtonSettingItemProps = SettingItemContainerWithoutChildrenProps;
-
-const ButtonSettingItem: React.FC<ButtonSettingItemProps> = ({ ...props }) => {
- return <SettingItemContainer {...props} />;
-};
-
-interface SelectSettingItemProps
- extends SettingItemContainerWithoutChildrenProps {
- options: {
- value: string;
- label: I18nText;
- }[];
- value?: string;
- onSelect: (value: string) => void;
-}
-
-const SelectSettingsItem: React.FC<SelectSettingItemProps> = ({
- options,
- value,
- onSelect,
- ...props
-}) => {
- const { t } = useTranslation();
-
- return (
- <SettingItemContainer {...props}>
- {value == null ? (
- <Spinner />
- ) : (
- <select
- value={value}
- onChange={(e) => {
- onSelect(e.target.value);
- }}
- >
- {options.map(({ value, label }) => (
- <option key={value} value={value}>
- {convertI18nText(label, t)}
- </option>
- ))}
- </select>
- )}
- </SettingItemContainer>
- );
-};
-
-const SettingsPage: React.FC = () => {
- const { i18n } = useTranslation();
- const user = useUser();
- const navigate = useNavigate();
-
- const [dialog, setDialog] = useState<
- | null
- | "changepassword"
- | "changeavatar"
- | "changenickname"
- | "logout"
- | "renewregistercode"
- >(null);
-
- const [registerCode, setRegisterCode] = useState<undefined | null | string>(
- undefined,
- );
-
- const [bookmarkVisibility, setBookmarkVisibility] =
- useState<TimelineVisibility>();
-
- React.useEffect(() => {
- if (user != null) {
- void getHttpUserClient()
- .getBookmarkVisibility(user.username)
- .then(({ visibility }) => {
- setBookmarkVisibility(visibility);
- });
- } else {
- setBookmarkVisibility(undefined);
- }
- }, [user]);
-
- React.useEffect(() => {
- setRegisterCode(undefined);
- }, [user]);
-
- React.useEffect(() => {
- if (user != null && registerCode === undefined) {
- void getHttpUserClient()
- .getRegisterCode(user.username)
- .then((code) => {
- setRegisterCode(code.registerCode ?? null);
- });
- }
- }, [user, registerCode]);
-
- const language = i18n.language.slice(0, 2);
-
- return (
- <>
- <div className="container">
- {user ? (
- <SettingSection title="settings.subheaders.account">
- <SettingItemContainer
- title="settings.myRegisterCode"
- subtext="settings.myRegisterCodeDesc"
- onClick={() => setDialog("renewregistercode")}
- >
- {registerCode === undefined ? (
- <Spinner />
- ) : registerCode === null ? (
- <span>Noop</span>
- ) : (
- <code
- className="register-code"
- onClick={(event) => {
- void navigator.clipboard
- .writeText(registerCode)
- .then(() => {
- pushAlert({
- type: "success",
- message: "settings.myRegisterCodeCopied",
- });
- });
- event.stopPropagation();
- }}
- >
- {registerCode}
- </code>
- )}
- </SettingItemContainer>
- <ButtonSettingItem
- title="settings.changeAvatar"
- onClick={() => setDialog("changeavatar")}
- first
- />
- <ButtonSettingItem
- title="settings.changeNickname"
- onClick={() => setDialog("changenickname")}
- />
- <SelectSettingsItem
- title="settings.changeBookmarkVisibility"
- options={[
- {
- value: "Private",
- label: "visibility.private",
- },
- {
- value: "Register",
- label: "visibility.register",
- },
- {
- value: "Public",
- label: "visibility.public",
- },
- ]}
- value={bookmarkVisibility}
- onSelect={(value) => {
- void getHttpUserClient()
- .putBookmarkVisibility(user.username, {
- visibility: value as TimelineVisibility,
- })
- .then(() => {
- setBookmarkVisibility(value as TimelineVisibility);
- });
- }}
- />
- <ButtonSettingItem
- title="settings.changePassword"
- onClick={() => setDialog("changepassword")}
- danger
- />
- <ButtonSettingItem
- title="settings.logout"
- onClick={() => {
- setDialog("logout");
- }}
- danger
- />
- </SettingSection>
- ) : null}
- <SettingSection title="settings.subheaders.customization">
- <SelectSettingsItem
- title="settings.languagePrimary"
- subtext="settings.languageSecondary"
- options={[
- {
- value: "zh",
- label: {
- type: "custom",
- value: "中文",
- },
- },
- {
- value: "en",
- label: {
- type: "custom",
- value: "English",
- },
- },
- ]}
- value={language}
- onSelect={(value) => {
- void i18n.changeLanguage(value);
- }}
- first
- />
- </SettingSection>
- </div>
- <ChangePasswordDialog
- open={dialog === "changepassword"}
- close={() => setDialog(null)}
- />
- <ConfirmDialog
- title="settings.dialogConfirmLogout.title"
- body="settings.dialogConfirmLogout.prompt"
- onClose={() => setDialog(null)}
- open={dialog === "logout"}
- onConfirm={() => {
- void userService.logout().then(() => {
- navigate("/");
- });
- }}
- />
- <ConfirmDialog
- title="settings.renewRegisterCode"
- body="settings.renewRegisterCodeDesc"
- onClose={() => setDialog(null)}
- open={dialog === "renewregistercode"}
- onConfirm={() => {
- if (user == null) throw new UiLogicError();
- void getHttpUserClient()
- .renewRegisterCode(user.username)
- .then(() => {
- setRegisterCode(undefined);
- });
- }}
- />
- <ChangeAvatarDialog
- open={dialog === "changeavatar"}
- close={() => setDialog(null)}
- />
- <ChangeNicknameDialog
- open={dialog === "changenickname"}
- close={() => setDialog(null)}
- />
- </>
- );
-};
-
-export default SettingsPage;