Update guest v2 branding and theming
This commit is contained in:
167
resources/js/guest-v2/components/EventLogo.tsx
Normal file
167
resources/js/guest-v2/components/EventLogo.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user