aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src')
-rw-r--r--FrontEnd/src/app/locales/en/translation.json37
-rw-r--r--FrontEnd/src/app/locales/zh/translation.json37
-rw-r--r--FrontEnd/src/app/views/settings/ChangeAvatarDialog.tsx (renamed from FrontEnd/src/app/views/user/ChangeAvatarDialog.tsx)49
-rw-r--r--FrontEnd/src/app/views/settings/ChangeNicknameDialog.tsx (renamed from FrontEnd/src/app/views/user/ChangeNicknameDialog.tsx)13
-rw-r--r--FrontEnd/src/app/views/settings/ChangePasswordDialog.tsx68
-rw-r--r--FrontEnd/src/app/views/settings/index.tsx94
-rw-r--r--FrontEnd/src/app/views/user/UserCard.tsx52
7 files changed, 163 insertions, 187 deletions
diff --git a/FrontEnd/src/app/locales/en/translation.json b/FrontEnd/src/app/locales/en/translation.json
index 274d629b..63b2a1be 100644
--- a/FrontEnd/src/app/locales/en/translation.json
+++ b/FrontEnd/src/app/locales/en/translation.json
@@ -129,24 +129,6 @@
"badCredential": "Username or password is invalid.",
"alreadyLogin": "Already login! Redirect to home page in 3s!"
},
- "userPage": {
- "dialogChangeNickname": {
- "title": "Change Nickname",
- "inputLabel": "New nickname"
- },
- "dialogChangeAvatar": {
- "title": "Change Avatar",
- "previewImgAlt": "preview",
- "prompt": {
- "select": "Please select a picture.",
- "crop": "Please crop the picture.",
- "processingCrop": "Cropping picture...",
- "uploading": "Uploading...",
- "preview": "Please preview avatar"
- },
- "upload": "upload"
- }
- },
"settings": {
"subheaders": {
"account": "Account",
@@ -156,7 +138,8 @@
"languageSecondary": "You language preference will be saved locally. Next time you visit this page, last language option will be used.",
"changePassword": "Change account's password.",
"logout": "Log out this account.",
- "gotoSelf": "Click here to go to timeline of myself to change nickname and avatar.",
+ "changeAvatar": "Change avatar.",
+ "changeNickname": "Change nickname.",
"dialogChangePassword": {
"title": "Change Password",
"prompt": "You are changing your password. You need to input the correct old password. After change, you need to login again and all old login will be invalid.",
@@ -170,6 +153,22 @@
"dialogConfirmLogout": {
"title": "Confirm Logout",
"prompt": "Are you sure to log out? All cached data in the browser will be deleted."
+ },
+ "dialogChangeNickname": {
+ "title": "Change Nickname",
+ "inputLabel": "New nickname"
+ },
+ "dialogChangeAvatar": {
+ "title": "Change Avatar",
+ "previewImgAlt": "preview",
+ "prompt": {
+ "select": "Please select a picture.",
+ "crop": "Please crop the picture.",
+ "processingCrop": "Cropping picture...",
+ "uploading": "Uploading...",
+ "preview": "Please preview avatar"
+ },
+ "upload": "upload"
}
},
"about": {
diff --git a/FrontEnd/src/app/locales/zh/translation.json b/FrontEnd/src/app/locales/zh/translation.json
index 759dc63c..296966c4 100644
--- a/FrontEnd/src/app/locales/zh/translation.json
+++ b/FrontEnd/src/app/locales/zh/translation.json
@@ -129,24 +129,6 @@
"badCredential": "用户名或密码错误。",
"alreadyLogin": "已经登陆,三秒后导航到首页!"
},
- "userPage": {
- "dialogChangeNickname": {
- "title": "更改昵称",
- "inputLabel": "新昵称"
- },
- "dialogChangeAvatar": {
- "title": "修改头像",
- "previewImgAlt": "预览",
- "prompt": {
- "select": "请选择一个图片",
- "crop": "请裁剪图片",
- "processingCrop": "正在裁剪图片",
- "uploading": "正在上传",
- "preview": "请预览图片"
- },
- "upload": "上传"
- }
- },
"settings": {
"subheaders": {
"account": "账户",
@@ -156,7 +138,8 @@
"languageSecondary": "您的语言偏好将会存储在本地,下次浏览时将自动使用上次保存的语言选项。",
"changePassword": "更改账号的密码。",
"logout": "注销此账号。",
- "gotoSelf": "点击前往个人时间线修改昵称和头像!",
+ "changeAvatar": "更改头像。",
+ "changeNickname": "更改昵称。",
"dialogChangePassword": {
"title": "修改密码",
"prompt": "您正在修改密码,您需要输入正确的旧密码。成功修改后您需要重新登陆,而且以前所有的登录都会失效。",
@@ -170,6 +153,22 @@
"dialogConfirmLogout": {
"title": "确定注销",
"prompt": "您确定注销此账号?这将删除所有已经缓存在浏览器的数据。"
+ },
+ "dialogChangeNickname": {
+ "title": "更改昵称",
+ "inputLabel": "新昵称"
+ },
+ "dialogChangeAvatar": {
+ "title": "修改头像",
+ "previewImgAlt": "预览",
+ "prompt": {
+ "select": "请选择一个图片",
+ "crop": "请裁剪图片",
+ "processingCrop": "正在裁剪图片",
+ "uploading": "正在上传",
+ "preview": "请预览图片"
+ },
+ "upload": "上传"
}
},
"about": {
diff --git a/FrontEnd/src/app/views/user/ChangeAvatarDialog.tsx b/FrontEnd/src/app/views/settings/ChangeAvatarDialog.tsx
index ffa2218b..53ffbc8d 100644
--- a/FrontEnd/src/app/views/user/ChangeAvatarDialog.tsx
+++ b/FrontEnd/src/app/views/settings/ChangeAvatarDialog.tsx
@@ -5,17 +5,22 @@ import { Modal, Row, Button } from "react-bootstrap";
import { UiLogicError } from "@/common";
+import { useUserLoggedIn } from "@/services/user";
+
+import { getHttpUserClient } from "@/http/user";
+
import ImageCropper, { Clip, applyClipToImage } from "../common/ImageCropper";
export interface ChangeAvatarDialogProps {
open: boolean;
close: () => void;
- process: (blob: Blob) => Promise<void>;
}
const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
const { t } = useTranslation();
+ const user = useUserLoggedIn();
+
const [file, setFile] = React.useState<File | null>(null);
const [fileUrl, setFileUrl] = React.useState<string | null>(null);
const [clip, setClip] = React.useState<Clip | null>(null);
@@ -38,7 +43,7 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
const [message, setMessage] = useState<
string | { type: "custom"; text: string } | null
- >("userPage.dialogChangeAvatar.prompt.select");
+ >("settings.dialogChangeAvatar.prompt.select");
const trueMessage =
message == null
@@ -121,24 +126,24 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
setState("crop");
}, []);
- const process = props.process;
-
const upload = React.useCallback(() => {
if (resultBlob == null) {
throw new UiLogicError();
}
setState("uploading");
- process(resultBlob).then(
- () => {
- setState("success");
- },
- (e: unknown) => {
- setState("error");
- setMessage({ type: "custom", text: (e as AxiosError).message });
- }
- );
- }, [resultBlob, process]);
+ getHttpUserClient()
+ .putAvatar(user.username, resultBlob)
+ .then(
+ () => {
+ setState("success");
+ },
+ (e: unknown) => {
+ setState("error");
+ setMessage({ type: "custom", text: (e as AxiosError).message });
+ }
+ );
+ }, [user.username, resultBlob]);
const createPreviewRow = (): React.ReactElement => {
if (resultUrl == null) {
@@ -149,7 +154,7 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
<img
className="change-avatar-img"
src={resultUrl}
- alt={t("userPage.dialogChangeAvatar.previewImgAlt")}
+ alt={t("settings.dialogChangeAvatar.previewImgAlt")}
/>
</Row>
);
@@ -158,14 +163,14 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
return (
<Modal show={props.open} onHide={close}>
<Modal.Header>
- <Modal.Title> {t("userPage.dialogChangeAvatar.title")}</Modal.Title>
+ <Modal.Title> {t("settings.dialogChangeAvatar.title")}</Modal.Title>
</Modal.Header>
{(() => {
if (state === "select") {
return (
<>
<Modal.Body className="container">
- <Row>{t("userPage.dialogChangeAvatar.prompt.select")}</Row>
+ <Row>{t("settings.dialogChangeAvatar.prompt.select")}</Row>
<Row>
<input type="file" accept="image/*" onChange={onSelectFile} />
</Row>
@@ -192,7 +197,7 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
imageElementCallback={setCropImgElement}
/>
</Row>
- <Row>{t("userPage.dialogChangeAvatar.prompt.crop")}</Row>
+ <Row>{t("settings.dialogChangeAvatar.prompt.crop")}</Row>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={close}>
@@ -218,7 +223,7 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
<>
<Modal.Body className="container">
<Row>
- {t("userPage.dialogChangeAvatar.prompt.processingCrop")}
+ {t("settings.dialogChangeAvatar.prompt.processingCrop")}
</Row>
</Modal.Body>
<Modal.Footer>
@@ -236,7 +241,7 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
<>
<Modal.Body className="container">
{createPreviewRow()}
- <Row>{t("userPage.dialogChangeAvatar.prompt.preview")}</Row>
+ <Row>{t("settings.dialogChangeAvatar.prompt.preview")}</Row>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={close}>
@@ -246,7 +251,7 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
{t("operationDialog.previousStep")}
</Button>
<Button variant="primary" onClick={upload}>
- {t("userPage.dialogChangeAvatar.upload")}
+ {t("settings.dialogChangeAvatar.upload")}
</Button>
</Modal.Footer>
</>
@@ -256,7 +261,7 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => {
<>
<Modal.Body className="container">
{createPreviewRow()}
- <Row>{t("userPage.dialogChangeAvatar.prompt.uploading")}</Row>
+ <Row>{t("settings.dialogChangeAvatar.prompt.uploading")}</Row>
</Modal.Body>
<Modal.Footer></Modal.Footer>
</>
diff --git a/FrontEnd/src/app/views/user/ChangeNicknameDialog.tsx b/FrontEnd/src/app/views/settings/ChangeNicknameDialog.tsx
index f319ac37..4b44cdd6 100644
--- a/FrontEnd/src/app/views/user/ChangeNicknameDialog.tsx
+++ b/FrontEnd/src/app/views/settings/ChangeNicknameDialog.tsx
@@ -1,3 +1,5 @@
+import { getHttpUserClient } from "@/http/user";
+import { useUserLoggedIn } from "@/services/user";
import React from "react";
import OperationDialog from "../common/OperationDialog";
@@ -5,19 +7,22 @@ import OperationDialog from "../common/OperationDialog";
export interface ChangeNicknameDialogProps {
open: boolean;
close: () => void;
- onProcess: (newNickname: string) => Promise<void>;
}
const ChangeNicknameDialog: React.FC<ChangeNicknameDialogProps> = (props) => {
+ const user = useUserLoggedIn();
+
return (
<OperationDialog
open={props.open}
- title="userPage.dialogChangeNickname.title"
+ title="settings.dialogChangeNickname.title"
inputScheme={[
- { type: "text", label: "userPage.dialogChangeNickname.inputLabel" },
+ { type: "text", label: "settings.dialogChangeNickname.inputLabel" },
]}
onProcess={([newNickname]) => {
- return props.onProcess(newNickname);
+ return getHttpUserClient().patch(user.username, {
+ nickname: newNickname,
+ });
}}
close={props.close}
/>
diff --git a/FrontEnd/src/app/views/settings/ChangePasswordDialog.tsx b/FrontEnd/src/app/views/settings/ChangePasswordDialog.tsx
new file mode 100644
index 00000000..21eeeb09
--- /dev/null
+++ b/FrontEnd/src/app/views/settings/ChangePasswordDialog.tsx
@@ -0,0 +1,68 @@
+import React, { useState } from "react";
+import { useHistory } from "react-router";
+
+import { userService } from "@/services/user";
+
+import OperationDialog from "../common/OperationDialog";
+
+export interface ChangePasswordDialogProps {
+ open: boolean;
+ close: () => void;
+}
+
+const ChangePasswordDialog: React.FC<ChangePasswordDialogProps> = (props) => {
+ const history = useHistory();
+
+ 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);
+ }}
+ close={() => {
+ props.close();
+ if (redirect) {
+ history.push("/login");
+ }
+ }}
+ />
+ );
+};
+
+export default ChangePasswordDialog;
diff --git a/FrontEnd/src/app/views/settings/index.tsx b/FrontEnd/src/app/views/settings/index.tsx
index ccba59b7..6710ea25 100644
--- a/FrontEnd/src/app/views/settings/index.tsx
+++ b/FrontEnd/src/app/views/settings/index.tsx
@@ -4,67 +4,10 @@ import { useTranslation } from "react-i18next";
import { Container, Form, Row, Col, Button, Modal } from "react-bootstrap";
import { useUser, userService } from "@/services/user";
-import OperationDialog from "../common/OperationDialog";
-interface ChangePasswordDialogProps {
- open: boolean;
- close: () => void;
-}
-
-const ChangePasswordDialog: React.FC<ChangePasswordDialogProps> = (props) => {
- const history = useHistory();
-
- 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);
- }}
- close={() => {
- props.close();
- if (redirect) {
- history.push("/login");
- }
- }}
- />
- );
-};
+import ChangePasswordDialog from "./ChangePasswordDialog";
+import ChangeAvatarDialog from "./ChangeAvatarDialog";
+import ChangeNicknameDialog from "./ChangeNicknameDialog";
const ConfirmLogoutDialog: React.FC<{
onClose: () => void;
@@ -97,9 +40,9 @@ const SettingsPage: React.FC = (_) => {
const user = useUser();
const history = useHistory();
- const [dialog, setDialog] = useState<null | "changepassword" | "logout">(
- null
- );
+ const [dialog, setDialog] = useState<
+ null | "changepassword" | "changeavatar" | "changenickname" | "logout"
+ >(null);
const language = i18n.language.slice(0, 2);
@@ -113,11 +56,15 @@ const SettingsPage: React.FC = (_) => {
</h3>
<div
className="settings-item clickable first"
- onClick={() => {
- history.push(`/users/${user.username}`);
- }}
+ onClick={() => setDialog("changeavatar")}
>
- {t("settings.gotoSelf")}
+ {t("settings.changeAvatar")}
+ </div>
+ <div
+ className="settings-item clickable first"
+ onClick={() => setDialog("changenickname")}
+ >
+ {t("settings.changeNickname")}
</div>
<div
className="settings-item clickable text-danger"
@@ -164,14 +111,7 @@ const SettingsPage: React.FC = (_) => {
{(() => {
switch (dialog) {
case "changepassword":
- return (
- <ChangePasswordDialog
- open
- close={() => {
- setDialog(null);
- }}
- />
- );
+ return <ChangePasswordDialog open close={() => setDialog(null)} />;
case "logout":
return (
<ConfirmLogoutDialog
@@ -183,6 +123,10 @@ const SettingsPage: React.FC = (_) => {
}}
/>
);
+ case "changeavatar":
+ return <ChangeAvatarDialog open close={() => setDialog(null)} />;
+ case "changenickname":
+ return <ChangeNicknameDialog open close={() => setDialog(null)} />;
default:
return null;
}
diff --git a/FrontEnd/src/app/views/user/UserCard.tsx b/FrontEnd/src/app/views/user/UserCard.tsx
index 575ca2c1..b2c94457 100644
--- a/FrontEnd/src/app/views/user/UserCard.tsx
+++ b/FrontEnd/src/app/views/user/UserCard.tsx
@@ -5,16 +5,13 @@ import TimelinePageCardTemplate, {
} from "../timeline-common/TimelinePageCardTemplate";
import { TimelinePageCardProps } from "../timeline-common/TimelinePageTemplate";
import UserAvatar from "../common/user/UserAvatar";
-import ChangeNicknameDialog from "./ChangeNicknameDialog";
-import { getHttpUserClient } from "@/http/user";
-import ChangeAvatarDialog from "./ChangeAvatarDialog";
const UserCard: React.FC<TimelinePageCardProps> = (props) => {
- const { timeline, onReload } = props;
+ const { timeline } = props;
- const [dialog, setDialog] = React.useState<
- "member" | "property" | "avatar" | "nickname" | null
- >(null);
+ const [dialog, setDialog] = React.useState<"member" | "property" | null>(
+ null
+ );
return (
<>
@@ -43,16 +40,6 @@ const UserCard: React.FC<TimelinePageCardProps> = (props) => {
items: [
{
type: "button",
- text: "timeline.manageItem.nickname",
- onClick: () => setDialog("nickname"),
- },
- {
- type: "button",
- text: "timeline.manageItem.avatar",
- onClick: () => setDialog("avatar"),
- },
- {
- type: "button",
text: "timeline.manageItem.property",
onClick: () => setDialog("property"),
},
@@ -69,37 +56,6 @@ const UserCard: React.FC<TimelinePageCardProps> = (props) => {
setDialog={setDialog}
{...props}
/>
- {(() => {
- // TODO: Move this two to settings.
- if (dialog === "nickname") {
- return (
- <ChangeNicknameDialog
- open
- close={() => setDialog(null)}
- onProcess={async (newNickname) => {
- await getHttpUserClient().patch(timeline.owner.username, {
- nickname: newNickname,
- });
- onReload();
- }}
- />
- );
- } else if (dialog === "avatar") {
- return (
- <ChangeAvatarDialog
- open
- close={() => setDialog(null)}
- process={async (file) => {
- await getHttpUserClient().putAvatar(
- timeline.owner.username,
- file
- );
- onReload();
- }}
- />
- );
- }
- })()}
</>
);
};