import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { FlatList, Image, Platform, ScrollView, StyleSheet, Text, View, } from 'react-native' import { BlurView } from 'expo-blur' import { responsiveHeight } from 'react-native-responsive-dimensions' import { background, icons } from '../../assets' import BorderGradientButton from '../../components/BorderGradientButton' import SearchBar from '../../components/SearchBar' import ShareBtn from '../../components/ShareBtn/ShareBtn' import firebase, { projectsRef, usersRef } from '../../config/firebase' import useDataFromRef from '../../hooks/useDataFromRef' import useNavigateToMusicDetails from '../../hooks/useNavigateToMusicDetails' import useSearch from '../../hooks/useSearch' import Page from '../../layouts/Page' import { Palette, Style } from '../../styles' import { FONT_FAMILY } from '../../styles/Fonts' import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader' import MobileCoinBadge from '../../components/MobileCoinBadge' import ResearchHeader from '../Library/components/ResearchHeader' import SearchResultsList from '../Library/components/SearchResultsList' import SongCard from './components/SongCard' const allowedPremiumLevels = new Set(['starter', 'pro', 'premium']) const normalizePremiumLevel = (value) => { if (typeof value !== 'string') { return null } const normalized = value.trim().toLowerCase() return allowedPremiumLevels.has(normalized) ? normalized : null } const chunkArray = (items = [], size = 10) => { const chunks = [] for (let i = 0; i < items.length; i += size) { chunks.push(items.slice(i, i + size)) } return chunks } const HitParade = () => { const [selectedCategory, setSelectedCategory] = useState('Chansons') const navigateToMusicDetails = useNavigateToMusicDetails() const { data: allSongs } = useDataFromRef({ ref: projectsRef.orderBy('updatedAt', 'desc'), format: (docs) => docs.filter((item) => item?.hasPlayback !== true), simpleRef: false, listener: true, condition: true, }) const previousMonthKey = useMemo(() => { const now = new Date() const target = new Date(now.getFullYear(), now.getMonth(), 1) target.setMonth(target.getMonth() - 1) const month = String(target.getMonth() + 1).padStart(2, '0') return `${target.getFullYear()}-${month}` }, []) const { data: monthlyTopDoc } = useDataFromRef({ ref: firebase.firestore().collection('monthlyTopSongs').doc(previousMonthKey), initialState: null, simpleRef: true, listener: false, condition: true, }) const songsList = useMemo(() => (Array.isArray(allSongs) ? allSongs : []), [allSongs]) const topMonthList = useMemo(() => { const items = monthlyTopDoc?.topProjects return Array.isArray(items) ? items : [] }, [monthlyTopDoc]) // === RENDUS PAR PLATEFORME === const renderSongsMobile = () => ( item.id} renderItem={({ item }) => ( navigateToMusicDetails({ projectId: item.id, songUrl: item?.songUrl, project: item, }) } /> )} /> ) const renderTopMonthMobile = () => ( item.projectId} renderItem={({ item }) => ( navigateToMusicDetails({ projectId: item?.projectId, songUrl: item?.songUrl, }) } /> )} /> ) const renderSongList = () => ( {songsList.map((item) => ( navigateToMusicDetails({ projectId: item.id, songUrl: item?.songUrl, project: item, }) } /> ))} ) const renderTopMonthColumns = () => ( {topMonthList.map((item) => ( navigateToMusicDetails({ projectId: item?.projectId, songUrl: item?.songUrl, }) } /> ))} ) const isWeb = Platform.OS === 'web' const { search, setSearch, selected: searchSelected, setSelected: setSearchSelected, users = [], musics = [], playbacks = [], loading: searchLoading, } = useSearch() const [dropdownVisible, setDropdownVisible] = useState(false) const searchWrapperRef = useRef(null) const hasSearchQuery = search.trim().length > 0 const toggleSearchSelected = useCallback( (value) => { setSearchSelected((previous) => (previous === value ? null : value)) }, [setSearchSelected] ) const musicResults = useMemo(() => (Array.isArray(musics) ? musics : []), [musics]) const [creatorsById, setCreatorsById] = useState({}) const requiredUserIds = useMemo(() => { const collected = new Set() const collectUserId = (project) => { const uid = project && typeof project.userId === 'string' ? project.userId.trim() : null if (uid) { collected.add(uid) } } songsList.forEach(collectUserId) topMonthList.forEach(collectUserId) return Array.from(collected) }, [songsList, topMonthList]) const fetchCreatorsByIds = useCallback( async (userIds) => { if (!Array.isArray(userIds) || userIds.length === 0) { return } const normalizedIds = userIds .map((id) => (typeof id === 'string' ? id.trim() : null)) .filter(Boolean) if (normalizedIds.length === 0) { return } const pendingIds = new Set(normalizedIds) const nextCreators = {} const idChunks = chunkArray(normalizedIds, 10) await Promise.all( idChunks.map(async (chunk) => { try { const snapshot = await usersRef .where(firebase.firestore.FieldPath.documentId(), 'in', chunk) .get() snapshot.docs.forEach((doc) => { const data = doc.data() || {} nextCreators[doc.id] = { premiumLevel: normalizePremiumLevel(data.premiumLevel), } pendingIds.delete(doc.id) }) } catch (error) { console.log('HitParade: unable to fetch creators', error?.message || error) } }) ) pendingIds.forEach((userId) => { nextCreators[userId] = { premiumLevel: null } }) if (Object.keys(nextCreators).length > 0) { setCreatorsById((previous) => ({ ...previous, ...nextCreators })) } }, [setCreatorsById] ) useEffect(() => { const missingIds = requiredUserIds.filter((id) => !creatorsById[id]) if (missingIds.length === 0) { return } fetchCreatorsByIds(missingIds) }, [creatorsById, fetchCreatorsByIds, requiredUserIds]) const getCreatorLevel = useCallback( (project) => { const uid = project && typeof project.userId === 'string' ? project.userId.trim() : null if (!uid) { return null } return creatorsById?.[uid]?.premiumLevel || null }, [creatorsById] ) const playbackResults = useMemo(() => (Array.isArray(playbacks) ? playbacks : []), [playbacks]) const userResults = useMemo(() => (Array.isArray(users) ? users : []), [users]) const musicsLoading = searchLoading const playbacksLoading = searchLoading const usersLoading = searchLoading const closeDropdown = useCallback(() => { setDropdownVisible(false) setSearchSelected(null) setSearch('') }, [setSearch, setSearchSelected]) const handleSearchFocus = useCallback(() => { if (isWeb) { setDropdownVisible(true) } }, [isWeb]) const handleSearchChange = useCallback( (value) => { setSearch(value) if (isWeb) { setDropdownVisible(true) } }, [isWeb, setSearch] ) useEffect(() => { if (!isWeb || !dropdownVisible) { return undefined } const handleClickOutside = (event) => { if (searchWrapperRef.current?.contains?.(event.target)) { return } closeDropdown() } document.addEventListener('mousedown', handleClickOutside) document.addEventListener('touchstart', handleClickOutside) return () => { document.removeEventListener('mousedown', handleClickOutside) document.removeEventListener('touchstart', handleClickOutside) } }, [closeDropdown, dropdownVisible, isWeb]) const shouldShowResults = isWeb && dropdownVisible const shouldBlurContent = shouldShowResults const handlePlayRandomSong = useCallback(() => { const arr = songsList if (!arr.length) { console.warn('Aucune chanson disponible pour le moment.') return } const randomSong = arr[Math.floor(Math.random() * arr.length)] navigateToMusicDetails({ projectId: randomSong.id, songUrl: randomSong?.songUrl, project: randomSong, }) }, [navigateToMusicDetails, songsList]) const heroTitle = isWeb ? 'Coups de coeur des utilisateurs' : `Coups de coeur des utilisateurs catégorie ${selectedCategory}` const heroSubtitle = 'Les 3 musiques les plus likées du mois reçoivent des tokens.' const renderHero = () => ( {/* */} {heroTitle} {heroSubtitle} ) const renderWebContent = () => ( {renderHero()} {/* */} Chansons {/* */} {renderSongList()} {/* */} Top du mois {/* */} {renderTopMonthColumns()} ) const renderMobileContent = () => ( {renderHero()} {['Chansons', 'Top du mois'].map((item, index) => ( setSelectedCategory(item)} tint={selectedCategory === item ? 'light' : 'dark'} containerStyle={{ flex: 1 }} titleStyle={{ fontSize: 16, fontFamily: FONT_FAMILY.HelveticaNeueBold, }} /> ))} {selectedCategory === 'Chansons' && renderSongsMobile()} {selectedCategory === 'Top du mois' && renderTopMonthMobile()} {/* "Clips" pourra reprendre la même logique si tu le réactives */} ) return ( {!isWeb ? ( ) : null} {isWeb && ( {shouldShowResults && ( )} )} {shouldBlurContent && ( )} {isWeb ? renderWebContent() : renderMobileContent()} ) } export default HitParade