diff --git a/src/screens/HitParade/HitParade.js b/src/screens/HitParade/HitParade.js
index e5884e1..90cb36a 100644
--- a/src/screens/HitParade/HitParade.js
+++ b/src/screens/HitParade/HitParade.js
@@ -1,20 +1,30 @@
-import React, { useCallback, useMemo, useState } from "react";
+import React, {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
import {
FlatList,
Image,
Platform,
Pressable,
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 { projectsRef } from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef";
import useNavigateToMusicDetails from "../../hooks/useNavigateToMusicDetails";
+import useSearch from "../../hooks/useSearch";
import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
@@ -22,31 +32,33 @@ 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 PlaybacksCard from "./components/PlaybacksCard";
import SongCard from "./components/SongCard";
+import BorderGradient from "../../components/BorderGradient/BorderGradient.web";
const HitParade = () => {
- const [selected, setSelected] = useState("Chansons");
+ const [selectedCategory, setSelectedCategory] = useState("Chansons");
const navigateToMusicDetails = useNavigateToMusicDetails();
const { data: topSongs } = useDataFromRef({
- ref: projectsRef.where("views", ">", 0).orderBy("views", "desc").limit(10),
+ ref: projectsRef.where("views", ">", 0).orderBy("views", "desc").limit(20),
simpleRef: false,
listener: true,
condition: true,
});
const { data: topPlaybacks } = useDataFromRef({
- ref: projectsRef.where("views", ">", 0).orderBy("views", "desc").limit(50),
- format: (docs) => docs.filter((d) => d?.playbackUrl != null).slice(0, 10),
+ ref: projectsRef.where("views", ">", 0).orderBy("views", "desc").limit(60),
+ format: (docs) => docs.filter((d) => d?.playbackUrl != null).slice(0, 20),
simpleRef: false,
listener: true,
condition: true,
});
- // Découpe en 2 colonnes (pour le web uniquement)
- const [songsCol1, songsCol2] = useMemo(() => {
- const arr = Array.isArray(topSongs) ? topSongs : [];
- return [arr.slice(0, 5), arr.slice(5)];
- }, [topSongs]);
+ const songsList = useMemo(
+ () => (Array.isArray(topSongs) ? topSongs : []),
+ [topSongs]
+ );
const [playbacksCol1, playbacksCol2] = useMemo(() => {
const arr = Array.isArray(topPlaybacks) ? topPlaybacks : [];
@@ -60,7 +72,7 @@ const HitParade = () => {
project?.coverUrl,
];
const uri = candidates.find(
- (value) => typeof value === "string" && value.trim().length > 0,
+ (value) => typeof value === "string" && value.trim().length > 0
);
return uri || null;
}, []);
@@ -112,103 +124,147 @@ const HitParade = () => {
/>
);
- const renderSongsWeb = () => (
-
-
- {/* Colonne gauche (1..5) */}
-
- {songsCol1.map((item, idx) => (
-
- navigateToMusicDetails({
- projectId: item.id,
- songUrl: item?.songUrl,
- project: item,
- })
- }
- />
- ))}
-
- {/* Colonne droite (6..N) */}
-
- {songsCol2.length > 0 && (
-
- {songsCol2.map((item, idx) => (
-
- navigateToMusicDetails({
- projectId: item.id,
- songUrl: item?.songUrl,
- project: item,
- })
- }
- />
- ))}
-
- )}
-
-
+ const renderSongList = () => (
+
+ {songsList.map((item, idx) => (
+
+ navigateToMusicDetails({
+ projectId: item.id,
+ songUrl: item?.songUrl,
+ project: item,
+ })
+ }
+ />
+ ))}
+
);
- const renderPlaybacksWeb = () => (
-
-
- {/* Colonne gauche (1..5) */}
+ const renderPlaybackColumns = () => (
+
+
+ {playbacksCol1.map((item, idx) => (
+ navigate(Routes.Playbacks, { projectId: item.id })}
+ />
+ ))}
+
+
+ {playbacksCol2.length > 0 && (
- {playbacksCol1.map((item, idx) => (
+ {playbacksCol2.map((item, idx) => (
navigate(Routes.Playbacks, { projectId: item.id })}
/>
))}
-
- {/* Colonne droite (6..N) */}
- {playbacksCol2.length > 0 && (
-
- {playbacksCol2.map((item, idx) => (
-
- navigate(Routes.Playbacks, { projectId: item.id })
- }
- />
- ))}
-
- )}
-
-
+ )}
+
);
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 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 && hasSearchQuery;
+
const handlePlayRandomSong = useCallback(() => {
const arr = Array.isArray(topSongs) ? topSongs : [];
@@ -226,12 +282,162 @@ const HitParade = () => {
});
}, [navigateToMusicDetails, topSongs]);
+ const categoryLabel =
+ selectedCategory === "Playback" ? "playbacks" : "chansons";
+
+ const heroTitle = isWeb
+ ? "Coups de coeur des utilisateurs"
+ : `Coups de coeur des utilisateurs catégorie ${selectedCategory}`;
+
+ const heroSubtitle = isWeb
+ ? "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 = () => (
+
+ {/*
+
+ */}
+
+
+ {heroTitle}
+
+
+ {heroSubtitle}
+
+
+
+ );
+
+ const renderWebContent = () => (
+
+ {renderHero()}
+
+
+
+ {/* */}
+
+ Chansons
+
+ {/* */}
+
+ {renderSongList()}
+
+
+
+ {/* */}
+
+ Playbacks
+
+ {/* */}
+
+ {renderPlaybackColumns()}
+
+
+
+ );
+
+ const renderMobileContent = () => (
+
+ {renderHero()}
+
+ {["Chansons", "Playback"].map((item, index) => (
+ setSelectedCategory(item)}
+ tint={selectedCategory === item ? "light" : "dark"}
+ containerStyle={{ flex: 1 }}
+ titleStyle={{
+ fontSize: 16,
+ fontFamily: FONT_FAMILY.HelveticaNeueBold,
+ }}
+ />
+ ))}
+
+
+ {selectedCategory === "Chansons" && renderSongsMobile()}
+ {selectedCategory === "Playback" && renderPlaybacksMobile()}
+ {/* "Clips" pourra reprendre la même logique si tu le réactives */}
+
+ );
+
return (
@@ -255,67 +461,89 @@ const HitParade = () => {
source={icons.musicLandLogo}
style={{ alignSelf: "center", height: 150, resizeMode: "contain" }}
/>
-
-
-
-
-
-
-
- Coups de coeur des utilisateurs catégorie {selected}
-
-
- Les 3 {selected} les plus likées du mois reçoivent des{"\n"}
- tokens.
-
-
-
+ {isWeb && (
- {["Chansons", "Playback"].map((item, index) => (
- setSelected(item)}
- tint={selected === item ? "light" : "dark"}
- containerStyle={{ flex: 1 }}
- titleStyle={{
- fontSize: 16,
- fontFamily: FONT_FAMILY.HelveticaNeueBold,
+
+ {shouldShowResults && (
+
- ))}
+ >
+
+
+
+
+
+ )}
+ )}
- {selected === "Chansons" &&
- (isWeb ? renderSongsWeb() : renderSongsMobile())}
- {selected === "Playback" &&
- (isWeb ? renderPlaybacksWeb() : renderPlaybacksMobile())}
- {/* "Clips" pourra reprendre la même logique si tu le réactives */}
+
+ {shouldBlurContent && (
+
+ )}
+
+ {isWeb ? renderWebContent() : renderMobileContent()}
diff --git a/src/screens/HitParade/components/PlaybacksCard.js b/src/screens/HitParade/components/PlaybacksCard.js
index 87a1d95..b064109 100644
--- a/src/screens/HitParade/components/PlaybacksCard.js
+++ b/src/screens/HitParade/components/PlaybacksCard.js
@@ -20,7 +20,7 @@ const PlaybacksCard = ({
return (
)}