feat: montly songs

This commit is contained in:
2026-01-13 12:01:28 +01:00
parent 671bfe8a73
commit 29c19bb34a
2 changed files with 72 additions and 59 deletions
+63 -59
View File
@@ -3,7 +3,6 @@ import {
FlatList, FlatList,
Image, Image,
Platform, Platform,
Pressable,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
Text, Text,
@@ -20,17 +19,13 @@ import useDataFromRef from '../../hooks/useDataFromRef'
import useNavigateToMusicDetails from '../../hooks/useNavigateToMusicDetails' import useNavigateToMusicDetails from '../../hooks/useNavigateToMusicDetails'
import useSearch from '../../hooks/useSearch' import useSearch from '../../hooks/useSearch'
import Page from '../../layouts/Page' import Page from '../../layouts/Page'
import { Routes } from '../../navigation'
import { navigate } from '../../navigation/NavigationService'
import { Palette, Style } from '../../styles' import { Palette, Style } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts' import { FONT_FAMILY } from '../../styles/Fonts'
import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader' import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader'
import MobileCoinBadge from '../../components/MobileCoinBadge' import MobileCoinBadge from '../../components/MobileCoinBadge'
import ResearchHeader from '../Library/components/ResearchHeader' import ResearchHeader from '../Library/components/ResearchHeader'
import SearchResultsList from '../Library/components/SearchResultsList' import SearchResultsList from '../Library/components/SearchResultsList'
import PlaybacksCard from './components/PlaybacksCard'
import SongCard from './components/SongCard' import SongCard from './components/SongCard'
import BorderGradient from '../../components/BorderGradient/BorderGradient.web'
const allowedPremiumLevels = new Set(['starter', 'pro', 'premium']) const allowedPremiumLevels = new Set(['starter', 'pro', 'premium'])
@@ -53,34 +48,37 @@ const chunkArray = (items = [], size = 10) => {
const HitParade = () => { const HitParade = () => {
const [selectedCategory, setSelectedCategory] = useState('Chansons') const [selectedCategory, setSelectedCategory] = useState('Chansons')
const navigateToMusicDetails = useNavigateToMusicDetails() const navigateToMusicDetails = useNavigateToMusicDetails()
const { data: topSongs } = useDataFromRef({ const { data: allSongs } = useDataFromRef({
ref: projectsRef.where('views', '>', 0).orderBy('views', 'desc').limit(20), ref: projectsRef.orderBy('updatedAt', 'desc'),
format: (docs) => docs.filter((item) => item?.hasPlayback !== true),
simpleRef: false, simpleRef: false,
listener: true, listener: true,
condition: true, condition: true,
}) })
const { data: topPlaybacks } = useDataFromRef({ const previousMonthKey = useMemo(() => {
ref: projectsRef.where('views', '>', 0).orderBy('views', 'desc').limit(60), const now = new Date()
format: (docs) => docs.filter((d) => d?.playbackUrl != null).slice(0, 20), const target = new Date(now.getFullYear(), now.getMonth(), 1)
simpleRef: false, target.setMonth(target.getMonth() - 1)
listener: true, const month = String(target.getMonth() + 1).padStart(2, '0')
condition: true, return `${target.getFullYear()}-${month}`
})
const songsList = useMemo(() => (Array.isArray(topSongs) ? topSongs : []), [topSongs])
const playbackList = useMemo(
() => (Array.isArray(topPlaybacks) ? topPlaybacks : []),
[topPlaybacks]
)
const resolvePlaybackThumbnail = useCallback((project) => {
const candidates = [project?.thumbnailUrl, project?.songThumbnailUrl, project?.coverUrl]
const uri = candidates.find((value) => typeof value === 'string' && value.trim().length > 0)
return uri || null
}, []) }, [])
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 === // === RENDUS PAR PLATEFORME ===
const renderSongsMobile = () => ( const renderSongsMobile = () => (
<FlatList <FlatList
@@ -88,11 +86,11 @@ const HitParade = () => {
gap: 10, gap: 10,
paddingBottom: responsiveHeight(20), paddingBottom: responsiveHeight(20),
}} }}
data={Array.isArray(topSongs) ? topSongs : []} data={songsList}
keyExtractor={(item) => item.id} keyExtractor={(item) => item.id}
renderItem={({ item, index }) => ( renderItem={({ item }) => (
<SongCard <SongCard
rank={index + 1} rank={null}
title={item?.title || 'Sans titre'} title={item?.title || 'Sans titre'}
artist={item?.userName} artist={item?.userName}
coverUrl={item?.coverUrl || null} coverUrl={item?.coverUrl || null}
@@ -109,22 +107,27 @@ const HitParade = () => {
/> />
) )
const renderPlaybacksMobile = () => ( const renderTopMonthMobile = () => (
<FlatList <FlatList
contentContainerStyle={{ contentContainerStyle={{
gap: 10, gap: 10,
paddingBottom: responsiveHeight(20), paddingBottom: responsiveHeight(20),
}} }}
data={playbackList} data={topMonthList}
keyExtractor={(item) => item.id} keyExtractor={(item) => item.projectId}
renderItem={({ item, index }) => ( renderItem={({ item }) => (
<PlaybacksCard <SongCard
rank={index + 1} rank={item?.rank}
title={item?.title || 'Sans titre'} title={item?.title || 'Sans titre'}
artist={item?.userName} artist={item?.userName}
thumbnailUrl={resolvePlaybackThumbnail(item)} coverUrl={item?.coverUrl || null}
subscriptionLevel={getCreatorLevel(item)} subscriptionLevel={getCreatorLevel(item)}
onPress={() => navigate(Routes.Playbacks, { projectId: item.id })} onPress={() =>
navigateToMusicDetails({
projectId: item?.projectId,
songUrl: item?.songUrl,
})
}
/> />
)} )}
/> />
@@ -132,10 +135,10 @@ const HitParade = () => {
const renderSongList = () => ( const renderSongList = () => (
<View style={{ gap: 10 }}> <View style={{ gap: 10 }}>
{songsList.map((item, idx) => ( {songsList.map((item) => (
<SongCard <SongCard
key={item.id} key={item.id}
rank={idx + 1} rank={null}
title={item?.title || 'Sans titre'} title={item?.title || 'Sans titre'}
artist={item?.userName} artist={item?.userName}
coverUrl={item?.coverUrl || null} coverUrl={item?.coverUrl || null}
@@ -152,17 +155,22 @@ const HitParade = () => {
</View> </View>
) )
const renderPlaybackColumns = () => ( const renderTopMonthColumns = () => (
<View style={{ gap: 10 }}> <View style={{ gap: 10 }}>
{playbackList.map((item, idx) => ( {topMonthList.map((item) => (
<PlaybacksCard <SongCard
key={item.id} key={item.projectId}
rank={idx + 1} rank={item?.rank}
title={item?.title || 'Sans titre'} title={item?.title || 'Sans titre'}
artist={item?.userName} artist={item?.userName}
thumbnailUrl={resolvePlaybackThumbnail(item)} coverUrl={item?.coverUrl || null}
subscriptionLevel={getCreatorLevel(item)} subscriptionLevel={getCreatorLevel(item)}
onPress={() => navigate(Routes.Playbacks, { projectId: item.id })} onPress={() =>
navigateToMusicDetails({
projectId: item?.projectId,
songUrl: item?.songUrl,
})
}
/> />
))} ))}
</View> </View>
@@ -204,9 +212,9 @@ const HitParade = () => {
} }
} }
songsList.forEach(collectUserId) songsList.forEach(collectUserId)
playbackList.forEach(collectUserId) topMonthList.forEach(collectUserId)
return Array.from(collected) return Array.from(collected)
}, [playbackList, songsList]) }, [songsList, topMonthList])
const fetchCreatorsByIds = useCallback( const fetchCreatorsByIds = useCallback(
async (userIds) => { async (userIds) => {
@@ -332,7 +340,7 @@ const HitParade = () => {
const shouldBlurContent = shouldShowResults const shouldBlurContent = shouldShowResults
const handlePlayRandomSong = useCallback(() => { const handlePlayRandomSong = useCallback(() => {
const arr = Array.isArray(topSongs) ? topSongs : [] const arr = songsList
if (!arr.length) { if (!arr.length) {
console.warn('Aucune chanson disponible pour le moment.') console.warn('Aucune chanson disponible pour le moment.')
@@ -346,17 +354,13 @@ const HitParade = () => {
songUrl: randomSong?.songUrl, songUrl: randomSong?.songUrl,
project: randomSong, project: randomSong,
}) })
}, [navigateToMusicDetails, topSongs]) }, [navigateToMusicDetails, songsList])
const categoryLabel = selectedCategory === 'Playback' ? 'playbacks' : 'chansons'
const heroTitle = isWeb const heroTitle = isWeb
? 'Coups de coeur des utilisateurs' ? 'Coups de coeur des utilisateurs'
: `Coups de coeur des utilisateurs catégorie ${selectedCategory}` : `Coups de coeur des utilisateurs catégorie ${selectedCategory}`
const heroSubtitle = isWeb const heroSubtitle = 'Les 3 musiques les plus likées du mois reçoivent des tokens.'
? 'Les 3 chansons et playbacks les plus likées du mois reçoivent des tokens.'
: `Les 3 ${categoryLabel} les plus likées du mois reçoivent des tokens.`
const renderHero = () => ( const renderHero = () => (
<View style={{ gap: 11, maxWidth: 800, width: '100%', alignSelf: 'center' }}> <View style={{ gap: 11, maxWidth: 800, width: '100%', alignSelf: 'center' }}>
@@ -453,11 +457,11 @@ const HitParade = () => {
fontFamily: FONT_FAMILY.HelveticaNeueBold, fontFamily: FONT_FAMILY.HelveticaNeueBold,
}} }}
> >
Playbacks Top du mois
</Text> </Text>
{/* </BorderGradient> */} {/* </BorderGradient> */}
</BlurView> </BlurView>
{renderPlaybackColumns()} {renderTopMonthColumns()}
</View> </View>
</View> </View>
</ScrollView> </ScrollView>
@@ -474,7 +478,7 @@ const HitParade = () => {
gap: 12, gap: 12,
}} }}
> >
{['Chansons', 'Playback'].map((item, index) => ( {['Chansons', 'Top du mois'].map((item, index) => (
<BorderGradientButton <BorderGradientButton
key={index} key={index}
title={item} title={item}
@@ -490,7 +494,7 @@ const HitParade = () => {
</View> </View>
{selectedCategory === 'Chansons' && renderSongsMobile()} {selectedCategory === 'Chansons' && renderSongsMobile()}
{selectedCategory === 'Playback' && renderPlaybacksMobile()} {selectedCategory === 'Top du mois' && renderTopMonthMobile()}
{/* "Clips" pourra reprendre la même logique si tu le réactives */} {/* "Clips" pourra reprendre la même logique si tu le réactives */}
</View> </View>
) )
+9
View File
@@ -0,0 +1,9 @@
export const buildMonthKey = (date = new Date()) => {
const target = date instanceof Date ? new Date(date) : new Date()
const year = target.getFullYear()
const month = String(target.getMonth() + 1).padStart(2, '0')
return `${year}-${month}`
}
export default buildMonthKey