Initial commit: FunConnect project with server, relay, client and admin panel

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-24 20:56:36 +08:00
parent eb6e901440
commit b6891483ae
167 changed files with 16147 additions and 106 deletions

View File

@@ -0,0 +1,110 @@
import { cn, generateAvatarColor, getInitials } from '../lib/utils';
interface AvatarProps {
seed: string;
name: string;
size?: 'sm' | 'md' | 'lg' | 'xl';
className?: string;
showOnline?: boolean;
isOnline?: boolean;
}
export function Avatar({
seed,
name,
size = 'md',
className,
showOnline = false,
isOnline = false,
}: AvatarProps) {
const sizeClasses = {
sm: 'w-6 h-6 text-xs',
md: 'w-8 h-8 text-sm',
lg: 'w-10 h-10 text-base',
xl: 'w-12 h-12 text-lg',
};
const dotSizeClasses = {
sm: 'w-1.5 h-1.5 -bottom-0 -right-0',
md: 'w-2 h-2 -bottom-0.5 -right-0.5',
lg: 'w-2.5 h-2.5 -bottom-0.5 -right-0.5',
xl: 'w-3 h-3 -bottom-1 -right-1',
};
const bgColor = generateAvatarColor(seed);
const initials = getInitials(name);
return (
<div className={cn('relative flex-shrink-0', className)}>
<div
className={cn(
'rounded-full flex items-center justify-center font-semibold text-white',
sizeClasses[size]
)}
style={{ backgroundColor: bgColor }}
>
{initials}
</div>
{showOnline && (
<span
className={cn(
'absolute rounded-full border-2 border-bg-secondary',
dotSizeClasses[size],
isOnline ? 'bg-accent-green shadow-[0_0_6px_#4ade80]' : 'bg-text-muted'
)}
/>
)}
</div>
);
}
interface AvatarGroupProps {
users: Array<{ seed: string; name: string }>;
max?: number;
size?: 'sm' | 'md' | 'lg';
}
export function AvatarGroup({ users, max = 4, size = 'md' }: AvatarGroupProps) {
const displayed = users.slice(0, max);
const remaining = users.length - max;
const overlapClasses = {
sm: '-ml-2',
md: '-ml-3',
lg: '-ml-4',
};
const sizeClasses = {
sm: 'w-6 h-6 text-xs',
md: 'w-8 h-8 text-sm',
lg: 'w-10 h-10 text-base',
};
return (
<div className="flex items-center">
{displayed.map((user, index) => (
<div
key={user.seed}
className={cn(
'rounded-full border-2 border-bg-secondary',
index > 0 && overlapClasses[size]
)}
style={{ zIndex: displayed.length - index }}
>
<Avatar seed={user.seed} name={user.name} size={size} />
</div>
))}
{remaining > 0 && (
<div
className={cn(
'rounded-full bg-bg-tertiary flex items-center justify-center font-medium text-text-secondary border-2 border-bg-secondary',
overlapClasses[size],
sizeClasses[size]
)}
>
+{remaining}
</div>
)}
</div>
);
}