All files / src/utils eventCategoryPresentation.ts

91.3% Statements 21/23
85.71% Branches 12/14
60% Functions 3/5
91.3% Lines 21/23

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 996x                               6x                                                   6x               51x 51x     51x                   51x 51x 51x 51x 51x   51x     6x       51x   51x 51x 51x 51x   51x       51x   51x                
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' },
];
 
function normalizeCategoryName(name: string): string {
  const normalized = name.toLowerCase().replace(/[^a-z0-9]+/g, '');
  const aliases: Record<string, string> = {
    doga: 'outdoors',
  };
  return aliases[normalized] ?? normalized;
}
 
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';
  // Look up the translated category name; if not in catalog, fall back to the raw label.
  const translationKey = `events.categories.${rawLabel}`;
  const translated = i18n.t(translationKey);
  const label = translated === translationKey ? rawLabel : translated;
  const normalizedName = normalizeCategoryName(rawLabel);
  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),
  };
}