diff options
Diffstat (limited to 'FrontEnd/src/app/views')
-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.tsx | 68 | ||||
-rw-r--r-- | FrontEnd/src/app/views/settings/index.tsx | 94 | ||||
-rw-r--r-- | FrontEnd/src/app/views/user/UserCard.tsx | 52 |
5 files changed, 127 insertions, 149 deletions
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(); - }} - /> - ); - } - })()} </> ); }; |