diff --git a/src/app/features/settings/account/ExternalConnections.tsx b/src/app/features/settings/account/ExternalConnections.tsx
new file mode 100644
index 000000000..202b5f5e7
--- /dev/null
+++ b/src/app/features/settings/account/ExternalConnections.tsx
@@ -0,0 +1,42 @@
+import { SequenceCard } from '$components/sequence-card';
+import { useMatrixClient } from '$hooks/useMatrixClient';
+import { UserProfile } from '$hooks/useUserProfile';
+import { profilesCacheAtom } from '$state/userRoomProfile';
+import { Box, Text } from 'folds';
+import { useSetAtom } from 'jotai';
+import { useCallback } from 'react';
+import { SequenceCardStyle } from '../styles.css';
+import { ExternalListenbrainzConnectionEditor } from './ExternalListenbrainzConnection';
+
+type ExternalConnectionsProps = {
+ profile: UserProfile;
+ userId: string;
+};
+export function ExternalConnectionsEditor({ profile, userId }: ExternalConnectionsProps) {
+ const mx = useMatrixClient();
+ const setGlobalProfiles = useSetAtom(profilesCacheAtom);
+
+ const handleSaveField = useCallback(
+ async (key: string, value: any) => {
+ await mx.setExtendedProfileProperty?.(key, value);
+ setGlobalProfiles((prev) => {
+ const newCache = { ...prev };
+ delete newCache[userId];
+ return newCache;
+ });
+ },
+ [mx, userId, setGlobalProfiles]
+ );
+
+ return (
+
+ External Connections
+
+ handleSaveField('fyi.cisnt.external.listenbrainz', con)}
+ />
+
+
+ );
+}
diff --git a/src/app/features/settings/account/ExternalListenbrainzConnection.tsx b/src/app/features/settings/account/ExternalListenbrainzConnection.tsx
new file mode 100644
index 000000000..9f74d9b1a
--- /dev/null
+++ b/src/app/features/settings/account/ExternalListenbrainzConnection.tsx
@@ -0,0 +1,49 @@
+import { SettingTile } from '$components/setting-tile';
+import { ExternalListenbrainzConnection } from '$hooks/useUserProfile';
+import { Input } from 'folds';
+import { useState, useEffect, ChangeEvent } from 'react';
+
+export type ExternalListenbrainzConnectionProps = {
+ current?: ExternalListenbrainzConnection;
+ onSave: (p: ExternalListenbrainzConnection) => void;
+};
+
+export function ExternalListenbrainzConnectionEditor({
+ current,
+ onSave,
+}: ExternalListenbrainzConnectionProps) {
+ const initialString = current?.username ? `@${current.username}` : '';
+ const [val, setVal] = useState(initialString);
+
+ useEffect(() => setVal(initialString), [initialString]);
+
+ const handleSave = () => {
+ if (val === initialString) return;
+ onSave({ username: val.replace('@', ''), v: 1 });
+ };
+
+ const handleChange = (e: ChangeEvent) => {
+ setVal(e.currentTarget.value);
+ };
+
+ return (
+ e.key === 'Enter' && handleSave()}
+ style={{ width: '232px' }}
+ />
+ }
+ />
+ );
+}
diff --git a/src/app/features/settings/account/Profile.tsx b/src/app/features/settings/account/Profile.tsx
index 85bf0bc37..6127ca930 100644
--- a/src/app/features/settings/account/Profile.tsx
+++ b/src/app/features/settings/account/Profile.tsx
@@ -54,6 +54,7 @@ import { PronounEditor } from './PronounEditor';
import { BioEditor } from './BioEditor';
import { NameColorEditor } from './NameColorEditor';
import { StatusEditor } from './StatusEditor';
+import { ExternalConnectionsEditor } from './ExternalConnections';
type PronounSet = {
summary: string;
@@ -735,6 +736,7 @@ export function Profile() {
+
);
}
diff --git a/src/app/hooks/useUserProfile.ts b/src/app/hooks/useUserProfile.ts
index e5f1a013a..7b684d7ba 100644
--- a/src/app/hooks/useUserProfile.ts
+++ b/src/app/hooks/useUserProfile.ts
@@ -11,6 +11,11 @@ import { useMatrixClient } from './useMatrixClient';
const inFlightProfiles = new Map>();
+export type ExternalListenbrainzConnection = {
+ username?: string;
+ v?: number;
+};
+
export type UserProfile = {
avatarUrl?: string;
displayName?: string;
@@ -22,6 +27,7 @@ export type UserProfile = {
nameColor?: string;
isCat?: boolean;
hasCats?: boolean;
+ listenBrainzAccount?: ExternalListenbrainzConnection;
extended?: Record;
_fetched?: boolean;
};
@@ -40,6 +46,8 @@ const normalizeInfo = (info: any): UserProfile => {
'moe.sable.app.name_color',
'kitty.meow.has_cats',
'kitty.meow.is_cat',
+ // listenbrainz linking
+ 'fyi.cisnt.external.listenbrainz',
];
const extended: Record = {};
@@ -60,6 +68,7 @@ const normalizeInfo = (info: any): UserProfile => {
nameColor: info['moe.sable.app.name_color'],
isCat: info['kitty.meow.is_cat'] === true,
hasCats: info['kitty.meow.has_cats'] === true,
+ listenBrainzAccount: info['fyi.cisnt.external.listenbrainz'],
extended,
_fetched: true,
};