aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/views/settings/index.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/views/settings/index.tsx')
-rw-r--r--FrontEnd/src/views/settings/index.tsx141
1 files changed, 141 insertions, 0 deletions
diff --git a/FrontEnd/src/views/settings/index.tsx b/FrontEnd/src/views/settings/index.tsx
new file mode 100644
index 00000000..840bb7e8
--- /dev/null
+++ b/FrontEnd/src/views/settings/index.tsx
@@ -0,0 +1,141 @@
+import React, { useState } from "react";
+import { useHistory } from "react-router";
+import { useTranslation } from "react-i18next";
+import { Container, Form, Row, Col, Button, Modal } from "react-bootstrap";
+
+import { useUser, userService } from "@/services/user";
+
+import ChangePasswordDialog from "./ChangePasswordDialog";
+import ChangeAvatarDialog from "./ChangeAvatarDialog";
+import ChangeNicknameDialog from "./ChangeNicknameDialog";
+import Card from "../common/Card";
+
+import "./index.css";
+
+const ConfirmLogoutDialog: React.FC<{
+ onClose: () => void;
+ onConfirm: () => void;
+}> = ({ onClose, onConfirm }) => {
+ const { t } = useTranslation();
+
+ return (
+ <Modal show centered onHide={onClose}>
+ <Modal.Header>
+ <Modal.Title className="text-danger">
+ {t("settings.dialogConfirmLogout.title")}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>{t("settings.dialogConfirmLogout.prompt")}</Modal.Body>
+ <Modal.Footer>
+ <Button variant="secondary" onClick={onClose}>
+ {t("operationDialog.cancel")}
+ </Button>
+ <Button variant="danger" onClick={onConfirm}>
+ {t("operationDialog.confirm")}
+ </Button>
+ </Modal.Footer>
+ </Modal>
+ );
+};
+
+const SettingsPage: React.FC = (_) => {
+ const { i18n, t } = useTranslation();
+ const user = useUser();
+ const history = useHistory();
+
+ const [dialog, setDialog] = useState<
+ null | "changepassword" | "changeavatar" | "changenickname" | "logout"
+ >(null);
+
+ const language = i18n.language.slice(0, 2);
+
+ return (
+ <>
+ <Container>
+ {user ? (
+ <Card className="my-3 py-3">
+ <h3 className="px-3 mb-3 text-primary">
+ {t("settings.subheaders.account")}
+ </h3>
+ <div
+ className="settings-item clickable first"
+ onClick={() => setDialog("changeavatar")}
+ >
+ {t("settings.changeAvatar")}
+ </div>
+ <div
+ className="settings-item clickable"
+ onClick={() => setDialog("changenickname")}
+ >
+ {t("settings.changeNickname")}
+ </div>
+ <div
+ className="settings-item clickable text-danger"
+ onClick={() => setDialog("changepassword")}
+ >
+ {t("settings.changePassword")}
+ </div>
+ <div
+ className="settings-item clickable text-danger"
+ onClick={() => {
+ setDialog("logout");
+ }}
+ >
+ {t("settings.logout")}
+ </div>
+ </Card>
+ ) : null}
+ <Card className="my-3 py-3">
+ <h3 className="px-3 mb-3 text-primary">
+ {t("settings.subheaders.customization")}
+ </h3>
+ <Row className="settings-item first mx-0">
+ <Col xs="12" sm="auto">
+ <div>{t("settings.languagePrimary")}</div>
+ <small className="d-block text-secondary">
+ {t("settings.languageSecondary")}
+ </small>
+ </Col>
+ <Col xs="auto" className="ms-auto">
+ <Form.Control
+ as="select"
+ value={language}
+ onChange={(e) => {
+ void i18n.changeLanguage(e.target.value);
+ }}
+ >
+ <option value="zh">中文</option>
+ <option value="en">English</option>
+ </Form.Control>
+ </Col>
+ </Row>
+ </Card>
+ </Container>
+ {(() => {
+ switch (dialog) {
+ case "changepassword":
+ return <ChangePasswordDialog open close={() => setDialog(null)} />;
+ case "logout":
+ return (
+ <ConfirmLogoutDialog
+ onClose={() => setDialog(null)}
+ onConfirm={() => {
+ void userService.logout().then(() => {
+ history.push("/");
+ });
+ }}
+ />
+ );
+ case "changeavatar":
+ return <ChangeAvatarDialog open close={() => setDialog(null)} />;
+ case "changenickname":
+ return <ChangeNicknameDialog open close={() => setDialog(null)} />;
+ default:
+ return null;
+ }
+ })()}
+ </>
+ );
+};
+
+export default SettingsPage;