import type { Animator, Comment, CommentLike, Event, EventExternalLink, EventHashtag, EventImage, EventLineup, EventStatsSnapshot, EventTheme, Hashtag, Organizer, OrganizerMember, OrganizerSubscription, Plan, Post, PostLike, PostMedia, SearchHistory, SocialAuthProvider, Theme, Ticket, User, UserAnimatorFollow, UserEventInteraction, UserEventShare, UserOrganizerFollow, UserThemePreference, } from '@/models'; const makeUuid = (seed: string): string => { const base = seed.toLowerCase().replace(/[^a-z0-9]/g, ''); const normalized = base.padEnd(12, 'x').slice(0, 12); const parts = [ normalized.slice(0, 8), normalized.slice(8, 12), '4' + normalized.slice(12, 15).padEnd(3, '0'), '8' + normalized.slice(15, 18).padEnd(3, '0'), normalized.slice(18, 30).padEnd(12, '9'), ]; return `${parts[0]}-${parts[1]}-${parts[2]}-${parts[3]}-${parts[4]}`; }; const baseDate = new Date(); const relativeDate = (options: { days?: number; hours?: number; minutes?: number } = {}): Date => { const date = new Date(baseDate); const { days = 0, hours = 0, minutes = 0 } = options; date.setDate(date.getDate() + days); date.setHours(date.getHours() + hours); date.setMinutes(date.getMinutes() + minutes); return date; }; const toSqlDate = (date: Date): string => date.toISOString().slice(0, 10); const makeEventDates = (daysFromNow: number, hours: number, durationHours: number) => { const start = relativeDate({ days: daysFromNow, hours }); const end = new Date(start.getTime() + durationHours * 60 * 60 * 1000); return { start, end }; }; const sortByDisplayOrder = (items: T[]): T[] => [...items].sort((a, b) => (a.display_order ?? 0) - (b.display_order ?? 0)); const themeIds = { nightlife: makeUuid('theme-nightlife'), techno: makeUuid('theme-techno'), festival: makeUuid('theme-festival'), youth: makeUuid('theme-youth'), concert: makeUuid('theme-concert'), free: makeUuid('theme-free'), withFriends: makeUuid('theme-with-friends'), afterwork: makeUuid('theme-afterwork'), }; const organizerIds = { neon: makeUuid('organizer-neon-session'), vertige: makeUuid('organizer-club-vertige'), nyx: makeUuid('organizer-nyx-vandal'), selene: makeUuid('organizer-selene'), cernier: makeUuid('organizer-cernier'), }; const eventIds = { neon: makeUuid('event-neon-session-12'), vertige: makeUuid('event-club-vertige'), nyx: makeUuid('event-nyx-vandal'), selene: makeUuid('event-selene'), cernier: makeUuid('event-cernier-youth'), }; const userIds = { lucie: makeUuid('user-lucie'), noah: makeUuid('user-noah'), amelia: makeUuid('user-amelia'), sam: makeUuid('user-sam'), }; const animatorIds = { glow: makeUuid('animator-glow-reign'), ocho: makeUuid('animator-ocho-k'), vanta: makeUuid('animator-vanta-echo'), isae: makeUuid('animator-isae-noir'), }; const hashtagIds = { neon: makeUuid('hashtag-neon'), techno: makeUuid('hashtag-techno'), club: makeUuid('hashtag-club'), cernier: makeUuid('hashtag-cernier'), electro: makeUuid('hashtag-electro'), afterwork: makeUuid('hashtag-afterwork'), }; const planIds = { free: makeUuid('plan-free'), pro: makeUuid('plan-pro'), ticketing: makeUuid('plan-ticketing'), }; export const mockPlans: Plan[] = [ { id: planIds.free, name: 'FREE', price_monthly: 0, max_active_events: 3, ticketing_enabled: false, ticketing_commission: null, }, { id: planIds.pro, name: 'PRO', price_monthly: 19, max_active_events: 12, ticketing_enabled: true, ticketing_commission: 12, }, { id: planIds.ticketing, name: 'TICKETING', price_monthly: 49, max_active_events: 30, ticketing_enabled: true, ticketing_commission: 10, }, ]; export const mockThemes: Theme[] = [ { id: themeIds.nightlife, name: 'Soirée', color_hex: '#D21CFF', display_order: 1 }, { id: themeIds.techno, name: 'Techno', color_hex: '#00E5FF', display_order: 2 }, { id: themeIds.festival, name: 'Festival', color_hex: '#91F763', display_order: 3 }, { id: themeIds.youth, name: 'Fête de jeunesse', color_hex: '#FFB703', display_order: 4 }, { id: themeIds.concert, name: 'Concert', color_hex: '#FF6B6B', display_order: 5 }, { id: themeIds.free, name: 'Gratuit', color_hex: '#61F0C1', display_order: 6 }, { id: themeIds.withFriends, name: 'Entre amis', color_hex: '#7A8BFF', display_order: 7 }, { id: themeIds.afterwork, name: 'Afterwork', color_hex: '#F4A261', display_order: 8 }, ]; export const mockHashtags: Hashtag[] = [ { id: hashtagIds.neon, name: '#NeonSession' }, { id: hashtagIds.techno, name: '#Techno' }, { id: hashtagIds.club, name: '#ClubNight' }, { id: hashtagIds.cernier, name: '#Cernier' }, { id: hashtagIds.electro, name: '#Electro' }, { id: hashtagIds.afterwork, name: '#Afterwork' }, ]; export const mockOrganizers: Organizer[] = [ { id: organizerIds.neon, name: 'Neon Session', email: 'hello@neonsession.ch', profile_picture: 'https://images.unsplash.com/photo-1516280440614-37939bbacd81?auto=format&fit=crop&w=500&q=80', cover_picture: 'https://images.unsplash.com/photo-1492684223066-81342ee5ff30?auto=format&fit=crop&w=1200&q=80', city: 'La Chaux-de-Fonds', is_verified: true, verified_at: relativeDate({ days: -14 }), verified_by: null, instagram_handle: '@neonsession', tiktok_handle: '@neonsession', facebook_handle: null, plan_id: planIds.pro, created_at: relativeDate({ days: -120 }), updated_at: relativeDate({ hours: -2 }), }, { id: organizerIds.vertige, name: 'Club Vertige', email: 'club@vertige.local', profile_picture: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=500&q=80', cover_picture: 'https://images.unsplash.com/photo-1501386761578-eac5c94b800a?auto=format&fit=crop&w=1200&q=80', city: 'Neuchâtel', is_verified: true, verified_at: relativeDate({ days: -21 }), verified_by: null, instagram_handle: '@clubvertige', tiktok_handle: null, facebook_handle: '@clubvertige', plan_id: planIds.ticketing, created_at: relativeDate({ days: -180 }), updated_at: relativeDate({ hours: -1 }), }, { id: organizerIds.nyx, name: 'Nyx Vandal', email: 'bookings@nyxvandal.ch', profile_picture: 'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?auto=format&fit=crop&w=500&q=80', cover_picture: 'https://images.unsplash.com/photo-1507878866276-a947ef722fee?auto=format&fit=crop&w=1200&q=80', city: 'Fribourg', is_verified: true, verified_at: relativeDate({ days: -36 }), verified_by: null, instagram_handle: '@nyxvandal', tiktok_handle: '@nyxvandal', facebook_handle: null, plan_id: planIds.pro, created_at: relativeDate({ days: -210 }), updated_at: relativeDate({ days: -1 }), }, { id: organizerIds.selene, name: 'Sélène', email: 'team@selene.events', profile_picture: 'https://images.unsplash.com/photo-1521119989659-a83eee488004?auto=format&fit=crop&w=500&q=80', cover_picture: 'https://images.unsplash.com/photo-1525201548942-d8732f6617a0?auto=format&fit=crop&w=1200&q=80', city: 'Lausanne', is_verified: true, verified_at: relativeDate({ days: -12 }), verified_by: null, instagram_handle: '@selene', tiktok_handle: null, facebook_handle: '@selene.events', plan_id: planIds.pro, created_at: relativeDate({ days: -160 }), updated_at: relativeDate({ hours: -6 }), }, { id: organizerIds.cernier, name: 'Fête de la Jeunesse de Cernier', email: 'contact@cernierjeunesse.ch', profile_picture: 'https://images.unsplash.com/photo-1529156069898-49953e39b3ac?auto=format&fit=crop&w=500&q=80', cover_picture: 'https://images.unsplash.com/photo-1470229722913-7c0e2dbbafd3?auto=format&fit=crop&w=1200&q=80', city: 'Cernier', is_verified: true, verified_at: relativeDate({ days: -9 }), verified_by: null, instagram_handle: '@cernierjeunesse', tiktok_handle: '@cernierjeunesse', facebook_handle: '@cernierjeunesse', plan_id: planIds.free, created_at: relativeDate({ days: -90 }), updated_at: relativeDate({ hours: -4 }), }, ]; export const mockUsers: User[] = [ { id: userIds.lucie, first_name: 'Lucie', last_name: 'Morel', email: 'lucie@bahla.app', password_hash: 'hashed-1', profile_picture: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=500&q=80', address: 'Rue de la Gare 7', city: 'Neuchâtel', latitude: 46.9911, longitude: 6.9319, default_radius_km: 25, language: 'fr', notifications_enabled: true, calendar_sync_enabled: true, created_at: relativeDate({ days: -360 }), updated_at: relativeDate({ hours: -3 }), }, { id: userIds.noah, first_name: 'Noah', last_name: 'Martin', email: 'noah@bahla.app', password_hash: 'hashed-2', profile_picture: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=500&q=80', address: 'Rue des Moulins 15', city: 'La Chaux-de-Fonds', latitude: 47.0998, longitude: 6.8233, default_radius_km: 30, language: 'fr', notifications_enabled: true, calendar_sync_enabled: false, created_at: relativeDate({ days: -430 }), updated_at: relativeDate({ hours: -9 }), }, { id: userIds.amelia, first_name: 'Amélia', last_name: 'Dubois', email: 'amelia@bahla.app', password_hash: 'hashed-3', profile_picture: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=500&q=80', address: 'Avenue du Midi 11', city: 'Cernier', latitude: 47.0921, longitude: 6.9311, default_radius_km: 15, language: 'fr', notifications_enabled: true, calendar_sync_enabled: true, created_at: relativeDate({ days: -520 }), updated_at: relativeDate({ hours: -2 }), }, { id: userIds.sam, first_name: 'Sam', last_name: 'Renaud', email: 'sam@bahla.app', password_hash: 'hashed-4', profile_picture: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?auto=format&fit=crop&w=500&q=80', address: 'Place du Marché 4', city: 'Lausanne', latitude: 46.5197, longitude: 6.6323, default_radius_km: 40, language: 'fr', notifications_enabled: false, calendar_sync_enabled: false, created_at: relativeDate({ days: -300 }), updated_at: relativeDate({ days: -2 }), }, ]; export const mockAnimators: Animator[] = [ { id: animatorIds.glow, name: 'Glow Reign', profile_picture: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=500&q=80', genre: 'Techno / House', city: 'Neuchâtel', bio: 'Set long, percussion brute, ambiance club.', created_at: relativeDate({ days: -210 }), updated_at: relativeDate({ days: -7 }), }, { id: animatorIds.ocho, name: 'Ocho K', profile_picture: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=500&q=80', genre: 'Electro', city: 'Fribourg', bio: 'Mélange de synthés, groove et tension.', created_at: relativeDate({ days: -180 }), updated_at: relativeDate({ days: -2 }), }, { id: animatorIds.vanta, name: 'Vanta Echo', profile_picture: 'https://images.unsplash.com/photo-1521119989659-a83eee488004?auto=format&fit=crop&w=500&q=80', genre: 'Synthwave', city: 'Lausanne', bio: 'Ambiance nocturne et lignes de basse incisives.', created_at: relativeDate({ days: -260 }), updated_at: relativeDate({ days: -9 }), }, { id: animatorIds.isae, name: 'Isae Noir', profile_picture: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=500&q=80', genre: 'Live set', city: 'Cernier', bio: 'Live set improvisé avec voix et machine.', created_at: relativeDate({ days: -320 }), updated_at: relativeDate({ days: -4 }), }, ]; const neonEventDates = makeEventDates(0, 19, 4); const vertigeEventDates = makeEventDates(1, 22, 5); const nyxEventDates = makeEventDates(2, 23, 4); const seleneEventDates = makeEventDates(3, 20, 3); const cernierEventDates = makeEventDates(7, 18, 6); export const mockEvents: Event[] = [ { id: eventIds.neon, organizer_id: organizerIds.neon, name: 'Néon Session #12', description: 'Une session nocturne de techno minimale et d’underground, avec des sets longs et une lumière à la limite du visible.', start_date: neonEventDates.start, end_date: neonEventDates.end, doors_open_time: relativeDate({ hours: 2 }), min_age: 18, address: 'Rue des Étoiles 14', city: 'La Chaux-de-Fonds', latitude: 47.1012, longitude: 6.8251, price_presale: 18, price_door: 24, is_free: false, presale_total: 220, presale_sold: 177, ticketing_enabled: true, ticketing_commission: 10, status: 'PUBLISHED', featured_image_id: makeUuid('event-neon-image'), published_at: relativeDate({ days: -3 }), created_at: relativeDate({ days: -45 }), updated_at: relativeDate({ hours: -1 }), }, { id: eventIds.vertige, organizer_id: organizerIds.vertige, name: 'Club Vertige', description: 'Lieu de tension et de basses profondes, Club Vertige revient pour une nuit de danse jusqu’au bout de l’aube.', start_date: vertigeEventDates.start, end_date: vertigeEventDates.end, doors_open_time: relativeDate({ days: 1, hours: 20 }), min_age: 16, address: 'Avenue du Jura 8', city: 'Neuchâtel', latitude: 46.992, longitude: 6.929, price_presale: 15, price_door: 22, is_free: false, presale_total: 180, presale_sold: 142, ticketing_enabled: true, ticketing_commission: 12, status: 'PUBLISHED', featured_image_id: makeUuid('event-vertige-image'), published_at: relativeDate({ days: -5 }), created_at: relativeDate({ days: -60 }), updated_at: relativeDate({ hours: -3 }), }, { id: eventIds.nyx, organizer_id: organizerIds.nyx, name: 'Nyx Vandal', description: 'Une nuit pleine de synthés lourds, visuels noirs, peinture laser et énergie brute.', start_date: nyxEventDates.start, end_date: nyxEventDates.end, doors_open_time: relativeDate({ days: 2, hours: 21 }), min_age: 18, address: 'Impasse des Tilleuls 2', city: 'Fribourg', latitude: 46.806, longitude: 7.161, price_presale: 20, price_door: 28, is_free: false, presale_total: 260, presale_sold: 210, ticketing_enabled: true, ticketing_commission: 11, status: 'PUBLISHED', featured_image_id: makeUuid('event-nyx-image'), published_at: relativeDate({ days: -8 }), created_at: relativeDate({ days: -80 }), updated_at: relativeDate({ hours: -5 }), }, { id: eventIds.selene, organizer_id: organizerIds.selene, name: 'Sélène', description: 'Atmosphère élégante et nocturne, avec un line-up électro et un décor lumineux inspiré du ciel.', start_date: seleneEventDates.start, end_date: seleneEventDates.end, doors_open_time: relativeDate({ days: 3, hours: 18, minutes: 30 }), min_age: 18, address: 'Boulevard du Soleil 33', city: 'Lausanne', latitude: 46.5195, longitude: 6.633, price_presale: 25, price_door: 32, is_free: false, presale_total: 300, presale_sold: 243, ticketing_enabled: true, ticketing_commission: 9, status: 'PUBLISHED', featured_image_id: makeUuid('event-selene-image'), published_at: relativeDate({ days: -5 }), created_at: relativeDate({ days: -72 }), updated_at: relativeDate({ hours: -2 }), }, { id: eventIds.cernier, organizer_id: organizerIds.cernier, name: 'Fête de la Jeunesse de Cernier', description: 'Parade, musique en plein air et soirées conviviales pour la jeunesse locale, dans un esprit festif et libre.', start_date: cernierEventDates.start, end_date: cernierEventDates.end, doors_open_time: relativeDate({ days: 7, hours: 16 }), min_age: 0, address: 'Place du Village', city: 'Cernier', latitude: 47.092, longitude: 6.908, price_presale: 0, price_door: 0, is_free: true, presale_total: null, presale_sold: 0, ticketing_enabled: false, ticketing_commission: null, status: 'PUBLISHED', featured_image_id: makeUuid('event-cernier-image'), published_at: relativeDate({ days: -12 }), created_at: relativeDate({ days: -110 }), updated_at: relativeDate({ days: -1 }), }, ]; export const mockEventImages: EventImage[] = [ { id: makeUuid('image-neon-cover'), event_id: eventIds.neon, url: 'https://images.unsplash.com/photo-1516280440614-37939bbacd81?auto=format&fit=crop&w=1200&q=80', is_cover: true, display_order: 1, created_at: relativeDate({ days: -12 }), }, { id: makeUuid('image-vertige-cover'), event_id: eventIds.vertige, url: 'https://images.unsplash.com/photo-1501386761578-eac5c94b800a?auto=format&fit=crop&w=1200&q=80', is_cover: true, display_order: 1, created_at: relativeDate({ days: -18 }), }, { id: makeUuid('image-nyx-cover'), event_id: eventIds.nyx, url: 'https://images.unsplash.com/photo-1492684223066-81342ee5ff30?auto=format&fit=crop&w=1200&q=80', is_cover: true, display_order: 1, created_at: relativeDate({ days: -22 }), }, { id: makeUuid('image-selene-cover'), event_id: eventIds.selene, url: 'https://images.unsplash.com/photo-1525201548942-d8732f6617a0?auto=format&fit=crop&w=1200&q=80', is_cover: true, display_order: 1, created_at: relativeDate({ days: -14 }), }, { id: makeUuid('image-cernier-cover'), event_id: eventIds.cernier, url: 'https://images.unsplash.com/photo-1470229722913-7c0e2dbbafd3?auto=format&fit=crop&w=1200&q=80', is_cover: true, display_order: 1, created_at: relativeDate({ days: -20 }), }, ]; export const mockEventThemes: EventTheme[] = [ { event_id: eventIds.neon, theme_id: themeIds.nightlife }, { event_id: eventIds.neon, theme_id: themeIds.techno }, { event_id: eventIds.vertige, theme_id: themeIds.techno }, { event_id: eventIds.vertige, theme_id: themeIds.nightlife }, { event_id: eventIds.nyx, theme_id: themeIds.festival }, { event_id: eventIds.nyx, theme_id: themeIds.concert }, { event_id: eventIds.selene, theme_id: themeIds.nightlife }, { event_id: eventIds.selene, theme_id: themeIds.withFriends }, { event_id: eventIds.cernier, theme_id: themeIds.youth }, { event_id: eventIds.cernier, theme_id: themeIds.free }, ]; export const mockEventHashtags: EventHashtag[] = [ { event_id: eventIds.neon, hashtag_id: hashtagIds.neon }, { event_id: eventIds.neon, hashtag_id: hashtagIds.techno }, { event_id: eventIds.vertige, hashtag_id: hashtagIds.club }, { event_id: eventIds.vertige, hashtag_id: hashtagIds.electro }, { event_id: eventIds.nyx, hashtag_id: hashtagIds.electro }, { event_id: eventIds.nyx, hashtag_id: hashtagIds.club }, { event_id: eventIds.selene, hashtag_id: hashtagIds.afterwork }, { event_id: eventIds.selene, hashtag_id: hashtagIds.techno }, { event_id: eventIds.cernier, hashtag_id: hashtagIds.cernier }, { event_id: eventIds.cernier, hashtag_id: hashtagIds.afterwork }, ]; export const mockEventLineup: EventLineup[] = [ { id: makeUuid('lineup-neon-headliner'), event_id: eventIds.neon, animator_id: animatorIds.glow, stage_name: 'Glow Reign', start_time: relativeDate({ days: 0, hours: 19, minutes: 30 }), end_time: relativeDate({ days: 0, hours: 22, minutes: 0 }), role: 'HEADLINER', display_order: 1, }, { id: makeUuid('lineup-neon-opening'), event_id: eventIds.neon, animator_id: animatorIds.vanta, stage_name: 'Vanta Echo', start_time: relativeDate({ days: 0, hours: 17, minutes: 30 }), end_time: relativeDate({ days: 0, hours: 19, minutes: 15 }), role: 'OPENING', display_order: 2, }, { id: makeUuid('lineup-vertige-headliner'), event_id: eventIds.vertige, animator_id: animatorIds.ocho, stage_name: 'Ocho K', start_time: relativeDate({ days: 1, hours: 22, minutes: 30 }), end_time: relativeDate({ days: 2, hours: 1, minutes: 30 }), role: 'HEADLINER', display_order: 1, }, { id: makeUuid('lineup-nyx-headliner'), event_id: eventIds.nyx, animator_id: animatorIds.vanta, stage_name: 'Vanta Echo', start_time: relativeDate({ days: 2, hours: 23, minutes: 15 }), end_time: relativeDate({ days: 3, hours: 1, minutes: 45 }), role: 'HEADLINER', display_order: 1, }, { id: makeUuid('lineup-selene-headliner'), event_id: eventIds.selene, animator_id: animatorIds.isae, stage_name: 'Isae Noir', start_time: relativeDate({ days: 3, hours: 20, minutes: 0 }), end_time: relativeDate({ days: 3, hours: 21, minutes: 45 }), role: 'HEADLINER', display_order: 1, }, { id: makeUuid('lineup-cernier-headliner'), event_id: eventIds.cernier, animator_id: animatorIds.isae, stage_name: 'Isae Noir', start_time: relativeDate({ days: 7, hours: 18, minutes: 15 }), end_time: relativeDate({ days: 7, hours: 20, minutes: 0 }), role: 'HEADLINER', display_order: 1, }, ]; export const mockEventExternalLinks: EventExternalLink[] = [ { id: makeUuid('link-neon-website'), event_id: eventIds.neon, url: 'https://www.neonsession.ch', label: 'Site officiel', display_order: 1, }, { id: makeUuid('link-vertige-ticketing'), event_id: eventIds.vertige, url: 'https://www.clubvertige.ch/tickets', label: 'Billetterie', display_order: 1, }, { id: makeUuid('link-nyx-instagram'), event_id: eventIds.nyx, url: 'https://instagram.com/nyxvandal', label: 'Instagram', display_order: 1, }, { id: makeUuid('link-selene-flyer'), event_id: eventIds.selene, url: 'https://www.selene.events/flyer', label: 'Flyer', display_order: 1, }, { id: makeUuid('link-cernier-info'), event_id: eventIds.cernier, url: 'https://www.cernier.ch/fete-jeunesse', label: 'Programme', display_order: 1, }, ]; export const mockEventStatsSnapshots: EventStatsSnapshot[] = [ { id: makeUuid('stats-neon-1'), event_id: eventIds.neon, snapshot_date: toSqlDate(relativeDate({ days: -3 })), view_count: 1860, participant_count: 1280, interested_count: 362, share_count: 52, }, { id: makeUuid('stats-vertige-1'), event_id: eventIds.vertige, snapshot_date: toSqlDate(relativeDate({ days: -4 })), view_count: 2440, participant_count: 1530, interested_count: 498, share_count: 74, }, { id: makeUuid('stats-nyx-1'), event_id: eventIds.nyx, snapshot_date: toSqlDate(relativeDate({ days: -6 })), view_count: 3180, participant_count: 1860, interested_count: 619, share_count: 94, }, { id: makeUuid('stats-selene-1'), event_id: eventIds.selene, snapshot_date: toSqlDate(relativeDate({ days: -7 })), view_count: 2920, participant_count: 1712, interested_count: 444, share_count: 81, }, { id: makeUuid('stats-cernier-1'), event_id: eventIds.cernier, snapshot_date: toSqlDate(relativeDate({ days: -2 })), view_count: 1240, participant_count: 880, interested_count: 501, share_count: 39, }, ]; export const mockUserEventInteractions: UserEventInteraction[] = [ { user_id: userIds.lucie, event_id: eventIds.neon, status: 'PARTICIPATING', created_at: relativeDate({ days: -3 }), updated_at: relativeDate({ hours: -10 }) }, { user_id: userIds.noah, event_id: eventIds.vertige, status: 'INTERESTED', created_at: relativeDate({ days: -1 }), updated_at: relativeDate({ hours: -5 }) }, { user_id: userIds.amelia, event_id: eventIds.cernier, status: 'PARTICIPATING', created_at: relativeDate({ days: -6 }), updated_at: relativeDate({ days: -2 }) }, { user_id: userIds.sam, event_id: eventIds.selene, status: 'SAVED', created_at: relativeDate({ days: -2 }), updated_at: relativeDate({ days: -1 }) }, ]; export const mockUserOrganizerFollows: UserOrganizerFollow[] = [ { user_id: userIds.lucie, organizer_id: organizerIds.neon, created_at: relativeDate({ days: -30 }) }, { user_id: userIds.noah, organizer_id: organizerIds.vertige, created_at: relativeDate({ days: -18 }) }, { user_id: userIds.amelia, organizer_id: organizerIds.cernier, created_at: relativeDate({ days: -8 }) }, { user_id: userIds.sam, organizer_id: organizerIds.selene, created_at: relativeDate({ days: -14 }) }, ]; export const mockUserThemePreferences: UserThemePreference[] = [ { user_id: userIds.lucie, theme_id: themeIds.techno }, { user_id: userIds.lucie, theme_id: themeIds.nightlife }, { user_id: userIds.noah, theme_id: themeIds.festival }, { user_id: userIds.amelia, theme_id: themeIds.youth }, { user_id: userIds.sam, theme_id: themeIds.withFriends }, ]; export const mockUserAnimatorFollows: UserAnimatorFollow[] = [ { user_id: userIds.lucie, animator_id: animatorIds.glow, created_at: relativeDate({ days: -25 }) }, { user_id: userIds.noah, animator_id: animatorIds.ocho, created_at: relativeDate({ days: -12 }) }, { user_id: userIds.amelia, animator_id: animatorIds.isae, created_at: relativeDate({ days: -10 }) }, ]; export const mockUserEventShares: UserEventShare[] = [ { id: makeUuid('share-1'), user_id: userIds.lucie, event_id: eventIds.neon, shared_at: relativeDate({ days: -4 }), }, { id: makeUuid('share-2'), user_id: userIds.noah, event_id: eventIds.vertige, shared_at: relativeDate({ days: -2 }), }, ]; export const mockTickets: Ticket[] = [ { id: makeUuid('ticket-1'), event_id: eventIds.neon, user_id: userIds.lucie, type: 'PRESALE', price_paid: 18, bahla_commission: 1.8, status: 'VALID', purchased_at: relativeDate({ days: -10 }), used_at: null, qr_code: 'QR-NEON-12', }, { id: makeUuid('ticket-2'), event_id: eventIds.vertige, user_id: userIds.noah, type: 'PRESALE', price_paid: 15, bahla_commission: 1.5, status: 'VALID', purchased_at: relativeDate({ days: -6 }), used_at: null, qr_code: 'QR-VERTIGE-1', }, { id: makeUuid('ticket-3'), event_id: eventIds.cernier, user_id: userIds.amelia, type: 'DOOR', price_paid: 0, bahla_commission: 0, status: 'VALID', purchased_at: relativeDate({ days: -3 }), used_at: null, qr_code: 'QR-CERNIER-1', }, ]; export const mockPosts: Post[] = [ { id: makeUuid('post-neon'), organizer_id: organizerIds.neon, event_id: eventIds.neon, content: 'Première vague de billets presque sold out. On se retrouve pour une session longue et basse. #NeonSession', type: 'ANNOUNCEMENT', status: 'PUBLISHED', scheduled_at: null, published_at: relativeDate({ days: -2 }), rediffusion_instagram: true, rediffusion_tiktok: false, rediffusion_facebook: true, created_at: relativeDate({ days: -2 }), updated_at: relativeDate({ hours: -8 }), }, { id: makeUuid('post-vertige'), organizer_id: organizerIds.vertige, event_id: eventIds.vertige, content: 'Ce week-end, la salle passe en mode club total. Préparez vos meilleures chaussures.', type: 'TEASER', status: 'PUBLISHED', scheduled_at: null, published_at: relativeDate({ days: -1 }), rediffusion_instagram: true, rediffusion_tiktok: true, rediffusion_facebook: false, created_at: relativeDate({ days: -1 }), updated_at: relativeDate({ hours: -6 }), }, { id: makeUuid('post-cernier'), organizer_id: organizerIds.cernier, event_id: eventIds.cernier, content: 'La fête commence au village et s’étend sur tout le quartier. On vous attend avec la musique et les lumières.', type: 'PHOTO', status: 'PUBLISHED', scheduled_at: null, published_at: relativeDate({ days: -4 }), rediffusion_instagram: true, rediffusion_tiktok: false, rediffusion_facebook: true, created_at: relativeDate({ days: -4 }), updated_at: relativeDate({ days: -2 }), }, ]; export const mockPostMedia: PostMedia[] = [ { id: makeUuid('media-neon-1'), post_id: mockPosts[0].id, url: 'https://images.unsplash.com/photo-1516280440614-37939bbacd81?auto=format&fit=crop&w=1200&q=80', type: 'IMAGE', duration_seconds: null, display_order: 1, }, { id: makeUuid('media-vertige-1'), post_id: mockPosts[1].id, url: 'https://images.unsplash.com/photo-1501386761578-eac5c94b800a?auto=format&fit=crop&w=1200&q=80', type: 'IMAGE', duration_seconds: null, display_order: 1, }, { id: makeUuid('media-cernier-1'), post_id: mockPosts[2].id, url: 'https://images.unsplash.com/photo-1470229722913-7c0e2dbbafd3?auto=format&fit=crop&w=1200&q=80', type: 'IMAGE', duration_seconds: null, display_order: 1, }, ]; export const mockPostLikes: PostLike[] = [ { user_id: userIds.lucie, post_id: mockPosts[0].id, created_at: relativeDate({ days: -1 }) }, { user_id: userIds.noah, post_id: mockPosts[0].id, created_at: relativeDate({ days: -2 }) }, { user_id: userIds.amelia, post_id: mockPosts[2].id, created_at: relativeDate({ days: -3 }) }, { user_id: userIds.sam, post_id: mockPosts[1].id, created_at: relativeDate({ days: -1, hours: -2 }) }, ]; export const mockComments: Comment[] = [ { id: makeUuid('comment-1'), post_id: mockPosts[0].id, user_id: userIds.noah, parent_comment_id: null, content: 'Le line-up a l’air killer. J’y vais à fond.', created_at: relativeDate({ days: -1, hours: -3 }), updated_at: relativeDate({ days: -1, hours: -3 }), }, { id: makeUuid('comment-2'), post_id: mockPosts[0].id, user_id: userIds.lucie, parent_comment_id: null, content: 'J’ai réservé le billet en presale, ça part vite.', created_at: relativeDate({ days: -1, hours: -2 }), updated_at: relativeDate({ days: -1, hours: -2 }), }, { id: makeUuid('comment-3'), post_id: mockPosts[2].id, user_id: userIds.amelia, parent_comment_id: null, content: 'On compte sur le village pour faire la fête toute la nuit.', created_at: relativeDate({ days: -2 }), updated_at: relativeDate({ days: -2 }), }, ]; export const mockCommentLikes: CommentLike[] = [ { user_id: userIds.lucie, comment_id: mockComments[0].id, created_at: relativeDate({ days: -1, hours: -2 }) }, { user_id: userIds.amelia, comment_id: mockComments[2].id, created_at: relativeDate({ days: -2, hours: -1 }) }, ]; export const mockSearchHistory: SearchHistory[] = [ { id: makeUuid('search-1'), user_id: userIds.lucie, query: 'techno neuchâtel', searched_at: relativeDate({ days: -9 }) }, { id: makeUuid('search-2'), user_id: userIds.noah, query: 'club vertige', searched_at: relativeDate({ days: -3 }) }, { id: makeUuid('search-3'), user_id: userIds.amelia, query: 'cernier fête', searched_at: relativeDate({ days: -7 }) }, ]; export const mockSocialAuthProviders = [ { id: makeUuid('auth-1'), user_id: userIds.lucie, provider: 'google', provider_user_id: 'google-1', created_at: relativeDate({ days: -120 }), }, { id: makeUuid('auth-2'), user_id: userIds.noah, provider: 'apple', provider_user_id: 'apple-2', created_at: relativeDate({ days: -90 }), }, ] satisfies SocialAuthProvider[]; export const mockOrganizerMembers: OrganizerMember[] = [ { id: makeUuid('member-1'), organizer_id: organizerIds.neon, user_id: userIds.lucie, email: 'lucie@bahla.app', role: 'ADMIN', status: 'ACTIVE', invited_at: relativeDate({ days: -60 }), joined_at: relativeDate({ days: -55 }), }, { id: makeUuid('member-2'), organizer_id: organizerIds.vertige, user_id: userIds.noah, email: 'noah@bahla.app', role: 'EDITOR', status: 'ACTIVE', invited_at: relativeDate({ days: -40 }), joined_at: relativeDate({ days: -35 }), }, { id: makeUuid('member-3'), organizer_id: organizerIds.cernier, user_id: userIds.amelia, email: 'amelia@bahla.app', role: 'ADMIN', status: 'ACTIVE', invited_at: relativeDate({ days: -20 }), joined_at: relativeDate({ days: -19 }), }, ]; export const mockOrganizerSubscriptions: OrganizerSubscription[] = [ { id: makeUuid('subscription-1'), organizer_id: organizerIds.neon, plan_id: planIds.pro, status: 'ACTIVE', started_at: relativeDate({ days: -90 }), renewal_date: toSqlDate(relativeDate({ days: 29 })), cancelled_at: null, }, { id: makeUuid('subscription-2'), organizer_id: organizerIds.vertige, plan_id: planIds.ticketing, status: 'ACTIVE', started_at: relativeDate({ days: -120 }), renewal_date: toSqlDate(relativeDate({ days: 11 })), cancelled_at: null, }, { id: makeUuid('subscription-3'), organizer_id: organizerIds.cernier, plan_id: planIds.free, status: 'ACTIVE', started_at: relativeDate({ days: -35 }), renewal_date: toSqlDate(relativeDate({ days: 18 })), cancelled_at: null, }, ]; export const mockBahlaData = { plans: mockPlans, themes: mockThemes, hashtags: mockHashtags, organizers: mockOrganizers, users: mockUsers, animators: mockAnimators, events: mockEvents, event_images: mockEventImages, event_themes: mockEventThemes, event_hashtags: mockEventHashtags, event_lineup: mockEventLineup, event_external_links: mockEventExternalLinks, event_stats_snapshots: mockEventStatsSnapshots, organizer_members: mockOrganizerMembers, organizer_subscriptions: mockOrganizerSubscriptions, posts: mockPosts, post_media: mockPostMedia, post_likes: mockPostLikes, comments: mockComments, comment_likes: mockCommentLikes, tickets: mockTickets, searches: mockSearchHistory, social_auth_providers: mockSocialAuthProviders, user_event_interactions: mockUserEventInteractions, user_organizer_follows: mockUserOrganizerFollows, user_theme_preferences: mockUserThemePreferences, user_animator_follows: mockUserAnimatorFollows, user_event_shares: mockUserEventShares, }; export const mockEventsWithRelations = mockEvents.map((event) => { const organizer = mockOrganizers.find((item) => item.id === event.organizer_id) ?? null; const images = mockEventImages.filter((item) => item.event_id === event.id); const themes = mockEventThemes .filter((item) => item.event_id === event.id) .map((item) => mockThemes.find((theme) => theme.id === item.theme_id)) .filter(Boolean) as Theme[]; const hashtags = mockEventHashtags .filter((item) => item.event_id === event.id) .map((item) => mockHashtags.find((tag) => tag.id === item.hashtag_id)) .filter(Boolean) as Hashtag[]; const lineup = sortByDisplayOrder( mockEventLineup.filter((item) => item.event_id === event.id).map((entry) => ({ ...entry, animator: mockAnimators.find((animator) => animator.id === entry.animator_id) ?? null, })), ); return { ...event, organizer, images, themes, hashtags, lineup, }; }); export const mockNow = baseDate;