All files / src/utils eventCategoryPresentation.ts

91.48% Statements 86/94
30% Branches 3/10
75% Functions 3/4
91.48% Lines 86/94

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 1181x                               1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x   1x 1x 1x 1x 1x 1x   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x   36x 36x 36x               36x 36x 36x 36x 36x 36x   36x 36x   1x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x       36x   36x 36x 36x 36x 36x 36x 36x 36x  
import i18n from '@/i18n';
 
export interface EventCategoryPresentation {
  label: string;
  emoji: string;
  color: string;
  tintColor: string;
  textColor: string;
}
 
interface EventCategoryPresentationConfig {
  emoji: string;
  lightColor: string;
  darkColor: string;
}
 
const CATEGORY_PRESENTATION_BY_NAME: Record<string, EventCategoryPresentationConfig> = {
  sports: { emoji: '🏃', lightColor: '#2563EB', darkColor: '#60A5FA' },
  music: { emoji: '🎵', lightColor: '#DB2777', darkColor: '#F472B6' },
  education: { emoji: '🎓', lightColor: '#7C3AED', darkColor: '#A78BFA' },
  technology: { emoji: '💻', lightColor: '#0891B2', darkColor: '#22D3EE' },
  art: { emoji: '🎨', lightColor: '#EA580C', darkColor: '#FB923C' },
  fooddrink: { emoji: '🍽️', lightColor: '#C2410C', darkColor: '#FDBA74' },
  outdoors: { emoji: '🌲', lightColor: '#059669', darkColor: '#34D399' },
  fitness: { emoji: '💪', lightColor: '#DC2626', darkColor: '#F87171' },
  networking: { emoji: '🤝', lightColor: '#4F46E5', darkColor: '#818CF8' },
  gaming: { emoji: '🎮', lightColor: '#9333EA', darkColor: '#C084FC' },
  charity: { emoji: '💛', lightColor: '#B45309', darkColor: '#FBBF24' },
  photography: { emoji: '📷', lightColor: '#0284C7', darkColor: '#38BDF8' },
  travel: { emoji: '✈️', lightColor: '#0D9488', darkColor: '#2DD4BF' },
  workshops: { emoji: '🛠️', lightColor: '#475569', darkColor: '#CBD5E1' },
  conferences: { emoji: '🎤', lightColor: '#4338CA', darkColor: '#A5B4FC' },
  moviescinema: { emoji: '🎬', lightColor: '#B91C1C', darkColor: '#FCA5A5' },
  theatre: { emoji: '🎭', lightColor: '#C026D3', darkColor: '#E879F9' },
  booksliterature: { emoji: '📚', lightColor: '#D97706', darkColor: '#FCD34D' },
  wellness: { emoji: '🧘', lightColor: '#65A30D', darkColor: '#A3E635' },
  volunteering: { emoji: '🙌', lightColor: '#BE123C', darkColor: '#FB7185' },
  social: { emoji: '🤗', lightColor: '#0D9488', darkColor: '#5EEAD4' },
  culture: { emoji: '🏛️', lightColor: '#7C2D12', darkColor: '#FDBA74' },
  entertainment: { emoji: '🎉', lightColor: '#C026D3', darkColor: '#E879F9' },
};
 
const FALLBACK_CATEGORY_PRESENTATIONS: EventCategoryPresentationConfig[] = [
  { emoji: '📍', lightColor: '#2563EB', darkColor: '#60A5FA' },
  { emoji: '✨', lightColor: '#7C3AED', darkColor: '#A78BFA' },
  { emoji: '📌', lightColor: '#0D9488', darkColor: '#2DD4BF' },
  { emoji: '🎟️', lightColor: '#B45309', darkColor: '#FBBF24' },
];
 
const CATEGORY_TRANSLATION_KEYS_BY_NORMALIZED_NAME: Record<string, string> = {
  sports: 'Sports',
  music: 'Music',
  education: 'Education',
  technology: 'Technology',
  art: 'Art',
  fooddrink: 'Food & Drink',
  outdoors: 'Outdoors',
  fitness: 'Fitness',
  networking: 'Networking',
  gaming: 'Gaming',
  charity: 'Charity',
  photography: 'Photography',
  travel: 'Travel',
  workshops: 'Workshops',
  conferences: 'Conferences',
  moviescinema: 'Movies & Cinema',
  theatre: 'Theatre',
  booksliterature: 'Books & Literature',
  wellness: 'Wellness',
  volunteering: 'Volunteering',
};
 
function normalizeCategoryName(name: string): string {
  return name.toLowerCase().replace(/[^a-z0-9]+/g, '');
}
 
function hashString(value: string): number {
  return value.split('').reduce((hash, char) => {
    return (hash * 31 + char.charCodeAt(0)) >>> 0;
  }, 0);
}
 
function getReadableTextColor(backgroundColor: string): string {
  const normalized = backgroundColor.replace('#', '');
  const red = parseInt(normalized.slice(0, 2), 16);
  const green = parseInt(normalized.slice(2, 4), 16);
  const blue = parseInt(normalized.slice(4, 6), 16);
  const luminance = (0.299 * red + 0.587 * green + 0.114 * blue) / 255;
 
  return luminance > 0.62 ? '#0F172A' : '#FFFFFF';
}
 
export function getEventCategoryPresentation(
  categoryName: string,
  isDark: boolean,
): EventCategoryPresentation {
  const rawLabel = categoryName.trim() || 'Event';
  const normalizedName = normalizeCategoryName(rawLabel);
  const translationLabelKey = CATEGORY_TRANSLATION_KEYS_BY_NORMALIZED_NAME[normalizedName] ?? rawLabel;
  const translationKey = `events.categories.${translationLabelKey}`;
  const translated = i18n.t(translationKey);
  const label = translated === translationKey ? rawLabel : translated;
  const config =
    CATEGORY_PRESENTATION_BY_NAME[normalizedName] ??
    FALLBACK_CATEGORY_PRESENTATIONS[
      hashString(normalizedName) % FALLBACK_CATEGORY_PRESENTATIONS.length
    ];
  const color = isDark ? config.darkColor : config.lightColor;
 
  return {
    label,
    emoji: config.emoji,
    color,
    tintColor: `${color}${isDark ? '30' : '18'}`,
    textColor: getReadableTextColor(color),
  };
}