168 lines
4.9 KiB
TypeScript
168 lines
4.9 KiB
TypeScript
import React from 'react';
|
|
import { YStack } from '@tamagui/stacks';
|
|
import { SizableText as Text } from '@tamagui/text';
|
|
import { Camera, Heart, PartyPopper, Users } from 'lucide-react';
|
|
import { DEFAULT_EVENT_BRANDING, useOptionalEventBranding } from '@/guest/context/EventBrandingContext';
|
|
import { getContrastingTextColor } from '@/guest/lib/color';
|
|
import type { EventBranding } from '@/guest/types/event-branding';
|
|
|
|
type LogoSize = 's' | 'm' | 'l';
|
|
|
|
type EventLogoProps = {
|
|
name: string;
|
|
icon?: string | null;
|
|
logo?: EventBranding['logo'] | null;
|
|
size?: LogoSize;
|
|
};
|
|
|
|
const EVENT_ICON_COMPONENTS: Record<string, React.ComponentType<{ size?: number; color?: string }>> = {
|
|
heart: Heart,
|
|
guests: Users,
|
|
party: PartyPopper,
|
|
camera: Camera,
|
|
};
|
|
|
|
const LOGO_SIZE_MAP: Record<LogoSize, { container: number; image: number; emoji: number; icon: number; text: number }> = {
|
|
s: { container: 32, image: 24, emoji: 16, icon: 14, text: 12 },
|
|
m: { container: 40, image: 30, emoji: 18, icon: 18, text: 14 },
|
|
l: { container: 48, image: 38, emoji: 22, icon: 22, text: 16 },
|
|
};
|
|
|
|
function isLikelyEmoji(value: string): boolean {
|
|
if (!value) {
|
|
return false;
|
|
}
|
|
const characters = Array.from(value.trim());
|
|
if (characters.length === 0 || characters.length > 2) {
|
|
return false;
|
|
}
|
|
return characters.some((char) => {
|
|
const codePoint = char.codePointAt(0) ?? 0;
|
|
return codePoint > 0x2600;
|
|
});
|
|
}
|
|
|
|
function getInitials(name: string): string {
|
|
const words = name.split(' ').filter(Boolean);
|
|
if (words.length >= 2) {
|
|
return `${words[0][0]}${words[1][0]}`.toUpperCase();
|
|
}
|
|
return name.substring(0, 2).toUpperCase();
|
|
}
|
|
|
|
export default function EventLogo({ name, icon, logo, size }: EventLogoProps) {
|
|
const brandingContext = useOptionalEventBranding();
|
|
const branding = brandingContext?.branding ?? DEFAULT_EVENT_BRANDING;
|
|
const resolvedLogo = logo ?? branding.logo;
|
|
const logoMode = resolvedLogo?.mode ?? (branding.logoUrl ? 'upload' : 'emoticon');
|
|
const logoValue = resolvedLogo?.value ?? branding.logoUrl ?? null;
|
|
const logoSize = size ?? resolvedLogo?.size ?? 'm';
|
|
const sizes = LOGO_SIZE_MAP[logoSize];
|
|
const accentColor = branding.secondaryColor || DEFAULT_EVENT_BRANDING.secondaryColor;
|
|
const textColor = getContrastingTextColor(accentColor, '#ffffff', '#0f172a');
|
|
const [logoFailed, setLogoFailed] = React.useState(false);
|
|
|
|
React.useEffect(() => {
|
|
setLogoFailed(false);
|
|
}, [logoValue]);
|
|
|
|
if (logoMode === 'upload' && logoValue && !logoFailed) {
|
|
return (
|
|
<YStack
|
|
width={sizes.container}
|
|
height={sizes.container}
|
|
borderRadius={sizes.container}
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
backgroundColor="#ffffff"
|
|
borderWidth={1}
|
|
borderColor="rgba(15, 23, 42, 0.08)"
|
|
overflow="hidden"
|
|
>
|
|
<img
|
|
src={logoValue}
|
|
alt={name}
|
|
style={{
|
|
width: sizes.image,
|
|
height: sizes.image,
|
|
borderRadius: sizes.image,
|
|
objectFit: 'cover',
|
|
}}
|
|
onError={() => setLogoFailed(true)}
|
|
/>
|
|
</YStack>
|
|
);
|
|
}
|
|
|
|
if (logoMode === 'emoticon' && logoValue && isLikelyEmoji(logoValue)) {
|
|
return (
|
|
<YStack
|
|
width={sizes.container}
|
|
height={sizes.container}
|
|
borderRadius={sizes.container}
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
backgroundColor={accentColor}
|
|
>
|
|
<Text fontSize={sizes.emoji} color={textColor}>
|
|
{logoValue}
|
|
</Text>
|
|
</YStack>
|
|
);
|
|
}
|
|
|
|
if (typeof icon === 'string') {
|
|
const trimmed = icon.trim();
|
|
if (trimmed) {
|
|
const normalized = trimmed.toLowerCase();
|
|
const IconComponent = EVENT_ICON_COMPONENTS[normalized];
|
|
if (IconComponent) {
|
|
return (
|
|
<YStack
|
|
width={sizes.container}
|
|
height={sizes.container}
|
|
borderRadius={sizes.container}
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
backgroundColor={accentColor}
|
|
>
|
|
<IconComponent size={sizes.icon} color={textColor} />
|
|
</YStack>
|
|
);
|
|
}
|
|
|
|
if (isLikelyEmoji(trimmed)) {
|
|
return (
|
|
<YStack
|
|
width={sizes.container}
|
|
height={sizes.container}
|
|
borderRadius={sizes.container}
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
backgroundColor={accentColor}
|
|
>
|
|
<Text fontSize={sizes.emoji} color={textColor}>
|
|
{trimmed}
|
|
</Text>
|
|
</YStack>
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<YStack
|
|
width={sizes.container}
|
|
height={sizes.container}
|
|
borderRadius={sizes.container}
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
backgroundColor={accentColor}
|
|
>
|
|
<Text fontSize={sizes.text} color={textColor} fontWeight="$7">
|
|
{getInitials(name)}
|
|
</Text>
|
|
</YStack>
|
|
);
|
|
}
|