diff options
author | crupest <crupest@outlook.com> | 2023-07-20 23:01:58 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2023-07-20 23:01:58 +0800 |
commit | 2226efed8c8604a938d060d62565b611722e837c (patch) | |
tree | 1f87047fe87c66e101d6673fe09417ff62007543 /FrontEnd/src/pages | |
parent | 0e183074b326cf04a23ae1f1ba8dcc56166df485 (diff) | |
download | timeline-2226efed8c8604a938d060d62565b611722e837c.tar.gz timeline-2226efed8c8604a938d060d62565b611722e837c.tar.bz2 timeline-2226efed8c8604a938d060d62565b611722e837c.zip |
...
Diffstat (limited to 'FrontEnd/src/pages')
-rw-r--r-- | FrontEnd/src/pages/setting/index.tsx | 279 |
1 files changed, 124 insertions, 155 deletions
diff --git a/FrontEnd/src/pages/setting/index.tsx b/FrontEnd/src/pages/setting/index.tsx index 00503dcf..4e28585e 100644 --- a/FrontEnd/src/pages/setting/index.tsx +++ b/FrontEnd/src/pages/setting/index.tsx @@ -1,16 +1,21 @@ -import { useState, ReactNode } from "react"; -import { useNavigate } from "react-router-dom"; +import { + useState, + useEffect, + ReactNode, + ComponentPropsWithoutRef, +} from "react"; import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; import classNames from "classnames"; -import { useC, I18nText } from "@/common"; +import { useC, Text } 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 ConfirmDialog from "@/views/common/dialog/ConfirmDialog"; +import Card from "@/views/common/Card"; +import Spinner from "@/views/common/Spinner"; import ChangePasswordDialog from "./ChangePasswordDialog"; import ChangeAvatarDialog from "./ChangeAvatarDialog"; import ChangeNicknameDialog from "./ChangeNicknameDialog"; @@ -18,99 +23,94 @@ import ChangeNicknameDialog from "./ChangeNicknameDialog"; import "./index.css"; import { pushAlert } from "@/services/alert"; -interface SettingSectionProps { - title: I18nText; +interface SettingSectionProps + extends Omit<ComponentPropsWithoutRef<"div">, "title"> { + title: Text; children: ReactNode; } -function SettingSection({ title, children }: SettingSectionProps) { +function SettingSection({ + title, + className, + children, + ...otherProps +}: SettingSectionProps) { const c = useC(); return ( - <Card> - <h2 className="">{c(title)}</h2> + <Card className={classNames(className, "setting-section")} {...otherProps}> + <h2 className="setting-section-title">{c(title)}</h2> {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; + extends Omit<ComponentPropsWithoutRef<"div">, "title"> { + title: Text; + description?: Text; + danger?: boolean; + extraClassName?: string; } function SettingItemContainer({ title, - subtext, - first, + description, danger, - children, - style, + extraClassName, className, - onClick, -}: SettingItemContainerProps): JSX.Element { - const { t } = useTranslation(); + children, + ...otherProps +}: SettingItemContainerProps) { + const c = useC(); return ( <div - style={style} className={classNames( - "row settings-item mx-0", - first && "first", - onClick && "clickable", className, + "setting-item-container", + danger && "danger", + extraClassName, )} - onClick={onClick} + {...otherProps} > - <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 className="setting-item-label-area"> + <div className="setting-item-label-title">{c(title)}</div> + <small className="setting-item-label-sub">{c(description)}</small> </div> - <div className="col col-auto">{children}</div> + <div className="setting-item-value-area">{children}</div> </div> ); } -type ButtonSettingItemProps = SettingItemContainerWithoutChildrenProps; +type ButtonSettingItemProps = Omit<SettingItemContainerProps, "extraClassName">; -const ButtonSettingItem: React.FC<ButtonSettingItemProps> = ({ ...props }) => { - return <SettingItemContainer {...props} />; -}; +function ButtonSettingItem(props: ButtonSettingItemProps) { + return ( + <SettingItemContainer extraClassName="setting-type-button" {...props} /> + ); +} interface SelectSettingItemProps - extends SettingItemContainerWithoutChildrenProps { + extends Omit<SettingItemContainerProps, "onSelect" | "extraClassName"> { options: { value: string; - label: I18nText; + label: Text; }[]; - value?: string; + value?: string | null; onSelect: (value: string) => void; } -const SelectSettingsItem: React.FC<SelectSettingItemProps> = ({ +function SelectSettingsItem({ options, value, onSelect, - ...props -}) => { - const { t } = useTranslation(); + ...extraProps +}: SelectSettingItemProps) { + const c = useC(); return ( - <SettingItemContainer {...props}> + <SettingItemContainer extraClassName="setting-type-select" {...extraProps}> {value == null ? ( <Spinner /> ) : ( @@ -122,53 +122,30 @@ const SelectSettingsItem: React.FC<SelectSettingItemProps> = ({ > {options.map(({ value, label }) => ( <option key={value} value={value}> - {convertI18nText(label, t)} + {c(label)} </option> ))} </select> )} </SettingItemContainer> ); -}; +} -const SettingsPage: React.FC = () => { - const { i18n } = useTranslation(); +function RegisterCodeSettingItem({ + openRenewDialog, +}: { + openRenewDialog: () => void; +}) { 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]); + // undefined: loading + const [registerCode, setRegisterCode] = useState<undefined | null | string>(); - React.useEffect(() => { + useEffect(() => { setRegisterCode(undefined); }, [user]); - React.useEffect(() => { + useEffect(() => { if (user != null && registerCode === undefined) { void getHttpUserClient() .getRegisterCode(user.username) @@ -178,87 +155,81 @@ const SettingsPage: React.FC = () => { } }, [user, registerCode]); + return ( + <SettingItemContainer + title="settings.myRegisterCode" + description="settings.myRegisterCodeDesc" + onClick={openRenewDialog} + > + {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> + ); +} + +export default function SettingsPage() { + const c = useC(); + const { i18n } = useTranslation(); + const user = useUser(); + const navigate = useNavigate(); + + type DialogName = + | "change-password" + | "change-avatar" + | "change-nickname" + | "logout" + | "renew-register-code"; + + const [dialog, setDialog] = useState<null | DialogName>(null); + + function dialogOpener(name: DialogName): () => void { + return () => setDialog(name); + } + 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> + <SettingSection title="settings.subheader.account"> + <RegisterCodeSettingItem + openRenewDialog={dialogOpener("renew-register-code")} + /> <ButtonSettingItem title="settings.changeAvatar" - onClick={() => setDialog("changeavatar")} - first + onClick={dialogOpener("change-avatar")} /> <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); - }); - }} + onClick={dialogOpener("change-nickname")} /> <ButtonSettingItem title="settings.changePassword" - onClick={() => setDialog("changepassword")} + onClick={dialogOpener("change-password")} danger /> <ButtonSettingItem title="settings.logout" - onClick={() => { - setDialog("logout"); - }} + onClick={dialogOpener("logout")} danger /> </SettingSection> @@ -330,6 +301,4 @@ const SettingsPage: React.FC = () => { /> </> ); -}; - -export default SettingsPage; +} |