From 4a2f98dfd2639d645edb37330eaa477ca841e0a1 Mon Sep 17 00:00:00 2001 From: Tristan Yang Date: Wed, 16 Jul 2025 22:25:02 +0800 Subject: [PATCH] refactor: remark --- public/locales/en/chat.json | 6 +- public/locales/zh/chat.json | 2 + src/components/NicknameModal.tsx | 68 +++++++ src/components/Profile/index.tsx | 195 +++++++++++--------- src/components/Profile/remark.tsx | 49 +---- src/components/User/ContextMenu.tsx | 145 ++++++++------- src/routes/chat/SessionList/ContextMenu.tsx | 67 ++++--- 7 files changed, 297 insertions(+), 235 deletions(-) create mode 100644 src/components/NicknameModal.tsx diff --git a/public/locales/en/chat.json b/public/locales/en/chat.json index 733e603c..0409178c 100644 --- a/public/locales/en/chat.json +++ b/public/locales/en/chat.json @@ -66,6 +66,8 @@ "file_expired": "File Expired", "only_owner_can_send": "only Channel owner can send message!", - "remark": "remark", - "remark_placeholder": "remark this user" + "remark": "add friend nickname", + "remark_clear": "reset friend nickname", + "remark_intro": "Find a friend faster with a personal nickname. It will only be visible to you in your direct messages.", + "remark_placeholder": "Set Nickname" } diff --git a/public/locales/zh/chat.json b/public/locales/zh/chat.json index 0df89c32..e6d42ded 100644 --- a/public/locales/zh/chat.json +++ b/public/locales/zh/chat.json @@ -66,5 +66,7 @@ "only_owner_can_send": "只有频道创建者才能发消息!", "remark": "备注", + "remark_clear": "重置备注", + "remark_intro": "给联系人备注,方便日后查找,备注仅您可见", "remark_placeholder": "备注该用户" } diff --git a/src/components/NicknameModal.tsx b/src/components/NicknameModal.tsx new file mode 100644 index 00000000..a4c000a9 --- /dev/null +++ b/src/components/NicknameModal.tsx @@ -0,0 +1,68 @@ +import Modal from "./Modal"; +import { useTranslation } from "react-i18next"; +import StyledButton from "./styled/Button"; +import Input from "./styled/Input"; +import { useUpdateRemarkMutation } from "../app/services/user"; +import { useAppSelector } from "../app/store"; +import { ChangeEvent, useState } from "react"; +import toast from "react-hot-toast"; + +type Props = { + uid: number; + visible: boolean; + updateVisible: (_visible: boolean) => void; +}; + +const NicknameModal = ({ visible, updateVisible, uid }: Props) => { + const remark = useAppSelector((store) => store.footprint.remarkMap[uid] || ""); + const [updateRemark, { isLoading }] = useUpdateRemarkMutation(); + const { t } = useTranslation("chat"); + const { t: ct } = useTranslation("common", { keyPrefix: "action" }); + const [input, setInput] = useState(remark); + const handleChange = (evt: ChangeEvent) => { + setInput(evt.target.value); + }; + const handleOK = async () => { + const { error } = await updateRemark({ contact_uid: uid, remark: input }); + if (!error) { + updateVisible(false); + } else { + toast.error("failed"); + } + }; + if (!visible) return null; + return ( + +
+

+ {t("remark")} +

+

{t("remark_intro")}

+
+ {/* */} + + +
+
+ + {ct("yes")} + + + {ct("cancel")} + +
+
+
+ ); +}; + +export default NicknameModal; diff --git a/src/components/Profile/index.tsx b/src/components/Profile/index.tsx index 41f4a383..d98615c5 100644 --- a/src/components/Profile/index.tsx +++ b/src/components/Profile/index.tsx @@ -1,4 +1,4 @@ -import { FC, memo } from "react"; +import { FC, memo, useState } from "react"; import { useTranslation } from "react-i18next"; import { NavLink } from "react-router-dom"; import Tippy from "@tippyjs/react"; @@ -14,6 +14,7 @@ import Avatar from "../Avatar"; import ContextMenu, { Item } from "../ContextMenu"; import { shallowEqual } from "react-redux"; import Remark from "./remark"; +import NicknameModal from "../NicknameModal"; interface Props { uid: number; @@ -22,8 +23,10 @@ interface Props { } const Profile: FC = ({ uid, type = "embed", cid }) => { + const [remarkVisible, setRemarkVisible] = useState(false); const { data: agoraEnabled } = useGetAgoraStatusQuery(); const { t } = useTranslation("member"); + const { t: chatTrans } = useTranslation("chat"); const { t: ct } = useTranslation(); const { canDM, @@ -57,97 +60,107 @@ const Profile: FC = ({ uid, type = "embed", cid }) => { ); return ( -
- - -

- {name} {canDM && #{uid}} -

- {canCopyEmail && ( - {email} - )} - {/*

{introduction}

*/} - {canDM && ( -
    - -
  • - - {t("send_msg")} -
  • -
    - {agoraEnabled && type == "embed" && ( -
  • - - {t("call")} -
  • - )} - - } + <> + +
    + + +

    + {name} {canDM && #{uid}} +

    + {canCopyEmail && ( + {email} + )} + {/*

    {introduction}

    */} + {canDM && ( +
      -
    • - - {ct("more")} -
    • - -
    - )} -
    + +
  • + + {t("send_msg")} +
  • +
    + {agoraEnabled && type == "embed" && ( +
  • + + {t("call")} +
  • + )} + + } + > +
  • + + {ct("more")} +
  • +
    +
+ )} +
+ ); }; diff --git a/src/components/Profile/remark.tsx b/src/components/Profile/remark.tsx index 2c9aacbd..34bfb6b3 100644 --- a/src/components/Profile/remark.tsx +++ b/src/components/Profile/remark.tsx @@ -1,10 +1,4 @@ -import React, { ChangeEvent, useState } from "react"; import { useAppSelector } from "../../app/store"; -import Input from "../styled/Input"; -import IconEdit from "@/assets/icons/edit.svg"; -import StyledButton from "../styled/Button"; -import { useUpdateRemarkMutation } from "../../app/services/user"; -import { useTranslation } from "react-i18next"; import ServerVersionChecker from "../ServerVersionChecker"; type Props = { @@ -12,50 +6,13 @@ type Props = { }; const Remark = ({ uid }: Props) => { - const { t } = useTranslation("chat"); - const { t: ct } = useTranslation("common", { keyPrefix: "action" }); - const [updateRemark, { isLoading }] = useUpdateRemarkMutation(); - const [editMode, setEditMode] = useState(false); const remark = useAppSelector((store) => store.footprint.remarkMap[uid] || ""); - const [input, setInput] = useState(remark); - const handleChange = (evt: ChangeEvent) => { - setInput(evt.target.value); - }; - const handleOK = async () => { - const { error } = await updateRemark({ contact_uid: uid, remark: input }); - if (!error) { - setEditMode(false); - } - }; return (
- {editMode ? ( -
- -
- - {ct("yes")} - - - {ct("cancel")} - -
-
- ) : ( -
- {remark} - -
- )} +
+ {remark} +
); diff --git a/src/components/User/ContextMenu.tsx b/src/components/User/ContextMenu.tsx index 11935c89..9ca03232 100644 --- a/src/components/User/ContextMenu.tsx +++ b/src/components/User/ContextMenu.tsx @@ -1,9 +1,10 @@ -import { FC, ReactElement } from "react"; +import { FC, ReactElement, useState } from "react"; import { useTranslation } from "react-i18next"; import Tippy from "@tippyjs/react"; import useUserOperation from "@/hooks/useUserOperation"; import ContextMenu, { Item } from "../ContextMenu"; +import NicknameModal from "../NicknameModal"; interface Props { enable?: boolean; @@ -15,6 +16,7 @@ interface Props { } const UserContextMenu: FC = ({ enable = false, uid, cid, visible, hide, children }) => { + const [remarkVisible, setRemarkVisible] = useState(false); const { t } = useTranslation("member"); const { t: chatTran } = useTranslation("chat"); const { @@ -31,77 +33,84 @@ const UserContextMenu: FC = ({ enable = false, uid, cid, visible, hide, c removeUser, isAdmin, canUpdateRole, - updateRole + updateRole, } = useUserOperation({ uid, - cid + cid, }); return ( - - } - > - {children} - + <> + + + } + > + {children} + + ); }; diff --git a/src/routes/chat/SessionList/ContextMenu.tsx b/src/routes/chat/SessionList/ContextMenu.tsx index e8a8f9c8..e88b671f 100644 --- a/src/routes/chat/SessionList/ContextMenu.tsx +++ b/src/routes/chat/SessionList/ContextMenu.tsx @@ -1,4 +1,4 @@ -import { FC, ReactElement } from "react"; +import { FC, ReactElement, useState } from "react"; import { useTranslation } from "react-i18next"; import { shallowEqual, useDispatch } from "react-redux"; import { useLocation, useMatch, useNavigate } from "react-router-dom"; @@ -8,13 +8,15 @@ import { useReadMessageMutation } from "@/app/services/message"; import { usePinChatMutation, useUnpinChatMutation, - useUpdateMuteSettingMutation + useUpdateMuteSettingMutation, } from "@/app/services/user"; import { removeUserSession } from "@/app/slices/message.user"; import { useAppSelector } from "@/app/store"; import { ChatContext } from "@/types/common"; import ContextMenu, { Item } from "@/components/ContextMenu"; import useUserOperation from "@/hooks/useUserOperation"; +import Modal from "../../../components/Modal"; +import NicknameModal from "../../../components/NicknameModal"; type Props = { context: ChatContext; @@ -36,12 +38,14 @@ const SessionContextMenu: FC = ({ hide, deleteChannel, setInviteChannelId, - children + children, }) => { + const [remarkVisible, setRemarkVisible] = useState(false); + const { t: tChat } = useTranslation("chat"); const { t } = useTranslation(); const { canCopyEmail, copyEmail, canDeleteChannel, canInviteChannel } = useUserOperation({ uid: context == "dm" ? id : undefined, - cid: context == "channel" ? id : undefined + cid: context == "channel" ? id : undefined, }); const [muteChannel] = useUpdateMuteSettingMutation(); const [pinChat] = usePinChatMutation(); @@ -97,67 +101,74 @@ const SessionContextMenu: FC = ({ ? [ { title: pinTxt, - handler: handlePinChat + handler: handlePinChat, }, { title: t("action.mark_read"), - handler: handleReadAll + handler: handleReadAll, + }, + { + title: tChat("remark"), + handler: setRemarkVisible.bind(null, true), }, { title: t("setting"), - handler: handleDMSetting + handler: handleDMSetting, }, canCopyEmail && { title: t("action.copy_email"), - handler: copyEmail + handler: copyEmail, }, { title: t("action.hide_session"), danger: true, - handler: handleRemoveSession - } + handler: handleRemoveSession, + }, ] : [ { title: pinTxt, - handler: handlePinChat + handler: handlePinChat, }, { title: t("setting"), underline: true, - handler: handleChannelSetting + handler: handleChannelSetting, }, { title: t("action.mark_read"), // underline: true - handler: handleReadAll + handler: handleReadAll, }, { title: channelMuted ? t("action.unmute") : t("action.mute"), - handler: handleChannelMute + handler: handleChannelMute, }, canInviteChannel && { title: t("action.invite_people"), - handler: setInviteChannelId.bind(null, id) + handler: setInviteChannelId.bind(null, id), }, canDeleteChannel && { title: t("action.delete_channel"), danger: true, - handler: deleteChannel.bind(null, id) - } + handler: deleteChannel.bind(null, id), + }, ]; return ( - } - > - {children} - + <> + + } + > + {children} + + ); }; export default SessionContextMenu;