clear more ticket, sub and design

This commit is contained in:
Thomas Demirdjian
2025-12-10 15:39:32 +01:00
parent b1ab6a549f
commit aacbd5411e
15 changed files with 1001 additions and 145 deletions
+120 -2
View File
@@ -21,7 +21,7 @@ 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 firebase, { projectsRef, usersRef } from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef";
import useNavigateToMusicDetails from "../../hooks/useNavigateToMusicDetails";
import useSearch from "../../hooks/useSearch";
@@ -37,6 +37,25 @@ import SearchResultsList from "../Library/components/SearchResultsList";
import PlaybacksCard from "./components/PlaybacksCard";
import SongCard from "./components/SongCard";
import BorderGradient from "../../components/BorderGradient/BorderGradient.web";
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();
@@ -92,6 +111,7 @@ const HitParade = () => {
title={item?.title || "Sans titre"}
artist={item?.userName}
coverUrl={item?.coverUrl || null}
subscriptionLevel={getCreatorLevel(item)}
onPress={() =>
navigateToMusicDetails({
projectId: item.id,
@@ -118,6 +138,7 @@ const HitParade = () => {
title={item?.title || "Sans titre"}
artist={item?.userName}
thumbnailUrl={resolvePlaybackThumbnail(item)}
subscriptionLevel={getCreatorLevel(item)}
onPress={() => navigate(Routes.Playbacks, { projectId: item.id })}
/>
)}
@@ -133,6 +154,7 @@ const HitParade = () => {
title={item?.title || "Sans titre"}
artist={item?.userName}
coverUrl={item?.coverUrl || null}
subscriptionLevel={getCreatorLevel(item)}
onPress={() =>
navigateToMusicDetails({
projectId: item.id,
@@ -154,6 +176,7 @@ const HitParade = () => {
title={item?.title || "Sans titre"}
artist={item?.userName}
thumbnailUrl={resolvePlaybackThumbnail(item)}
subscriptionLevel={getCreatorLevel(item)}
onPress={() => navigate(Routes.Playbacks, { projectId: item.id })}
/>
))}
@@ -188,6 +211,99 @@ const HitParade = () => {
[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);
playbackList.forEach(collectUserId);
return Array.from(collected);
}, [playbackList, songsList]);
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]
@@ -246,7 +362,7 @@ const HitParade = () => {
}, [closeDropdown, dropdownVisible, isWeb]);
const shouldShowResults = isWeb && dropdownVisible;
const shouldBlurContent = shouldShowResults && hasSearchQuery;
const shouldBlurContent = shouldShowResults;
const handlePlayRandomSong = useCallback(() => {
const arr = Array.isArray(topSongs) ? topSongs : [];
@@ -520,7 +636,9 @@ const HitParade = () => {
StyleSheet.absoluteFillObject,
{
zIndex: 10,
borderRadius: 18,
backgroundColor: "rgba(0, 0, 0, 0.25)",
overflow: "hidden",
},
]}
/>
@@ -4,6 +4,7 @@ import { BlurView } from "expo-blur";
import { img } from "../../../assets";
import { Palette, Style } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts";
import SubscriptionBadge from "../../../components/SubscriptionBadge";
const PlaybacksCard = ({
rank = 1,
@@ -12,6 +13,7 @@ const PlaybacksCard = ({
thumbnailUrl = null,
coverUrl = null,
onPress = () => null,
subscriptionLevel = null,
}) => {
const resolveUri = (value) =>
typeof value === "string" && value.trim().length > 0 ? value : null;
@@ -38,43 +40,67 @@ const PlaybacksCard = ({
style={{
...Style.containerRow,
gap: 15,
alignItems: "center",
justifyContent: "space-between",
width: "100%",
}}
>
<Text
<View
style={{
fontSize: 22,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
...Style.containerRow,
gap: 15,
alignItems: "center",
flex: 1,
}}
>
{rank}
</Text>
<Image
source={imageUri ? { uri: imageUri } : img.placeholder3}
style={{ width: 67, height: 108, borderRadius: 16 }}
/>
<View>
<Text
style={{
fontSize: 16,
fontSize: 22,
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
fontFamily: FONT_FAMILY.InterSemiBold,
}}
numberOfLines={1}
>
{title}
</Text>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
}}
numberOfLines={1}
>
{artist}
{rank}
</Text>
<Image
source={imageUri ? { uri: imageUri } : img.placeholder3}
style={{ width: 67, height: 108, borderRadius: 16 }}
/>
<View style={{ flex: 1 }}>
<Text
style={{
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
}}
numberOfLines={1}
>
{title}
</Text>
<View
style={{
...Style.containerRow,
gap: 6,
flexWrap: "wrap",
alignItems: "center",
justifyContent: "space-between",
}}
>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
flexShrink: 1,
}}
numberOfLines={1}
>
{artist}
</Text>
</View>
</View>
</View>
<SubscriptionBadge level={subscriptionLevel} size={36} />
</View>
</BlurView>
</Pressable>
+25 -7
View File
@@ -12,6 +12,7 @@ import { BlurView } from "expo-blur";
import Style, { size } from "../../../styles/Style";
import { Palette } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts";
import SubscriptionBadge from "../../../components/SubscriptionBadge";
const SongCard = ({
rank = 1,
@@ -19,6 +20,7 @@ const SongCard = ({
artist = "MusicLand",
coverUrl = null,
onPress = null,
subscriptionLevel = null,
}) => {
return (
<Pressable
@@ -69,7 +71,7 @@ const SongCard = ({
>
{rank}
</Text>
<View>
<View style={{ flex: 1 }}>
<Text
style={{
fontSize: 16,
@@ -79,16 +81,32 @@ const SongCard = ({
>
{title}
</Text>
<Text
<View
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
...Style.containerRow,
gap: 6,
flexWrap: "wrap",
alignItems: "center",
justifyContent: "space-between",
}}
>
{artist}
</Text>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
flexShrink: 1,
}}
>
{artist}
</Text>
</View>
</View>
<SubscriptionBadge
level={subscriptionLevel}
size={36}
style={{ marginLeft: "auto" }}
/>
</View>
</BlurView>
</Pressable>