import React, { useCallback, useEffect, useState } from 'react'; import { Box, Text, Spinner } from 'folds'; import { Method } from 'matrix-js-sdk'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { SettingTile } from '../../../components/setting-tile'; import { DECORATION_CATEGORIES, DECORATION_CDN, decorationUrl, } from '../../lotus/avatarDecorations'; import { invalidateDecorationCache } from '../../../hooks/useAvatarDecoration'; const PROFILE_FIELD = 'io.lotus.avatar_decoration'; const CELL_SIZE = 72; function DecorationPreviewCell({ slug, name, selected, onSelect, }: { slug: string; name: string; selected: boolean; onSelect: (slug: string) => void; }) { return ( onSelect(slug)} style={{ position: 'relative', width: CELL_SIZE, height: CELL_SIZE, flexShrink: 0, border: `2px solid ${selected ? 'var(--accent-cyan)' : 'transparent'}`, borderRadius: '50%', background: 'var(--bg-surface-variant)', cursor: 'pointer', padding: 0, boxShadow: selected ? '0 0 0 1px var(--accent-cyan)' : 'none', overflow: 'hidden', outline: 'none', }} > ); } export function ProfileDecoration() { const mx = useMatrixClient(); const userId = mx.getUserId()!; const [current, setCurrent] = useState(null); const [selected, setSelected] = useState(null); useEffect(() => { mx.http .authedRequest>( Method.Get, `/profile/${encodeURIComponent(userId)}/${PROFILE_FIELD}`, ) .then((res) => { const val = (res[PROFILE_FIELD] as string | undefined) ?? null; setCurrent(val); setSelected(val); }) .catch(() => { setCurrent(null); setSelected(null); }); }, [mx, userId]); const [saveState, save] = useAsyncCallback( useCallback( async (slug: string | null) => { await mx.http.authedRequest( Method.Put, `/profile/${encodeURIComponent(userId)}/${PROFILE_FIELD}`, undefined, { [PROFILE_FIELD]: slug ?? '' }, ); setCurrent(slug); invalidateDecorationCache(userId); }, [mx, userId], ), ); const saving = saveState.status === AsyncStatus.Loading; const hasChanges = selected !== current; const handleSelect = (slug: string) => { setSelected((prev) => (prev === slug ? null : slug)); }; const handleClear = () => setSelected(null); const handleSave = () => { if (!hasChanges || saving) return; save(selected); }; return ( Avatar Decoration } description={ Shown on your avatar to all Lotus Chat users. } > {/* Current selection preview */} {selected && ( )} {selected ? (DECORATION_CATEGORIES.flatMap((c) => c.decorations).find( (d) => d.slug === selected, )?.name ?? selected) : 'None'} {selected && ( Remove )} {hasChanges && ( {saving && } {saving ? 'Saving…' : 'Save'} )} {saveState.status === AsyncStatus.Error && ( Failed to save. Try again. )} {/* Category grid */} {DECORATION_CATEGORIES.map((category) => ( {category.label} {category.decorations.map((d) => ( ))} ))} ); }