diff --git a/src/app/components/user-profile/UserHero.tsx b/src/app/components/user-profile/UserHero.tsx
index 0e7fb7485..90faf0545 100644
--- a/src/app/components/user-profile/UserHero.tsx
+++ b/src/app/components/user-profile/UserHero.tsx
@@ -95,8 +95,9 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
type UserHeroNameProps = {
displayName?: string;
userId: string;
+ status?: string;
};
-export function UserHeroName({ displayName, userId }: UserHeroNameProps) {
+export function UserHeroName({ displayName, userId, status }: UserHeroNameProps) {
const username = getMxIdLocalPart(userId);
return (
@@ -115,6 +116,13 @@ export function UserHeroName({ displayName, userId }: UserHeroNameProps) {
@{username}
+ {status && (
+
+
+ {status}
+
+
+ )}
);
}
diff --git a/src/app/components/user-profile/UserRoomProfile.tsx b/src/app/components/user-profile/UserRoomProfile.tsx
index 361d0f978..2157e456e 100644
--- a/src/app/components/user-profile/UserRoomProfile.tsx
+++ b/src/app/components/user-profile/UserRoomProfile.tsx
@@ -237,7 +237,7 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
-
+
{showEncryption && }
{userId !== myUserId && (
diff --git a/src/app/features/room/MembersDrawer.tsx b/src/app/features/room/MembersDrawer.tsx
index 29ae8f30e..0f7eb70f2 100644
--- a/src/app/features/room/MembersDrawer.tsx
+++ b/src/app/features/room/MembersDrawer.tsx
@@ -167,10 +167,15 @@ function MemberItem({
>
}
>
-
+
{name}
+ {presence?.status && (
+
+ {presence.status}
+
+ )}
);
diff --git a/src/app/features/settings/account/Profile.tsx b/src/app/features/settings/account/Profile.tsx
index 9b9ca3da5..fcd7d846b 100644
--- a/src/app/features/settings/account/Profile.tsx
+++ b/src/app/features/settings/account/Profile.tsx
@@ -1,9 +1,11 @@
import React, {
ChangeEventHandler,
FormEventHandler,
+ Suspense,
useCallback,
useEffect,
useMemo,
+ useRef,
useState,
} from 'react';
import {
@@ -23,6 +25,8 @@ import {
Header,
config,
Spinner,
+ PopOut,
+ RectCords,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { SequenceCard } from '../../../components/sequence-card';
@@ -43,6 +47,11 @@ import { ModalWide } from '../../../styles/Modal.css';
import { createUploadAtom, UploadSuccess } from '../../../state/upload';
import { CompactUploadCardRenderer } from '../../../components/upload-card';
import { useCapabilities } from '../../../hooks/useCapabilities';
+import { useUserPresence } from '../../../hooks/useUserPresence';
+
+const EmojiBoard = React.lazy(() =>
+ import('../../../components/emoji-board').then((m) => ({ default: m.EmojiBoard })),
+);
type ProfileProps = {
profile: UserProfile;
@@ -310,6 +319,166 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) {
);
}
+function ProfileStatus() {
+ const mx = useMatrixClient();
+ const userId = mx.getUserId()!;
+ const presence = useUserPresence(userId);
+
+ const [statusMsg, setStatusMsg] = useState(presence?.status ?? '');
+ const [emojiAnchor, setEmojiAnchor] = useState();
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ setStatusMsg(presence?.status ?? '');
+ }, [presence?.status]);
+
+ const [saveState, saveStatus] = useAsyncCallback(
+ useCallback(
+ (msg: string) =>
+ mx.setPresence({
+ presence: 'online',
+ status_msg: msg,
+ }),
+ [mx],
+ ),
+ );
+ const saving = saveState.status === AsyncStatus.Loading;
+
+ const handleEmojiSelect = useCallback(
+ (unicode: string) => {
+ const input = inputRef.current;
+ if (input) {
+ const start = input.selectionStart ?? statusMsg.length;
+ const end = input.selectionEnd ?? statusMsg.length;
+ const next = statusMsg.slice(0, start) + unicode + statusMsg.slice(end);
+ setStatusMsg(next);
+ // restore cursor after emoji insertion
+ requestAnimationFrame(() => {
+ input.focus();
+ input.setSelectionRange(start + unicode.length, start + unicode.length);
+ });
+ } else {
+ setStatusMsg((prev) => prev + unicode);
+ }
+ setEmojiAnchor(undefined);
+ },
+ [statusMsg],
+ );
+
+ const handleChange: ChangeEventHandler = (evt) => {
+ setStatusMsg(evt.currentTarget.value);
+ };
+
+ const handleSubmit: FormEventHandler = (evt) => {
+ evt.preventDefault();
+ if (saving) return;
+ saveStatus(statusMsg.trim());
+ };
+
+ const handleClear = () => {
+ setStatusMsg('');
+ mx.setPresence({
+ presence: 'online',
+ status_msg: '',
+ });
+ };
+
+ const hasChanges = statusMsg !== (presence?.status ?? '');
+
+ return (
+
+ Status Message
+
+ }
+ description={
+
+ Shown below your name in member lists. Supports emoji.
+
+ }
+ >
+
+
+
+ }>
+ setEmojiAnchor(undefined)}
+ />
+
+ }
+ >
+ ) =>
+ setEmojiAnchor((prev) =>
+ prev ? undefined : evt.currentTarget.getBoundingClientRect(),
+ )
+ }
+ >
+
+
+
+ }
+ />
+
+
+
+ {(presence?.status || statusMsg) && (
+
+ )}
+
+
+ );
+}
+
export function Profile() {
const mx = useMatrixClient();
const userId = mx.getUserId()!;
@@ -326,6 +495,7 @@ export function Profile() {
>
+
);