diff --git a/src/app/components/setting-tile/SettingTile.tsx b/src/app/components/setting-tile/SettingTile.tsx
index 2656903a..5c09732d 100644
--- a/src/app/components/setting-tile/SettingTile.tsx
+++ b/src/app/components/setting-tile/SettingTile.tsx
@@ -2,7 +2,7 @@ import React, { ReactNode } from 'react';
import { Box, Text } from 'folds';
type SettingTileProps = {
- title: ReactNode;
+ title?: ReactNode;
description?: ReactNode;
before?: ReactNode;
after?: ReactNode;
@@ -13,7 +13,7 @@ export function SettingTile({ title, description, before, after, children }: Set
{before && {before}}
- {title}
+ {title && {title}}
{description && (
{description}
diff --git a/src/app/features/settings/Account.tsx b/src/app/features/settings/Account.tsx
new file mode 100644
index 00000000..7ab14aa6
--- /dev/null
+++ b/src/app/features/settings/Account.tsx
@@ -0,0 +1,184 @@
+import React, { useCallback, useEffect } from 'react';
+import { Box, Text, IconButton, Icon, Icons, Scroll, Input, Avatar, Button, Chip } from 'folds';
+import { Page, PageContent, PageHeader } from '../../components/page';
+import { SequenceCard } from '../../components/sequence-card';
+import { SequenceCardStyle } from './styles.css';
+import { SettingTile } from '../../components/setting-tile';
+import { useMatrixClient } from '../../hooks/useMatrixClient';
+import { useUserProfile } from '../../hooks/useUserProfile';
+import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
+import { UserAvatar } from '../../components/user-avatar';
+import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
+import { nameInitials } from '../../utils/common';
+import { copyToClipboard } from '../../utils/dom';
+import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
+
+function MatrixId() {
+ const mx = useMatrixClient();
+ const userId = mx.getUserId()!;
+
+ return (
+
+ Matrix ID
+
+ copyToClipboard(userId)}>
+ Copy
+
+ }
+ />
+
+
+ );
+}
+
+function Profile() {
+ const mx = useMatrixClient();
+ const useAuthentication = useMediaAuthentication();
+ const userId = mx.getUserId()!;
+ const profile = useUserProfile(userId);
+
+ const defaultDisplayName = profile.displayName ?? getMxIdLocalPart(userId) ?? userId;
+ const avatarUrl = profile.avatarUrl
+ ? mxcUrlToHttp(mx, profile.avatarUrl, useAuthentication, 96, 96, 'crop') ?? undefined
+ : undefined;
+
+ return (
+
+ Profile
+
+
+ Avatar
+
+ }
+ before={
+
+ {nameInitials(defaultDisplayName)}}
+ />
+
+ }
+ >
+
+
+ {avatarUrl && (
+
+ )}
+
+
+
+ Display Name
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function ContactInformation() {
+ const mx = useMatrixClient();
+ const [threePIdsState, loadThreePIds] = useAsyncCallback(
+ useCallback(() => mx.getThreePids(), [mx])
+ );
+ const threePIds =
+ threePIdsState.status === AsyncStatus.Success ? threePIdsState.data.threepids : undefined;
+
+ const emailIds = threePIds?.filter((id) => id.medium === 'email');
+
+ useEffect(() => {
+ loadThreePIds();
+ }, [loadThreePIds]);
+
+ return (
+
+ Contact Information
+
+
+
+ {emailIds?.map((email) => (
+
+ {email.address}
+
+ ))}
+
+ {/* */}
+
+
+
+ );
+}
+
+type AccountProps = {
+ requestClose: () => void;
+};
+export function Account({ requestClose }: AccountProps) {
+ return (
+
+
+
+
+
+ Account
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/features/settings/General.tsx b/src/app/features/settings/General.tsx
index 4a2840f5..1ad15d32 100644
--- a/src/app/features/settings/General.tsx
+++ b/src/app/features/settings/General.tsx
@@ -1,6 +1,5 @@
import React, {
ChangeEventHandler,
- CSSProperties,
KeyboardEventHandler,
MouseEventHandler,
useState,
@@ -45,10 +44,7 @@ import {
import { stopPropagation } from '../../utils/keyboard';
import { useMessageLayoutItems } from '../../hooks/useMessageLayout';
import { useMessageSpacingItems } from '../../hooks/useMessageSpacing';
-
-const SequenceCardStyle: CSSProperties = {
- padding: config.space.S300,
-};
+import { SequenceCardStyle } from './styles.css';
type ThemeSelectorProps = {
themeNames: Record;
@@ -286,7 +282,7 @@ function PageZoomInput() {
return (
Appearance
-
+
}
-
+
-
+
}
/>
-
+
} />
@@ -347,7 +348,7 @@ function Editor() {
return (
Editor
-
+
}
/>
-
+
}
@@ -521,13 +522,13 @@ function Messages() {
return (
Messages
-
+
} />
-
+
} />
-
+
-
+
-
+
}
/>
-
+
}
/>
-
+
}
/>
-
+
void;
};
export function Settings({ requestClose }: SettingsProps) {
+ const mx = useMatrixClient();
+ const useAuthentication = useMediaAuthentication();
+ const userId = mx.getUserId()!;
+ const profile = useUserProfile(userId);
+ const displayName = profile.displayName ?? getMxIdLocalPart(userId) ?? userId;
+ const avatarUrl = profile.avatarUrl
+ ? mxcUrlToHttp(mx, profile.avatarUrl, useAuthentication, 96, 96, 'crop') ?? undefined
+ : undefined;
+
const screenSize = useScreenSizeContext();
const [activePage, setActivePage] = useState(
screenSize === ScreenSize.Mobile ? undefined : SettingsPages.GeneralPage
@@ -92,8 +108,17 @@ export function Settings({ requestClose }: SettingsProps) {
screenSize === ScreenSize.Mobile && activePage !== undefined ? undefined : (
-
- Settings
+
+
+ {nameInitials(displayName)}}
+ />
+
+
+ Settings
+
{screenSize === ScreenSize.Mobile && (
@@ -133,6 +158,9 @@ export function Settings({ requestClose }: SettingsProps) {
{activePage === SettingsPages.GeneralPage && (
)}
+ {activePage === SettingsPages.AccountPage && (
+
+ )}
);
}
diff --git a/src/app/features/settings/index.ts b/src/app/features/settings/index.ts
index 4fddb27e..90e26973 100644
--- a/src/app/features/settings/index.ts
+++ b/src/app/features/settings/index.ts
@@ -1,2 +1 @@
export * from './Settings';
-export * from './General';
diff --git a/src/app/features/settings/styles.css.ts b/src/app/features/settings/styles.css.ts
new file mode 100644
index 00000000..ce89c16e
--- /dev/null
+++ b/src/app/features/settings/styles.css.ts
@@ -0,0 +1,6 @@
+import { style } from '@vanilla-extract/css';
+import { config } from 'folds';
+
+export const SequenceCardStyle = style({
+ padding: config.space.S300,
+});
diff --git a/src/app/hooks/useUserProfile.ts b/src/app/hooks/useUserProfile.ts
new file mode 100644
index 00000000..c7cb7487
--- /dev/null
+++ b/src/app/hooks/useUserProfile.ts
@@ -0,0 +1,51 @@
+import { useEffect, useState } from 'react';
+import { UserEvent, UserEventHandlerMap } from 'matrix-js-sdk';
+import { useMatrixClient } from './useMatrixClient';
+
+export type UserProfile = {
+ avatarUrl?: string;
+ displayName?: string;
+};
+export const useUserProfile = (userId: string): UserProfile => {
+ const mx = useMatrixClient();
+
+ const [profile, setProfile] = useState(() => {
+ const user = mx.getUser(userId);
+ return {
+ avatarUrl: user?.avatarUrl,
+ displayName: user?.displayName,
+ };
+ });
+
+ useEffect(() => {
+ const user = mx.getUser(userId);
+ const onAvatarChange: UserEventHandlerMap[UserEvent.AvatarUrl] = (event, myUser) => {
+ setProfile((cp) => ({
+ ...cp,
+ avatarUrl: myUser.avatarUrl,
+ }));
+ };
+ const onDisplayNameChange: UserEventHandlerMap[UserEvent.DisplayName] = (event, myUser) => {
+ setProfile((cp) => ({
+ ...cp,
+ displayName: myUser.displayName,
+ }));
+ };
+
+ mx.getProfileInfo(userId).then((info) =>
+ setProfile({
+ avatarUrl: info.avatar_url,
+ displayName: info.displayname,
+ })
+ );
+
+ user?.on(UserEvent.AvatarUrl, onAvatarChange);
+ user?.on(UserEvent.DisplayName, onDisplayNameChange);
+ return () => {
+ user?.removeListener(UserEvent.AvatarUrl, onAvatarChange);
+ user?.removeListener(UserEvent.DisplayName, onDisplayNameChange);
+ };
+ }, [mx, userId]);
+
+ return profile;
+};
diff --git a/src/app/pages/client/sidebar/SettingsTab.tsx b/src/app/pages/client/sidebar/SettingsTab.tsx
index bd00dee6..f19b9221 100644
--- a/src/app/pages/client/sidebar/SettingsTab.tsx
+++ b/src/app/pages/client/sidebar/SettingsTab.tsx
@@ -1,7 +1,6 @@
-import React, { useEffect, useState } from 'react';
+import React, { useState } from 'react';
import { Modal, Overlay, OverlayBackdrop, OverlayCenter, Text } from 'folds';
import FocusTrap from 'focus-trap-react';
-import { UserEvent, UserEventHandlerMap } from 'matrix-js-sdk';
import { SidebarItem, SidebarItemTooltip, SidebarAvatar } from '../../../components/sidebar';
import { UserAvatar } from '../../../components/user-avatar';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
@@ -10,54 +9,7 @@ import { nameInitials } from '../../../utils/common';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { Settings } from '../../../features/settings';
import { stopPropagation } from '../../../utils/keyboard';
-
-type UserProfile = {
- avatarUrl?: string;
- displayName?: string;
-};
-const useUserProfile = (userId: string): UserProfile => {
- const mx = useMatrixClient();
-
- const [profile, setProfile] = useState(() => {
- const user = mx.getUser(userId);
- return {
- avatarUrl: user?.avatarUrl,
- displayName: user?.displayName,
- };
- });
-
- useEffect(() => {
- const user = mx.getUser(userId);
- const onAvatarChange: UserEventHandlerMap[UserEvent.AvatarUrl] = (event, myUser) => {
- setProfile((cp) => ({
- ...cp,
- avatarUrl: myUser.avatarUrl,
- }));
- };
- const onDisplayNameChange: UserEventHandlerMap[UserEvent.DisplayName] = (event, myUser) => {
- setProfile((cp) => ({
- ...cp,
- displayName: myUser.displayName,
- }));
- };
-
- mx.getProfileInfo(userId).then((info) =>
- setProfile({
- avatarUrl: info.avatar_url,
- displayName: info.displayname,
- })
- );
-
- user?.on(UserEvent.AvatarUrl, onAvatarChange);
- user?.on(UserEvent.DisplayName, onDisplayNameChange);
- return () => {
- user?.removeListener(UserEvent.AvatarUrl, onAvatarChange);
- user?.removeListener(UserEvent.DisplayName, onDisplayNameChange);
- };
- }, [mx, userId]);
-
- return profile;
-};
+import { useUserProfile } from '../../../hooks/useUserProfile';
export function SettingsTab() {
const mx = useMatrixClient();