From 12c2e13505b59e890654ab5c69dc4f06701b087e Mon Sep 17 00:00:00 2001 From: CrescentLeaf Date: Sat, 13 Sep 2025 00:39:58 +0800 Subject: [PATCH] feat(wip): user profile dialog --- client/ui/App.tsx | 39 +++++++++---- client/ui/dialog/UserProfileDialog.tsx | 80 ++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 client/ui/dialog/UserProfileDialog.tsx diff --git a/client/ui/App.tsx b/client/ui/App.tsx index 5aaed24..42d0b73 100644 --- a/client/ui/App.tsx +++ b/client/ui/App.tsx @@ -1,25 +1,29 @@ import Client from "../api/Client.ts" import data from "../Data.ts" import ChatFragment from "./chat/ChatFragment.jsx" -import LoginDialog from "./dialog/LoginDialog.tsx" import ContactsListItem from "./main/ContactsListItem.jsx" import RecentsListItem from "./main/RecentsListItem.jsx" import useEventListener from './useEventListener.ts' import User from "../api/client_data/User.ts" import RecentChat from "../api/client_data/RecentChat.ts" +import Avatar from "./Avatar.tsx" import * as React from 'react' -import { Button, Dialog, NavigationRail, TextField } from "mdui" +import { Button, ButtonIcon, Dialog, NavigationRail, TextField } from "mdui" import Split from 'split.js' import 'mdui/jsx.zh-cn.d.ts' -import { checkApiSuccessOrSncakbar } from "./snackbar.ts"; -import RegisterDialog from "./dialog/RegisterDialog.tsx"; +import { checkApiSuccessOrSncakbar } from "./snackbar.ts" + +import RegisterDialog from "./dialog/RegisterDialog.tsx" +import LoginDialog from "./dialog/LoginDialog.tsx" +import UserProfileDialog from "./dialog/UserProfileDialog.tsx" declare global { namespace React { namespace JSX { interface IntrinsicAttributes { id?: string + slot?: string } } } @@ -57,7 +61,7 @@ export default function App() { const [navigationItemSelected, setNavigationItemSelected] = React.useState('Recents') const navigationRailRef: React.MutableRefObject = React.useRef(null) - useEventListener(navigationRailRef as React.MutableRefObject, 'change', (event) => { + useEventListener(navigationRailRef, 'change', (event) => { setNavigationItemSelected((event.target as HTMLElement as NavigationRail).value as string) }) @@ -70,6 +74,14 @@ export default function App() { const registerInputNickNameRef: React.MutableRefObject = React.useRef(null) const registerInputPasswordRef: React.MutableRefObject = React.useRef(null) + const userProfileDialogRef: React.MutableRefObject = React.useRef(null) + const openMyUserProfileDialogButtonRef: React.MutableRefObject = React.useRef(null) + useEventListener(openMyUserProfileDialogButtonRef, 'click', (_event) => { + userProfileDialogRef.current!.open = true + }) + + const [myUserProfileCache, setMyUserProfileCache]: [User, React.Dispatch>] = React.useState(null as unknown as User) + React.useEffect(() => { ; (async () => { Split(['#SideBar', '#ChatFragment'], { @@ -79,13 +91,14 @@ export default function App() { }) Client.connect() - const re = await Client.invoke("User.auth", { - access_token: data.access_token || '', - }) + const re = await Client.auth(data.access_token || "") if (re.code == 401) loginDialogRef.current!.open = true - else if (re.code != 200) + else if (re.code != 200) { if (checkApiSuccessOrSncakbar(re, "驗證失敗")) return + } else if (re.code == 200) { + setMyUserProfileCache(Client.myUserProfile as User) + } })() }, []) @@ -110,8 +123,14 @@ export default function App() { loginInputAccountRef={loginInputAccountRef} loginInputPasswordRef={loginInputPasswordRef} /> + + - + + + diff --git a/client/ui/dialog/UserProfileDialog.tsx b/client/ui/dialog/UserProfileDialog.tsx new file mode 100644 index 0000000..4f76636 --- /dev/null +++ b/client/ui/dialog/UserProfileDialog.tsx @@ -0,0 +1,80 @@ +import * as React from 'react' +import { Button, Dialog, TextField } from "mdui" +import useEventListener from "../useEventListener.ts" +import { checkApiSuccessOrSncakbar, snackbar } from "../snackbar.ts" +import Client from "../../api/Client.ts" + +import * as CryptoJS from 'crypto-js' +import data from "../../Data.ts" +import Avatar from "../Avatar.tsx" +import User from "../../api/client_data/User.ts" + +interface Refs { + userProfileDialogRef: React.MutableRefObject + user: User +} + +export default function UserProfileDialog({ + userProfileDialogRef, + user +}: Refs) { + const isMySelf = Client.myUserProfile?.id == user?.id + + const editAvatarButtonRef: React.MutableRefObject = React.useRef(null) + const chooseAvatarFileRef: React.MutableRefObject = React.useRef(null) + useEventListener(editAvatarButtonRef, 'click', () => chooseAvatarFileRef.current!.click()) + useEventListener(chooseAvatarFileRef, 'change', async (_e) => { + const file = chooseAvatarFileRef.current!.files?.[0] as File + if (file == null) return + + const re = await Client.invoke("User.setAvatar", { + token: data.access_token, + avatar: file + }) + + if (checkApiSuccessOrSncakbar(re, "修改失敗")) return + snackbar({ + message: "修改成功 (刷新頁面以更新)" + }) + }) + + return ( + +
+ +
+ +
+ + {user?.nickname} +
+ + + + {!isMySelf && 編輯聯絡人訊息} + { + isMySelf && <> + 編輯資料 + 賬號設定 + 隱私設定 + + } + +
+ ) +} \ No newline at end of file