feat: Enhance user color and banner management
- Updated `useUserColor` hook to support fetching and embedding user color from avatar metadata with authentication. - Introduced `useUserBanner` hook for managing user banners stored in avatar metadata, including fetching and embedding functionality. - Added `fetchAndExtractMetadata` utility to retrieve both color and banner from images. - Implemented `CustomStatusDialog` component for users to set and clear custom status messages. - Modified `SettingsTab` to include custom status functionality and improved user experience. - Enhanced image metadata utilities to support banner extraction and embedding in PNG format. - Updated sidebar settings to handle user profile changes more gracefully.
This commit is contained in:
180
src/app/components/custom-status/CustomStatusDialog.tsx
Normal file
180
src/app/components/custom-status/CustomStatusDialog.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import React, { useState, useCallback, ChangeEventHandler } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
config,
|
||||
Dialog,
|
||||
Input,
|
||||
Text,
|
||||
Overlay,
|
||||
OverlayBackdrop,
|
||||
OverlayCenter,
|
||||
} from 'folds';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useUserPresence } from '../../hooks/useUserPresence';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
|
||||
/**
|
||||
* Props for the CustomStatusDialog component
|
||||
*/
|
||||
export interface CustomStatusDialogProps {
|
||||
/** Whether the dialog is currently open */
|
||||
open: boolean;
|
||||
/** Callback function invoked when the dialog should close */
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* CustomStatusDialog Component
|
||||
*
|
||||
* Provides a dialog interface for users to set or clear their custom status message.
|
||||
* The status is stored in Matrix presence and will be visible to other users.
|
||||
*
|
||||
* @param props - Component props
|
||||
* @returns The custom status dialog component
|
||||
*/
|
||||
export function CustomStatusDialog({ open, onClose }: CustomStatusDialogProps) {
|
||||
const mx = useMatrixClient();
|
||||
const userId = mx.getUserId();
|
||||
const presence = useUserPresence(userId ?? '');
|
||||
|
||||
const [statusText, setStatusText] = useState(presence?.status || '');
|
||||
|
||||
/**
|
||||
* Handles input changes for the status text field
|
||||
* @param evt - The change event from the input
|
||||
*/
|
||||
const handleChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
|
||||
setStatusText(evt.currentTarget.value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Async callback to save the status to Matrix presence
|
||||
*/
|
||||
const [saveState, saveStatus] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
await mx.setPresence({
|
||||
presence: presence?.presence || 'online',
|
||||
status_msg: statusText,
|
||||
});
|
||||
onClose();
|
||||
}, [mx, presence?.presence, statusText, onClose])
|
||||
);
|
||||
|
||||
/**
|
||||
* Async callback to clear the status from Matrix presence
|
||||
*/
|
||||
const [clearState, clearStatus] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
await mx.setPresence({
|
||||
presence: presence?.presence || 'online',
|
||||
status_msg: '',
|
||||
});
|
||||
setStatusText('');
|
||||
onClose();
|
||||
}, [mx, presence?.presence, onClose])
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles form submission to save the custom status
|
||||
* @param evt - The form submit event
|
||||
*/
|
||||
const handleSubmit = (evt: React.FormEvent) => {
|
||||
evt.preventDefault();
|
||||
saveStatus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the clear status button click
|
||||
*/
|
||||
const handleClear = () => {
|
||||
clearStatus();
|
||||
};
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: onClose,
|
||||
clickOutsideDeactivates: true,
|
||||
}}
|
||||
>
|
||||
<Dialog>
|
||||
<Box
|
||||
as="form"
|
||||
onSubmit={handleSubmit}
|
||||
direction="Column"
|
||||
gap="400"
|
||||
style={{ padding: config.space.S400 }}
|
||||
>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="H4">Set Custom Status</Text>
|
||||
<Text size="T300" priority="400">
|
||||
Your custom status will be visible to other users
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box direction="Column" gap="200">
|
||||
<Input
|
||||
value={statusText}
|
||||
onChange={handleChange}
|
||||
placeholder="What's on your mind?"
|
||||
maxLength={100}
|
||||
autoFocus
|
||||
disabled={saveState.status === AsyncStatus.Loading || clearState.status === AsyncStatus.Loading}
|
||||
/>
|
||||
{statusText.length > 0 && (
|
||||
<Text size="T200" priority="300">
|
||||
{statusText.length}/100 characters
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{(saveState.status === AsyncStatus.Error || clearState.status === AsyncStatus.Error) && (
|
||||
<Text size="T300" priority="400" style={{ color: 'var(--color-critical-main)' }}>
|
||||
Failed to update status. Please try again.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Box direction="Row" gap="200" justifyContent="End">
|
||||
<Button
|
||||
type="button"
|
||||
variant="Secondary"
|
||||
onClick={onClose}
|
||||
disabled={saveState.status === AsyncStatus.Loading || clearState.status === AsyncStatus.Loading}
|
||||
>
|
||||
<Text size="B400">Cancel</Text>
|
||||
</Button>
|
||||
{presence?.status && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="Critical"
|
||||
fill="None"
|
||||
onClick={handleClear}
|
||||
disabled={saveState.status === AsyncStatus.Loading || clearState.status === AsyncStatus.Loading}
|
||||
>
|
||||
<Text size="B400">Clear Status</Text>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="submit"
|
||||
variant="Primary"
|
||||
disabled={saveState.status === AsyncStatus.Loading || clearState.status === AsyncStatus.Loading}
|
||||
>
|
||||
<Text size="B400">
|
||||
{saveState.status === AsyncStatus.Loading ? 'Saving...' : 'Save'}
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Dialog>
|
||||
</FocusTrap>
|
||||
</OverlayCenter>
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
1
src/app/components/custom-status/index.ts
Normal file
1
src/app/components/custom-status/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { CustomStatusDialog, type CustomStatusDialogProps } from './CustomStatusDialog';
|
||||
Reference in New Issue
Block a user