diff --git a/src/mocks/bahla.ts b/src/mocks/bahla.ts new file mode 100644 index 0000000..76e4481 --- /dev/null +++ b/src/mocks/bahla.ts @@ -0,0 +1,1085 @@ +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; diff --git a/src/mocks/index.ts b/src/mocks/index.ts new file mode 100644 index 0000000..b4d16df --- /dev/null +++ b/src/mocks/index.ts @@ -0,0 +1 @@ +export * from './bahla';