All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
- Implemented Button component with various props for customization. - Created Modal component with header, content, and footer subcomponents. - Added Spinner component for loading indicators. - Developed Toast component for displaying notifications. - Introduced Tooltip component for contextual hints with keyboard shortcuts. - Added corresponding CSS modules for styling each component. - Updated index file to export new components. - Configured TypeScript settings for the UI package.
125 lines
3.4 KiB
TypeScript
125 lines
3.4 KiB
TypeScript
/**
|
|
* ChannelWelcomeSection — the "start of channel" header rendered above
|
|
* the first message in any channel view. Two variants:
|
|
*
|
|
* - DM (1:1) → centred avatar + display name + "@username" handle +
|
|
* "This is the beginning of your direct message history with Name."
|
|
* - Text channel → "Welcome to #channel-name!" with the Hash icon
|
|
* and a short description.
|
|
*
|
|
* Voice channels never reach the message list so they never need this.
|
|
*/
|
|
import { useMemo } from 'react';
|
|
import { useQuery } from 'convex/react';
|
|
import { Hash } from '@phosphor-icons/react';
|
|
import { Avatar } from '@discord-clone/ui';
|
|
import { api } from '../../../../../convex/_generated/api';
|
|
import { useOnlineUsers } from '../../contexts/PresenceContext';
|
|
import styles from './ChannelWelcomeSection.module.css';
|
|
|
|
interface ChannelWelcomeSectionProps {
|
|
channelId: string;
|
|
channelName?: string;
|
|
channelType?: string;
|
|
}
|
|
|
|
function mapPresence(
|
|
status: string | undefined,
|
|
): 'online' | 'idle' | 'dnd' | 'offline' {
|
|
switch (status) {
|
|
case 'online':
|
|
return 'online';
|
|
case 'idle':
|
|
return 'idle';
|
|
case 'dnd':
|
|
return 'dnd';
|
|
default:
|
|
return 'offline';
|
|
}
|
|
}
|
|
|
|
export function ChannelWelcomeSection({
|
|
channelId,
|
|
channelName,
|
|
channelType,
|
|
}: ChannelWelcomeSectionProps) {
|
|
if (channelType === 'dm') {
|
|
return <DmWelcome channelId={channelId} channelName={channelName} />;
|
|
}
|
|
|
|
const name = channelName || 'channel';
|
|
return (
|
|
<div className={styles.container}>
|
|
<div className={styles.iconWrapper}>
|
|
<Hash size={42} weight="bold" />
|
|
</div>
|
|
<h1 className={styles.title}>Welcome to #{name}!</h1>
|
|
<p className={styles.description}>
|
|
This is the start of the #{name} channel. All messages are
|
|
end-to-end encrypted.
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface DmWelcomeProps {
|
|
channelId: string;
|
|
channelName?: string;
|
|
}
|
|
|
|
function DmWelcome({ channelId, channelName }: DmWelcomeProps) {
|
|
const { resolveStatus } = useOnlineUsers();
|
|
const myUserId =
|
|
typeof localStorage !== 'undefined' ? localStorage.getItem('userId') : null;
|
|
|
|
const dmRows = useQuery(
|
|
api.dms.listDMs,
|
|
myUserId ? { userId: myUserId as any } : 'skip',
|
|
);
|
|
const allUsers = useQuery(api.auth.getPublicKeys) ?? [];
|
|
|
|
const other = useMemo(() => {
|
|
if (!dmRows) return null;
|
|
const row = (dmRows as any[]).find((r) => r.channel_id === channelId);
|
|
if (!row) return null;
|
|
const profile = allUsers.find((u) => u.id === row.other_user_id);
|
|
const username = profile?.username || row.other_username || '';
|
|
return {
|
|
userId: row.other_user_id as string,
|
|
displayName: username || channelName || 'this user',
|
|
username,
|
|
avatarUrl: profile?.avatarUrl ?? row.other_user_avatar_url ?? null,
|
|
status:
|
|
(profile?.status as string | undefined) ||
|
|
(row.other_user_status as string | undefined) ||
|
|
'offline',
|
|
};
|
|
}, [dmRows, allUsers, channelId, channelName]);
|
|
|
|
const presence = mapPresence(
|
|
other ? resolveStatus(other.status, other.userId) : 'offline',
|
|
);
|
|
|
|
const displayName = other?.displayName ?? channelName ?? 'this user';
|
|
|
|
return (
|
|
<div className={styles.dmContainer}>
|
|
<div className={styles.dmAvatar}>
|
|
<Avatar
|
|
src={other?.avatarUrl ?? null}
|
|
fallback={displayName}
|
|
size={80}
|
|
status={presence}
|
|
/>
|
|
</div>
|
|
<div className={styles.dmNameRow}>
|
|
<h1 className={styles.dmName}>{displayName}</h1>
|
|
</div>
|
|
<p className={styles.dmDescription}>
|
|
This is the beginning of your direct message history with{' '}
|
|
<strong>{displayName}</strong>.
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|