feat: Aether Drift UI polish — popout redesign, sidebar cleanup, status dot fix
- Redesign UserProfilePopout with glass material, viewport clamping, flow-based avatar layout, and subtle send-message button - Server sidebar: downsize icons to 40px (matching prototype), remove nonfunctional Explore button, add separator before action buttons, replace Tooltip with native title, remove hover boxShadow artifacts - Avatar status dot: fixed 12px size with semi-transparent border for universal cutout effect matching the design prototype - MemberListToggleButton: distinct three-person icon (was duplicate of Add Friends) - FriendsPage: fix Add Friend button contrast (dark text on mint background) - ChannelSidebar: rename Nitro/Shop to Coming Soon placeholders
This commit is contained in:
@@ -197,7 +197,7 @@ export function FriendsPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('add')}
|
onClick={() => setActiveTab('add')}
|
||||||
className={`px-2 py-0.5 rounded text-[14px] font-medium transition-all ${
|
className={`px-2 py-0.5 rounded text-[14px] font-medium transition-all ${
|
||||||
activeTab === 'add' ? 'text-status-online bg-transparent' : 'bg-status-online text-white hover:bg-status-online/90'
|
activeTab === 'add' ? 'text-status-online bg-transparent' : 'bg-status-online text-[#13131a] hover:bg-status-online/90'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Add Friend
|
Add Friend
|
||||||
|
|||||||
@@ -142,25 +142,22 @@ export function ChannelSidebar() {
|
|||||||
<span className="font-medium text-[16px]">Friends</span>
|
<span className="font-medium text-[16px]">Friends</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Nitro */}
|
{/* Placeholder nav items */}
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary"
|
className="flex items-center gap-3 px-2 h-[42px] rounded-[4px] mb-[2px] text-txt-tertiary cursor-default opacity-50"
|
||||||
>
|
>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0 opacity-70 group-hover:opacity-100">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0">
|
||||||
<path d="M2.98966977,9.35789159 C2.98966977,9.77582472 2.63542482,10.1300697 2.21749169,10.1300697 L2.21749169,10.1300697 C1.79955856,10.1300697 1.44531361,9.77582472 1.44531361,9.35789159 L1.44531361,9.35789159 C1.44531361,8.93995846 1.79955856,8.58571351 2.21749169,8.58571351 L2.21749169,8.58571351 C2.63542482,8.58571351 2.98966977,8.93995846 2.98966977,9.35789159 Z" transform="translate(12, 12) scale(1.2) translate(-12, -12)" />
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
|
||||||
<path d="M21.7,9.358 L12.957,2.46 C12.396,2.014 11.604,2.014 11.043,2.46 L2.3,9.358 C2.113,9.507 2,9.734 2,9.975 L2,19.5 C2,20.881 3.119,22 4.5,22 L19.5,22 C20.881,22 22,20.881 22,19.5 L22,9.975 C22,9.734 21.887,9.507 21.7,9.358 Z M12,17.5 C10.619,17.5 9.5,16.381 9.5,15 C9.5,13.619 10.619,12.5 12,12.5 C13.381,12.5 14.5,13.619 14.5,15 C14.5,16.381 13.381,17.5 12,17.5 Z" />
|
|
||||||
</svg>
|
</svg>
|
||||||
<span className="font-medium text-[16px]">Nitro</span>
|
<span className="font-medium text-[16px]">Coming Soon</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Shop */}
|
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary"
|
className="flex items-center gap-3 px-2 h-[42px] rounded-[4px] mb-[2px] text-txt-tertiary cursor-default opacity-50"
|
||||||
>
|
>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0 opacity-70 group-hover:opacity-100">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0">
|
||||||
<path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h17A1.5 1.5 0 0 1 22 5.5V7H2V5.5ZM2 9v9.5A1.5 1.5 0 0 0 3.5 20h17a1.5 1.5 0 0 0 1.5-1.5V9H2Zm9.5 3a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5Zm-4-1h2a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1Z" />
|
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="font-medium text-[16px]">Shop</span>
|
<span className="font-medium text-[16px]">Coming Soon</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-[18px] px-2 mb-1 flex items-center justify-between group">
|
<div className="mt-[18px] px-2 mb-1 flex items-center justify-between group">
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function MemberListToggleButton() {
|
|||||||
title="Toggle Member List"
|
title="Toggle Member List"
|
||||||
>
|
>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<path d="M14 8.00598C14 10.211 12.206 12.006 10 12.006C7.795 12.006 6 10.211 6 8.00598C6 5.80098 7.794 4.00598 10 4.00598C12.206 4.00598 14 5.80098 14 8.00598ZM2 19.006C2 15.473 5.29 13.006 10 13.006C14.711 13.006 18 15.473 18 19.006V20.006H2V19.006ZM20 20.006H22V19.006C22 16.451 20.178 14.471 17.532 13.471C19.461 14.601 20 16.561 20 19.006V20.006Z" />
|
<path d="M12 12.75c1.63 0 3.07.39 4.24.9 1.08.48 1.76 1.56 1.76 2.73V18H6v-1.61c0-1.18.68-2.26 1.76-2.73 1.17-.52 2.61-.91 4.24-.91zM4 13c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm1.13 1.1c-.37-.06-.74-.1-1.13-.1-.99 0-1.93.21-2.78.58A2.01 2.01 0 0 0 0 16.43V18h4.5v-1.61c0-.83.23-1.61.63-2.29zM20 13c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm4 3.43c0-.81-.48-1.53-1.22-1.85A6.95 6.95 0 0 0 20 14c-.39 0-.76.04-1.13.1.4.68.63 1.46.63 2.29V18H24v-1.57zM12 6c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { useServerStore } from '../../stores/serverStore';
|
import { useServerStore } from '../../stores/serverStore';
|
||||||
import { useChatStore } from '../../stores/chatStore';
|
import { useChatStore } from '../../stores/chatStore';
|
||||||
import { useUIStore } from '../../stores/uiStore';
|
import { useUIStore } from '../../stores/uiStore';
|
||||||
import { Tooltip } from '../ui/Tooltip';
|
|
||||||
import { getServerGradient, HOME_GRADIENT } from '../../utils/gradients';
|
import { getServerGradient, HOME_GRADIENT } from '../../utils/gradients';
|
||||||
|
|
||||||
interface SidebarItemProps {
|
interface SidebarItemProps {
|
||||||
@@ -13,7 +13,7 @@ interface SidebarItemProps {
|
|||||||
active: boolean;
|
active: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
type?: 'server' | 'dm' | 'action';
|
type?: 'server' | 'dm' | 'action';
|
||||||
actionType?: 'add' | 'join' | 'explore';
|
actionType?: 'add' | 'join';
|
||||||
hasUnread?: boolean;
|
hasUnread?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,94 +22,86 @@ function SidebarItem({ id, name, icon, active, onClick, type = 'server', actionT
|
|||||||
const firstLetter = name.charAt(0).toUpperCase();
|
const firstLetter = name.charAt(0).toUpperCase();
|
||||||
|
|
||||||
const getPillHeight = () => {
|
const getPillHeight = () => {
|
||||||
if (active) return 'h-10';
|
if (active) return 'h-8';
|
||||||
if (isHovered) return 'h-5';
|
if (isHovered) return 'h-4';
|
||||||
if (hasUnread && !active) return 'h-2';
|
if (hasUnread && !active) return 'h-2';
|
||||||
return 'h-2 scale-0';
|
return 'h-2 scale-0';
|
||||||
};
|
};
|
||||||
|
|
||||||
const backgroundStyle = useMemo((): React.CSSProperties | undefined => {
|
const backgroundStyle = useMemo((): React.CSSProperties | undefined => {
|
||||||
if (type === 'action') return undefined;
|
if (type === 'action') {
|
||||||
|
return {
|
||||||
|
background: isHovered ? 'rgba(134, 239, 172, 0.12)' : 'rgba(255, 255, 255, 0.04)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'dm') {
|
if (type === 'dm') {
|
||||||
return {
|
return { background: HOME_GRADIENT.gradient };
|
||||||
background: HOME_GRADIENT.gradient,
|
|
||||||
...(isHovered ? { boxShadow: `0 0 12px ${HOME_GRADIENT.glow}40` } : {}),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server type — if it has a custom icon image, no gradient needed
|
// Server type — if it has a custom icon image, no gradient needed
|
||||||
if (icon) return undefined;
|
if (icon) return undefined;
|
||||||
|
|
||||||
const serverGrad = getServerGradient(id, name);
|
const serverGrad = getServerGradient(id, name);
|
||||||
return {
|
return { background: serverGrad.gradient };
|
||||||
background: serverGrad.gradient,
|
}, [type, id, name, icon, isHovered]);
|
||||||
...(isHovered ? { boxShadow: `0 0 12px ${serverGrad.glow}40` } : {}),
|
|
||||||
};
|
|
||||||
}, [type, id, name, icon, active, isHovered]);
|
|
||||||
|
|
||||||
const getButtonClasses = () => {
|
const getButtonClasses = () => {
|
||||||
const base = 'w-12 h-12 flex items-center justify-center transition-all duration-200 overflow-hidden relative group';
|
const base = 'w-10 h-10 flex items-center justify-center duration-200 overflow-hidden [transition:border-radius_0.2s,background_0.2s,color_0.2s]';
|
||||||
|
|
||||||
if (type === 'dm') {
|
if (type === 'dm') {
|
||||||
return `${base} text-white ${active ? 'rounded-[16px]' : 'rounded-[24px] hover:rounded-[16px]'}`;
|
return `${base} text-white ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'action') {
|
if (type === 'action') {
|
||||||
return `${base} bg-surface-chat rounded-[24px] hover:rounded-[16px] text-status-online hover:bg-status-online hover:text-white`;
|
return `${base} rounded-[20px] hover:rounded-[13px] text-accent-mint`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (icon) {
|
if (icon) {
|
||||||
return `${base} ${active ? 'rounded-[16px]' : 'rounded-[24px] hover:rounded-[16px]'}`;
|
return `${base} ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${base} text-white ${active ? 'rounded-[16px]' : 'rounded-[24px] hover:rounded-[16px]'}`;
|
return `${base} text-white ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative flex items-center mb-2 w-full justify-center"
|
className="relative flex items-center mb-1.5 w-full justify-center"
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
>
|
>
|
||||||
{/* Pill Indicator */}
|
{/* Pill Indicator */}
|
||||||
{(type === 'server' || type === 'dm') && (
|
{(type === 'server' || type === 'dm') && (
|
||||||
<div className="absolute -left-0 w-2 h-12 flex items-center">
|
<div className="absolute -left-0 w-2 h-10 flex items-center">
|
||||||
<div
|
<div
|
||||||
className={`bg-white rounded-r-full transition-all duration-200 origin-left ${getPillHeight()} w-1`}
|
className={`bg-white rounded-r-full transition-all duration-200 origin-left ${getPillHeight()} w-1`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tooltip content={name} position="right">
|
<button onClick={onClick} className={getButtonClasses()} style={backgroundStyle} title={name}>
|
||||||
<button onClick={onClick} className={getButtonClasses()} style={backgroundStyle}>
|
{type === 'dm' ? (
|
||||||
{type === 'dm' ? (
|
<span className="text-[17px] font-bold">B</span>
|
||||||
<span className="text-[20px] font-bold">B</span>
|
) : type === 'action' ? (
|
||||||
) : type === 'action' ? (
|
actionType === 'add' ? (
|
||||||
actionType === 'add' ? (
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z" />
|
||||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z" />
|
</svg>
|
||||||
</svg>
|
|
||||||
) : actionType === 'explore' ? (
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<path d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zm-3.146-5.351l2.78-1.042 1.042-2.78-2.78 1.042-1.042 2.78zM14.5 7.5l-2.5 5-5 2.5 2.5-5 5-2.5z" />
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<path d="M12 2L4.5 20.29l.71.71L12 18l6.79 3 .71-.71z" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
) : icon ? (
|
|
||||||
<img
|
|
||||||
src={icon.startsWith('http') ? icon : `/api/uploads/${icon}`}
|
|
||||||
alt={name}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<span className="text-[16px] font-medium">{firstLetter}</span>
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||||
)}
|
<path d="M12 2L4.5 20.29l.71.71L12 18l6.79 3 .71-.71z" />
|
||||||
</button>
|
</svg>
|
||||||
</Tooltip>
|
)
|
||||||
|
) : icon ? (
|
||||||
|
<img
|
||||||
|
src={icon.startsWith('http') ? icon : `/api/uploads/${icon}`}
|
||||||
|
alt={name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span className="text-[15px] font-medium">{firstLetter}</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -167,7 +159,7 @@ export function ServerSidebar() {
|
|||||||
hasUnread={hasDmUnread}
|
hasUnread={hasDmUnread}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="w-8 h-[2px] bg-interactive-muted rounded-full mb-2" />
|
<div className="w-8 h-[2px] bg-interactive-muted rounded-full mb-1.5" />
|
||||||
|
|
||||||
{servers.map((server) => (
|
{servers.map((server) => (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
@@ -181,6 +173,8 @@ export function ServerSidebar() {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
<div className="w-8 h-[2px] bg-interactive-muted rounded-full mb-1.5" />
|
||||||
|
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="add-server"
|
id="add-server"
|
||||||
name="Add a Server"
|
name="Add a Server"
|
||||||
@@ -199,14 +193,6 @@ export function ServerSidebar() {
|
|||||||
actionType="join"
|
actionType="join"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SidebarItem
|
|
||||||
id="explore"
|
|
||||||
name="Explore Discoverable Servers"
|
|
||||||
active={false}
|
|
||||||
onClick={() => {}}
|
|
||||||
type="action"
|
|
||||||
actionType="explore"
|
|
||||||
/>
|
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,12 +69,13 @@ export function Avatar({ src, name, size = 40, status, className = '', onClick,
|
|||||||
</div>
|
</div>
|
||||||
{status && (
|
{status && (
|
||||||
<div
|
<div
|
||||||
className={`absolute -bottom-0.5 -right-0.5 rounded-full border-[3px] border-surface-channel ${statusColors[status] ?? 'bg-status-offline'}`}
|
className={`absolute rounded-full ${statusColors[status] ?? 'bg-status-offline'}`}
|
||||||
style={{
|
style={{
|
||||||
width: size * 0.35,
|
width: size <= 24 ? 10 : 12,
|
||||||
height: size * 0.35,
|
height: size <= 24 ? 10 : 12,
|
||||||
minWidth: 12,
|
bottom: -2,
|
||||||
minHeight: 12,
|
right: -2,
|
||||||
|
border: '3px solid rgba(22, 22, 40, 0.72)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
|
|||||||
const addDmChannel = useServerStore((s) => s.addDmChannel);
|
const addDmChannel = useServerStore((s) => s.addDmChannel);
|
||||||
const displayName = user.displayName ?? user.username;
|
const displayName = user.displayName ?? user.username;
|
||||||
|
|
||||||
|
const top = position
|
||||||
|
? Math.min(Math.max(8, position.top), window.innerHeight - 360)
|
||||||
|
: undefined;
|
||||||
|
const left = position
|
||||||
|
? Math.min(Math.max(8, position.left), window.innerWidth - 316)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const handleSendMessage = async () => {
|
const handleSendMessage = async () => {
|
||||||
try {
|
try {
|
||||||
const channel = await api.dm.create({ userId: user.id });
|
const channel = await api.dm.create({ userId: user.id });
|
||||||
@@ -29,59 +36,77 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
|
|||||||
console.error('Failed to create DM channel:', err);
|
console.error('Failed to create DM channel:', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed z-[200] w-[300px] bg-surface-elevated rounded-[8px] shadow-elevation-high overflow-hidden animate-fade-in select-none"
|
className="fixed z-[200] w-[300px] rounded-[12px] overflow-hidden animate-fade-in select-none border border-white/[0.07]"
|
||||||
style={position ? { top: position.top, left: position.left } : { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }}
|
style={{
|
||||||
|
...(position
|
||||||
|
? { top, left }
|
||||||
|
: { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }),
|
||||||
|
backdropFilter: 'blur(20px) saturate(1.2)',
|
||||||
|
WebkitBackdropFilter: 'blur(20px) saturate(1.2)',
|
||||||
|
backgroundColor: 'rgba(20,20,26,0.85)',
|
||||||
|
boxShadow: '0 8px 32px rgba(0,0,0,0.45), 0 2px 8px rgba(0,0,0,0.25)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{/* Banner */}
|
{/* Banner */}
|
||||||
<div className="h-[60px]" style={{ background: getAvatarGradient(user.id, displayName).gradient }} />
|
<div
|
||||||
|
className="h-[48px] rounded-t-[12px]"
|
||||||
{/* Avatar Container */}
|
style={{
|
||||||
<div className="px-4 pb-4 relative">
|
background: getAvatarGradient(user.id, displayName).gradient,
|
||||||
<div className="absolute -top-8 left-4 rounded-full border-[6px] border-surface-elevated bg-surface-elevated">
|
opacity: 0.6,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
<div className="px-4 pb-4">
|
||||||
|
{/* Avatar — negative margin pulls it into the banner while staying in flow */}
|
||||||
|
<div
|
||||||
|
className="mt-[-28px] mb-3 w-fit rounded-full"
|
||||||
|
style={{ border: '4px solid rgba(20,20,26,0.85)' }}
|
||||||
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={user.avatar}
|
src={user.avatar}
|
||||||
name={displayName}
|
name={displayName}
|
||||||
size={80}
|
size={56}
|
||||||
status={user.status as any}
|
status={user.status as 'online' | 'idle' | 'dnd' | 'offline' | null}
|
||||||
user={user}
|
userId={user.id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Name & info — flows naturally after avatar */}
|
||||||
<div className="mt-12 bg-surface-input rounded-[8px] p-3">
|
<div>
|
||||||
<div className="text-[20px] font-bold text-txt-primary leading-tight mb-1">
|
<div className="text-[16px] font-semibold text-txt-primary leading-tight">
|
||||||
{displayName}
|
{displayName}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[14px] text-txt-secondary font-medium mb-3">
|
<div className="text-[13px] text-txt-tertiary">
|
||||||
@{user.username}
|
@{user.username}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full h-[1px] bg-border-soft mb-3" />
|
|
||||||
|
|
||||||
<div className="mb-3">
|
|
||||||
<div className="text-[12px] font-bold text-txt-primary uppercase mb-1">Backspace Member Since</div>
|
|
||||||
<div className="text-[12px] text-txt-secondary font-medium">
|
|
||||||
{new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{user.customStatus && (
|
{user.customStatus && (
|
||||||
<div className="mb-3">
|
<div className="text-[13px] text-txt-secondary italic mt-1">
|
||||||
<div className="text-[12px] font-bold text-txt-primary uppercase mb-1">Status</div>
|
{user.customStatus}
|
||||||
<div className="text-[14px] text-txt-secondary">{user.customStatus}</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
{/* Divider */}
|
||||||
{/* Footer / Actions */}
|
<div className="border-t border-white/[0.06] my-3" />
|
||||||
<div className="px-4 pb-4">
|
|
||||||
<button
|
{/* Member since */}
|
||||||
|
<div>
|
||||||
|
<span className="text-[11px] uppercase tracking-wide font-semibold text-txt-tertiary">
|
||||||
|
Member Since
|
||||||
|
</span>
|
||||||
|
<span className="text-[12px] text-txt-secondary ml-2">
|
||||||
|
{new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Send Message button */}
|
||||||
|
<button
|
||||||
onClick={handleSendMessage}
|
onClick={handleSendMessage}
|
||||||
className="w-full py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-[14px] font-medium rounded-[4px] transition-colors"
|
className="w-full mt-3 py-2 rounded-lg text-[13px] font-medium text-txt-primary bg-white/[0.06] hover:bg-white/[0.10] border border-white/[0.08] transition-colors"
|
||||||
>
|
>
|
||||||
Send Message
|
Send Message
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user