minors fixes
This commit is contained in:
+2
-1
@@ -6,7 +6,8 @@
|
|||||||
"firebase-debug.log",
|
"firebase-debug.log",
|
||||||
"firebase-debug.*.log"
|
"firebase-debug.*.log"
|
||||||
],
|
],
|
||||||
"predeploy": []
|
"predeploy": [],
|
||||||
|
"source": "functions"
|
||||||
},
|
},
|
||||||
"emulators": {
|
"emulators": {
|
||||||
"functions": {
|
"functions": {
|
||||||
|
|||||||
Generated
-31
@@ -7358,30 +7358,6 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/winston": {
|
|
||||||
"version": "3.18.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz",
|
|
||||||
"integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@colors/colors": "^1.6.0",
|
|
||||||
"@dabh/diagnostics": "^2.0.8",
|
|
||||||
"async": "^3.2.3",
|
|
||||||
"is-stream": "^2.0.0",
|
|
||||||
"logform": "^2.7.0",
|
|
||||||
"one-time": "^1.0.0",
|
|
||||||
"readable-stream": "^3.4.0",
|
|
||||||
"safe-stable-stringify": "^2.3.1",
|
|
||||||
"stack-trace": "0.0.x",
|
|
||||||
"triple-beam": "^1.3.0",
|
|
||||||
"winston-transport": "^4.9.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/winston-transport": {
|
"node_modules/winston-transport": {
|
||||||
"version": "4.9.0",
|
"version": "4.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
|
||||||
@@ -7397,13 +7373,6 @@
|
|||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/winston/node_modules/async": {
|
|
||||||
"version": "3.2.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
|
||||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ if (!firebase?.apps?.filter(({ name_ }) => name_ === "[DEFAULT]").length) {
|
|||||||
persistence: getReactNativePersistence(AsyncStorage),
|
persistence: getReactNativePersistence(AsyncStorage),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (__DEV__) {
|
// if (__DEV__) {
|
||||||
firebase.functions().useEmulator("localhost", 5001);
|
// firebase.functions().useEmulator("localhost", 5001);
|
||||||
}
|
// configureFunctionsEmulator(defaultFunctions, "us-central1");
|
||||||
|
// }
|
||||||
const defaultFunctions = firebase.functions();
|
const defaultFunctions = firebase.functions();
|
||||||
functionsInstances["us-central1"] = defaultFunctions;
|
functionsInstances["us-central1"] = defaultFunctions;
|
||||||
configureFunctionsEmulator(defaultFunctions, "us-central1");
|
|
||||||
console.log("Firebase init");
|
console.log("Firebase init");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ const HitParade = () => {
|
|||||||
[topSongs]
|
[topSongs]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [playbacksCol1, playbacksCol2] = useMemo(() => {
|
const playbackList = useMemo(
|
||||||
const arr = Array.isArray(topPlaybacks) ? topPlaybacks : [];
|
() => (Array.isArray(topPlaybacks) ? topPlaybacks : []),
|
||||||
return [arr.slice(0, 5), arr.slice(5)];
|
[topPlaybacks]
|
||||||
}, [topPlaybacks]);
|
);
|
||||||
|
|
||||||
const resolvePlaybackThumbnail = useCallback((project) => {
|
const resolvePlaybackThumbnail = useCallback((project) => {
|
||||||
const candidates = [
|
const candidates = [
|
||||||
@@ -110,7 +110,7 @@ const HitParade = () => {
|
|||||||
gap: 10,
|
gap: 10,
|
||||||
paddingBottom: responsiveHeight(20),
|
paddingBottom: responsiveHeight(20),
|
||||||
}}
|
}}
|
||||||
data={Array.isArray(topPlaybacks) ? topPlaybacks : []}
|
data={playbackList}
|
||||||
keyExtractor={(item) => item.id}
|
keyExtractor={(item) => item.id}
|
||||||
renderItem={({ item, index }) => (
|
renderItem={({ item, index }) => (
|
||||||
<PlaybacksCard
|
<PlaybacksCard
|
||||||
@@ -146,34 +146,17 @@ const HitParade = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderPlaybackColumns = () => (
|
const renderPlaybackColumns = () => (
|
||||||
<View style={{ flexDirection: "row", gap: 10, alignItems: "flex-start" }}>
|
<View style={{ gap: 10 }}>
|
||||||
<View style={{ flex: 1, gap: 10 }}>
|
{playbackList.map((item, idx) => (
|
||||||
{playbacksCol1.map((item, idx) => (
|
<PlaybacksCard
|
||||||
<PlaybacksCard
|
key={item.id}
|
||||||
key={item.id}
|
rank={idx + 1}
|
||||||
rank={idx + 1}
|
title={item?.title || "Sans titre"}
|
||||||
title={item?.title || "Sans titre"}
|
artist={item?.userName}
|
||||||
artist={item?.userName}
|
thumbnailUrl={resolvePlaybackThumbnail(item)}
|
||||||
thumbnailUrl={resolvePlaybackThumbnail(item)}
|
onPress={() => navigate(Routes.Playbacks, { projectId: item.id })}
|
||||||
onPress={() => navigate(Routes.Playbacks, { projectId: item.id })}
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{playbacksCol2.length > 0 && (
|
|
||||||
<View style={{ flex: 1, gap: 10 }}>
|
|
||||||
{playbacksCol2.map((item, idx) => (
|
|
||||||
<PlaybacksCard
|
|
||||||
key={item.id}
|
|
||||||
rank={5 + idx + 1}
|
|
||||||
title={item?.title || "Sans titre"}
|
|
||||||
artist={item?.userName}
|
|
||||||
thumbnailUrl={resolvePlaybackThumbnail(item)}
|
|
||||||
onPress={() => navigate(Routes.Playbacks, { projectId: item.id })}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ import {
|
|||||||
import { background, icons } from "../assets";
|
import { background, icons } from "../assets";
|
||||||
import FullscreenIntroVideo from "../components/FullscreenIntroVideo";
|
import FullscreenIntroVideo from "../components/FullscreenIntroVideo";
|
||||||
import GradientButton from "../components/GradientButton";
|
import GradientButton from "../components/GradientButton";
|
||||||
import { LinearGradient } from "../components/LinearGradient/LinearGradient";
|
|
||||||
import { videosRef } from "../config/firebase";
|
import { videosRef } from "../config/firebase";
|
||||||
import useDataFromRef from "../hooks/useDataFromRef";
|
import useDataFromRef from "../hooks/useDataFromRef";
|
||||||
import Page from "../layouts/Page";
|
import Page from "../layouts/Page";
|
||||||
import { Routes } from "../navigation/Routes";
|
import { Routes } from "../navigation";
|
||||||
import { useUser } from "../providers/UserDataProvider";
|
import { useUser } from "../providers/UserDataProvider";
|
||||||
import { Palette } from "../styles";
|
import { Palette } from "../styles";
|
||||||
import { FONT_FAMILY } from "../styles/Fonts";
|
import { FONT_FAMILY } from "../styles/Fonts";
|
||||||
@@ -154,7 +153,9 @@ export default function LandingPage() {
|
|||||||
title="Présentation de Musicland"
|
title="Présentation de Musicland"
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
setVideoUrl(
|
setVideoUrl(
|
||||||
Platform.OS === "web" ? video?.landingWeb : video?.landing
|
Platform.OS === "web"
|
||||||
|
? video?.landingWeb
|
||||||
|
: video?.landing,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
containerStyle={styles.actionButton}
|
containerStyle={styles.actionButton}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { background } from "../../assets";
|
|||||||
import FullscreenIntroVideo from "../../components/FullscreenIntroVideo";
|
import FullscreenIntroVideo from "../../components/FullscreenIntroVideo";
|
||||||
import BorderGradientButton from "../../components/BorderGradientButton";
|
import BorderGradientButton from "../../components/BorderGradientButton";
|
||||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||||
|
import AppCheckbox from "../../components/AppCheckbox";
|
||||||
import firebase, {
|
import firebase, {
|
||||||
projectsRef,
|
projectsRef,
|
||||||
serverTimestamp,
|
serverTimestamp,
|
||||||
@@ -143,6 +144,7 @@ const PlaybackDownload = ({ route }) => {
|
|||||||
project?.playbackUrl || null,
|
project?.playbackUrl || null,
|
||||||
);
|
);
|
||||||
const [isPublishing, setIsPublishing] = useState(false);
|
const [isPublishing, setIsPublishing] = useState(false);
|
||||||
|
const [hasAcceptedPublication, setHasAcceptedPublication] = useState(true);
|
||||||
const hasShownAfterPlaybackRef = useRef(false);
|
const hasShownAfterPlaybackRef = useRef(false);
|
||||||
|
|
||||||
const { data: video } = useDataFromRef({
|
const { data: video } = useDataFromRef({
|
||||||
@@ -284,6 +286,13 @@ const PlaybackDownload = ({ route }) => {
|
|||||||
|
|
||||||
const handlePublish = async () => {
|
const handlePublish = async () => {
|
||||||
if (isPublishing) return;
|
if (isPublishing) return;
|
||||||
|
if (!hasAcceptedPublication) {
|
||||||
|
setTooltip({
|
||||||
|
type: "error",
|
||||||
|
text: "Confirme la diffusion sur Musicland et YouTube avant de publier",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!project?.id) {
|
if (!project?.id) {
|
||||||
if (action === "playback") {
|
if (action === "playback") {
|
||||||
setTooltip({
|
setTooltip({
|
||||||
@@ -429,6 +438,19 @@ const PlaybackDownload = ({ route }) => {
|
|||||||
|
|
||||||
<ClubAdvantagesCard style={styles.clubCardSpacing} />
|
<ClubAdvantagesCard style={styles.clubCardSpacing} />
|
||||||
|
|
||||||
|
<View style={styles.publishConsentContainer}>
|
||||||
|
<AppCheckbox
|
||||||
|
selected={hasAcceptedPublication}
|
||||||
|
onPress={() =>
|
||||||
|
setHasAcceptedPublication((prevState) => !prevState)
|
||||||
|
}
|
||||||
|
label="J'accepte la diffusion de mon playback sur Musicland et YouTube."
|
||||||
|
/>
|
||||||
|
<Text style={styles.publishConsentDescription}>
|
||||||
|
Cette confirmation est requise avant de lancer la publication.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
<BorderGradientButton
|
<BorderGradientButton
|
||||||
title={continueLabel}
|
title={continueLabel}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -438,6 +460,7 @@ const PlaybackDownload = ({ route }) => {
|
|||||||
setShowConfirmModal(true);
|
setShowConfirmModal(true);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
disabled={isPublishing || !hasAcceptedPublication}
|
||||||
containerStyle={styles.continueButton}
|
containerStyle={styles.continueButton}
|
||||||
/>
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -491,6 +514,16 @@ const styles = StyleSheet.create({
|
|||||||
clubCardSpacing: {
|
clubCardSpacing: {
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
},
|
},
|
||||||
|
publishConsentContainer: {
|
||||||
|
gap: 6,
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
publishConsentDescription: {
|
||||||
|
fontFamily: FONT_FAMILY.InterRegular,
|
||||||
|
fontSize: 13,
|
||||||
|
color: Palette.white,
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
continueButton: {
|
continueButton: {
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useRoute } from "@react-navigation/native";
|
import { useRoute } from "@react-navigation/native";
|
||||||
import React, { useCallback, useMemo, useState } from "react";
|
import React, { useCallback, useMemo, useState } from "react";
|
||||||
import { Linking, Text, View } from "react-native";
|
import { Linking, StyleSheet, Text, View } from "react-native";
|
||||||
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
|
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
|
||||||
import { background } from "../../assets";
|
import { background } from "../../assets";
|
||||||
|
import AppCheckbox from "../../components/AppCheckbox";
|
||||||
import BorderGradientButton from "../../components/BorderGradientButton";
|
import BorderGradientButton from "../../components/BorderGradientButton";
|
||||||
import GradientButton from "../../components/GradientButton";
|
import GradientButton from "../../components/GradientButton";
|
||||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||||
@@ -37,6 +38,7 @@ const PublishYoutube = () => {
|
|||||||
const projectId = project?.id || routeProjectId || null;
|
const projectId = project?.id || routeProjectId || null;
|
||||||
|
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [hasAcceptedPublication, setHasAcceptedPublication] = useState(true);
|
||||||
const youtubeUrl = project?.youtubeUrl ?? null;
|
const youtubeUrl = project?.youtubeUrl ?? null;
|
||||||
const youtubeStatus = project?.youtubeStatus ?? null;
|
const youtubeStatus = project?.youtubeStatus ?? null;
|
||||||
const youtubeError = project?.youtubeError ?? null;
|
const youtubeError = project?.youtubeError ?? null;
|
||||||
@@ -50,7 +52,11 @@ const PublishYoutube = () => {
|
|||||||
[youtubeStatus]
|
[youtubeStatus]
|
||||||
);
|
);
|
||||||
const publishDisabled =
|
const publishDisabled =
|
||||||
saving || isPublishing || !playbackReady || !projectId;
|
saving ||
|
||||||
|
isPublishing ||
|
||||||
|
!playbackReady ||
|
||||||
|
!projectId ||
|
||||||
|
!hasAcceptedPublication;
|
||||||
const publishButtonLabel = isPublishing
|
const publishButtonLabel = isPublishing
|
||||||
? "Publication en cours..."
|
? "Publication en cours..."
|
||||||
: hasYoutubePublication
|
: hasYoutubePublication
|
||||||
@@ -128,6 +134,13 @@ const PublishYoutube = () => {
|
|||||||
}, [playbackReady, projectId, setTooltip]);
|
}, [playbackReady, projectId, setTooltip]);
|
||||||
|
|
||||||
const handleConfirmPublish = useCallback(() => {
|
const handleConfirmPublish = useCallback(() => {
|
||||||
|
if (!hasAcceptedPublication) {
|
||||||
|
setTooltip?.({
|
||||||
|
type: "error",
|
||||||
|
text: "Confirme la diffusion sur Musicland et YouTube avant de publier",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (publishDisabled) {
|
if (publishDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -144,7 +157,13 @@ const PublishYoutube = () => {
|
|||||||
onPress: handleMarkPublished,
|
onPress: handleMarkPublished,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}, [handleMarkPublished, hasYoutubePublication, publishDisabled]);
|
}, [
|
||||||
|
handleMarkPublished,
|
||||||
|
hasAcceptedPublication,
|
||||||
|
hasYoutubePublication,
|
||||||
|
publishDisabled,
|
||||||
|
setTooltip,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleResetPublication = useCallback(async () => {
|
const handleResetPublication = useCallback(async () => {
|
||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
@@ -246,6 +265,18 @@ const PublishYoutube = () => {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={{ gap: 14 }}>
|
<View style={{ gap: 14 }}>
|
||||||
|
<View style={styles.consentContainer}>
|
||||||
|
<AppCheckbox
|
||||||
|
selected={hasAcceptedPublication}
|
||||||
|
onPress={() =>
|
||||||
|
setHasAcceptedPublication((prevState) => !prevState)
|
||||||
|
}
|
||||||
|
label="J'accepte la diffusion de mon contenu sur Musicland et YouTube."
|
||||||
|
/>
|
||||||
|
<Text style={styles.consentDescription}>
|
||||||
|
Cette confirmation est requise avant toute mise en ligne.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
<GradientButton
|
<GradientButton
|
||||||
title={publishButtonLabel}
|
title={publishButtonLabel}
|
||||||
onPress={handleConfirmPublish}
|
onPress={handleConfirmPublish}
|
||||||
@@ -272,4 +303,16 @@ const PublishYoutube = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
consentContainer: {
|
||||||
|
gap: 6,
|
||||||
|
},
|
||||||
|
consentDescription: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: FONT_FAMILY.InterRegular,
|
||||||
|
color: Palette.white,
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default PublishYoutube;
|
export default PublishYoutube;
|
||||||
|
|||||||
@@ -70,9 +70,13 @@ const getIntervalLabel = (recurring) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function SubscriptionCard({ plan, selected, onSelect }) {
|
function SubscriptionCard({ plan, selected, onSelect }) {
|
||||||
|
const planKey = getPlanKeyForBadge(plan);
|
||||||
const planName =
|
const planName =
|
||||||
plan?.product?.name || plan?.nickname || plan?.priceId || "Abonnement";
|
(planKey && PLAN_DISPLAY_NAME_BY_KEY[planKey]) ||
|
||||||
const planDescription = plan?.product?.description || "";
|
plan?.product?.name ||
|
||||||
|
plan?.nickname ||
|
||||||
|
plan?.priceId ||
|
||||||
|
"Abonnement";
|
||||||
const formattedPrice = formatCurrency(plan?.unitAmount, plan?.currency);
|
const formattedPrice = formatCurrency(plan?.unitAmount, plan?.currency);
|
||||||
const intervalLabel = getIntervalLabel(plan?.recurring);
|
const intervalLabel = getIntervalLabel(plan?.recurring);
|
||||||
const coinsPerMonth =
|
const coinsPerMonth =
|
||||||
@@ -80,21 +84,10 @@ function SubscriptionCard({ plan, selected, onSelect }) {
|
|||||||
Number.isFinite(plan.coinsPerMonth)
|
Number.isFinite(plan.coinsPerMonth)
|
||||||
? Math.round(plan.coinsPerMonth)
|
? Math.round(plan.coinsPerMonth)
|
||||||
: null;
|
: null;
|
||||||
const songsPerMonth =
|
const planBadgeKey = planKey;
|
||||||
coinsPerMonth !== null
|
|
||||||
? Math.floor(coinsPerMonth / CREDITS_PER_MUSIC)
|
|
||||||
: null;
|
|
||||||
const songsPerMonthLabel =
|
|
||||||
songsPerMonth === null
|
|
||||||
? null
|
|
||||||
: songsPerMonth >= 1
|
|
||||||
? `≈ ${songsPerMonth} ${
|
|
||||||
songsPerMonth > 1 ? "musiques" : "musique"
|
|
||||||
} / mois`
|
|
||||||
: "Moins d'une musique / mois";
|
|
||||||
const planBadgeKey = getPlanKeyForBadge(plan);
|
|
||||||
const planBadgeSource =
|
const planBadgeSource =
|
||||||
planBadgeKey && subBadges[planBadgeKey] ? subBadges[planBadgeKey] : null;
|
planBadgeKey && subBadges[planBadgeKey] ? subBadges[planBadgeKey] : null;
|
||||||
|
const planFeatures = SUBSCRIPTION_FEATURES;
|
||||||
const handleSelect = React.useCallback(() => {
|
const handleSelect = React.useCallback(() => {
|
||||||
if (typeof onSelect === "function" && plan?.priceId) {
|
if (typeof onSelect === "function" && plan?.priceId) {
|
||||||
onSelect(plan.priceId);
|
onSelect(plan.priceId);
|
||||||
@@ -138,14 +131,16 @@ function SubscriptionCard({ plan, selected, onSelect }) {
|
|||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{formattedPrice ? (
|
{formattedPrice || coinsPerMonth !== null ? (
|
||||||
<View style={styles.priceBlock}>
|
<View style={styles.priceBlock}>
|
||||||
<View style={styles.priceRow}>
|
{formattedPrice ? (
|
||||||
<Text style={styles.priceValue}>{formattedPrice}</Text>
|
<View style={styles.priceRow}>
|
||||||
{intervalLabel ? (
|
<Text style={styles.priceValue}>{formattedPrice}</Text>
|
||||||
<Text style={styles.period}>{intervalLabel}</Text>
|
{intervalLabel ? (
|
||||||
) : null}
|
<Text style={styles.period}>{intervalLabel}</Text>
|
||||||
</View>
|
) : null}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
{coinsPerMonth !== null ? (
|
{coinsPerMonth !== null ? (
|
||||||
<View style={styles.coinsPerMonthRow}>
|
<View style={styles.coinsPerMonthRow}>
|
||||||
<CreditAmount
|
<CreditAmount
|
||||||
@@ -153,19 +148,22 @@ function SubscriptionCard({ plan, selected, onSelect }) {
|
|||||||
showPlus
|
showPlus
|
||||||
textStyle={styles.coinsPerMonth}
|
textStyle={styles.coinsPerMonth}
|
||||||
iconSize={16}
|
iconSize={16}
|
||||||
accessibilityLabel={`+${coinsPerMonth} pièces par mois`}
|
accessibilityLabel={`+${coinsPerMonth} crédits par mois`}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.coinsPerMonthSuffix}>/ mois</Text>
|
<Text style={styles.coinsPerMonthSuffix}>/ mois</Text>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
{songsPerMonthLabel ? (
|
|
||||||
<Text style={styles.songsPerMonth}>{songsPerMonthLabel}</Text>
|
|
||||||
) : null}
|
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{planDescription ? (
|
{planFeatures?.length ? (
|
||||||
<Text style={styles.description}>{planDescription}</Text>
|
<View style={styles.features}>
|
||||||
|
{planFeatures.map((feature) => (
|
||||||
|
<Text key={feature} style={styles.featureText}>
|
||||||
|
{feature}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</BlurView>
|
</BlurView>
|
||||||
@@ -174,11 +172,6 @@ function SubscriptionCard({ plan, selected, onSelect }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PRICE_PRIORITY_BY_PERIOD = {
|
const PRICE_PRIORITY_BY_PERIOD = {
|
||||||
monthly: [
|
|
||||||
"price_1SPgitCzf2o5bDRdbnhLFx6f", // Starter
|
|
||||||
"price_1SPgjCCzf2o5bDRdr08Xzp8u", // Pro
|
|
||||||
"price_1SPgjaCzf2o5bDRdd9Xo2u26", // Premium
|
|
||||||
],
|
|
||||||
annual: [
|
annual: [
|
||||||
"price_1SPgkDCzf2o5bDRdNGLVNeQ3", // Starter
|
"price_1SPgkDCzf2o5bDRdNGLVNeQ3", // Starter
|
||||||
"price_1SPgkXCzf2o5bDRdejBVxEBY", // Pro
|
"price_1SPgkXCzf2o5bDRdejBVxEBY", // Pro
|
||||||
@@ -187,16 +180,9 @@ const PRICE_PRIORITY_BY_PERIOD = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PACK_PRICE_ID_BY_PERIOD = {
|
const PACK_PRICE_ID_BY_PERIOD = {
|
||||||
monthly: {
|
|
||||||
starter: "price_1SPgitCzf2o5bDRdbnhLFx6f",
|
|
||||||
pro: "price_1SPgjCCzf2o5bDRdr08Xzp8u",
|
|
||||||
prop: "price_1SPgjCCzf2o5bDRdr08Xzp8u",
|
|
||||||
premium: "price_1SPgjaCzf2o5bDRdd9Xo2u26",
|
|
||||||
},
|
|
||||||
annual: {
|
annual: {
|
||||||
starter: "price_1SPgkDCzf2o5bDRdNGLVNeQ3",
|
starter: "price_1SPgkDCzf2o5bDRdNGLVNeQ3",
|
||||||
pro: "price_1SPgkXCzf2o5bDRdejBVxEBY",
|
pro: "price_1SPgkXCzf2o5bDRdejBVxEBY",
|
||||||
prop: "price_1SPgkXCzf2o5bDRdejBVxEBY",
|
|
||||||
premium: "price_1SPgkqCzf2o5bDRdIcUwTDrm",
|
premium: "price_1SPgkqCzf2o5bDRdIcUwTDrm",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -208,7 +194,7 @@ Object.values(PACK_PRICE_ID_BY_PERIOD).forEach((mapping) => {
|
|||||||
if (!priceId) {
|
if (!priceId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PRICE_ID_TO_PLAN_KEY[priceId] = planKey === "prop" ? "pro" : planKey;
|
PRICE_ID_TO_PLAN_KEY[priceId] = planKey;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -216,10 +202,22 @@ const PLAN_FALLBACK_ORDER = ["starter", "pro", "premium"];
|
|||||||
|
|
||||||
const PLAN_SYNONYMS = {
|
const PLAN_SYNONYMS = {
|
||||||
starter: ["starter"],
|
starter: ["starter"],
|
||||||
pro: ["pro", "prop"],
|
pro: ["pro"],
|
||||||
premium: ["premium"],
|
premium: ["premium"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PLAN_DISPLAY_NAME_BY_KEY = {
|
||||||
|
starter: "Play Backer Blue",
|
||||||
|
pro: "Play Backer Silver",
|
||||||
|
premium: "Play Backer Gold",
|
||||||
|
};
|
||||||
|
|
||||||
|
const SUBSCRIPTION_FEATURES = [
|
||||||
|
"*Eligible au Hit parade Vidéo",
|
||||||
|
"Privilège Membre : Récompense doublée",
|
||||||
|
"Eligible au hit parade Artiste trimestriel et annuel",
|
||||||
|
];
|
||||||
|
|
||||||
const getPlanKeyForBadge = (plan) => {
|
const getPlanKeyForBadge = (plan) => {
|
||||||
if (!plan || typeof plan !== "object") {
|
if (!plan || typeof plan !== "object") {
|
||||||
return null;
|
return null;
|
||||||
@@ -296,7 +294,6 @@ const PAGE_BACKGROUND_COLOR = "#303438";
|
|||||||
const SUBSCRIPTION_DISCLAIMER =
|
const SUBSCRIPTION_DISCLAIMER =
|
||||||
"Résiliable en un clic à tout moment. Les prix sont indiqués TTC. Contacte-nous pour des besoins spécifiques (facturation annuelle, volume, offres éducation).";
|
"Résiliable en un clic à tout moment. Les prix sont indiqués TTC. Contacte-nous pour des besoins spécifiques (facturation annuelle, volume, offres éducation).";
|
||||||
const CARD_MIN_HEIGHT = isWeb ? 320 : 240;
|
const CARD_MIN_HEIGHT = isWeb ? 320 : 240;
|
||||||
const CREDITS_PER_MUSIC = 8;
|
|
||||||
|
|
||||||
export default function Subscriptions() {
|
export default function Subscriptions() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -456,8 +453,6 @@ export default function Subscriptions() {
|
|||||||
);
|
);
|
||||||
}, [combinedErrorMessage]);
|
}, [combinedErrorMessage]);
|
||||||
|
|
||||||
const renderSegmentedControl = React.useCallback(() => null, []);
|
|
||||||
|
|
||||||
const renderMobilePlanItem = React.useCallback(
|
const renderMobilePlanItem = React.useCallback(
|
||||||
({ item }) => (
|
({ item }) => (
|
||||||
<View style={styles.mobileCard}>
|
<View style={styles.mobileCard}>
|
||||||
@@ -502,10 +497,9 @@ export default function Subscriptions() {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.mobileStickyHeader}>
|
<View style={styles.mobileStickyHeader}>
|
||||||
{renderHeaderSection()}
|
{renderHeaderSection()}
|
||||||
{renderSegmentedControl()}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}, [renderHeaderSection, renderSegmentedControl]);
|
}, [renderHeaderSection]);
|
||||||
|
|
||||||
const renderMobileBottomActions = React.useCallback(() => {
|
const renderMobileBottomActions = React.useCallback(() => {
|
||||||
return (
|
return (
|
||||||
@@ -606,8 +600,6 @@ export default function Subscriptions() {
|
|||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{renderSegmentedControl()}
|
|
||||||
|
|
||||||
<View style={[styles.packs, isWeb && styles.packsWeb]}>
|
<View style={[styles.packs, isWeb && styles.packsWeb]}>
|
||||||
{currentPlans.map((plan) => (
|
{currentPlans.map((plan) => (
|
||||||
<SubscriptionCard
|
<SubscriptionCard
|
||||||
@@ -768,32 +760,6 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: "stretch",
|
alignItems: "stretch",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
segmentedControl: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignSelf: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
padding: 4,
|
|
||||||
borderRadius: 999,
|
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.08)",
|
|
||||||
marginTop: isWeb ? 12 : 8,
|
|
||||||
marginBottom: isWeb ? 8 : 4,
|
|
||||||
},
|
|
||||||
segmentButton: {
|
|
||||||
paddingVertical: 8,
|
|
||||||
paddingHorizontal: 18,
|
|
||||||
borderRadius: 999,
|
|
||||||
},
|
|
||||||
segmentButtonActive: {
|
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.18)",
|
|
||||||
},
|
|
||||||
segmentLabel: {
|
|
||||||
fontFamily: FONT_FAMILY.InterMedium,
|
|
||||||
fontSize: 14,
|
|
||||||
color: "rgba(255, 255, 255, 0.7)",
|
|
||||||
},
|
|
||||||
segmentLabelActive: {
|
|
||||||
color: Palette.white,
|
|
||||||
},
|
|
||||||
actions: {
|
actions: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -888,10 +854,15 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: Palette.primary,
|
color: Palette.primary,
|
||||||
},
|
},
|
||||||
songsPerMonth: {
|
features: {
|
||||||
fontFamily: FONT_FAMILY.InterRegular,
|
marginTop: isWeb ? 12 : 8,
|
||||||
fontSize: 12,
|
gap: 6,
|
||||||
color: "rgba(255, 255, 255, 0.75)",
|
},
|
||||||
|
featureText: {
|
||||||
|
fontFamily: FONT_FAMILY.InterMedium,
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 18,
|
||||||
|
color: "rgba(255, 255, 255, 0.85)",
|
||||||
},
|
},
|
||||||
priceValue: {
|
priceValue: {
|
||||||
fontFamily: FONT_FAMILY.InterBold,
|
fontFamily: FONT_FAMILY.InterBold,
|
||||||
@@ -903,12 +874,6 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: "rgba(255, 255, 255, 0.7)",
|
color: "rgba(255, 255, 255, 0.7)",
|
||||||
},
|
},
|
||||||
description: {
|
|
||||||
fontFamily: FONT_FAMILY.InterRegular,
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 22,
|
|
||||||
color: Palette.white,
|
|
||||||
},
|
|
||||||
badge: {
|
badge: {
|
||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
@@ -939,10 +904,6 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: PAGE_BACKGROUND_COLOR,
|
backgroundColor: PAGE_BACKGROUND_COLOR,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
segmentedControlMobile: {
|
|
||||||
alignSelf: "center",
|
|
||||||
marginBottom: 0,
|
|
||||||
},
|
|
||||||
mobileList: {
|
mobileList: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
|||||||
Reference in New Issue
Block a user