diff --git a/functions/src/subscription/checkout.js b/functions/src/subscription/checkout.js
index 64160c9..b13e0f6 100644
--- a/functions/src/subscription/checkout.js
+++ b/functions/src/subscription/checkout.js
@@ -231,6 +231,85 @@ const createSongDownloadCheckoutSession = onCall({ region: REGION }, async (requ
}
})
+const createPlaybackDownloadCheckoutSession = onCall({ region: REGION }, async (request) => {
+ try {
+ const uid = request?.auth?.uid
+ if (!uid) {
+ throw new HttpsError('unauthenticated', 'Connecte-toi pour télécharger ton playback.')
+ }
+
+ const rawProjectId = request?.data?.projectId
+ const projectId = typeof rawProjectId === 'string' ? rawProjectId.trim() : ''
+ if (!projectId) {
+ throw new HttpsError('invalid-argument', 'Un identifiant de projet est requis.')
+ }
+
+ const stripe = getStripeClient()
+
+ const { lineItems } = await buildCheckoutLineItems(
+ [
+ {
+ label: 'Téléchargement playback',
+ unitAmount: 199,
+ currency: 'eur',
+ quantity: 1,
+ isRenewable: false,
+ },
+ ],
+ { stripe }
+ )
+
+ const uiMode = resolveCheckoutUiMode(request)
+ const shouldProvideReturnUrls = uiMode !== 'embedded'
+ const { successUrl, cancelUrl } = shouldProvideReturnUrls
+ ? getReturnUrls(request?.data?.returnUrls)
+ : { successUrl: null, cancelUrl: null }
+
+ const { customerId } = await ensureStripeCustomer({
+ uid,
+ stripe,
+ refsList,
+ createIfMissing: true,
+ })
+
+ if (!customerId) {
+ throw new HttpsError(
+ 'failed-precondition',
+ 'Impossible de retrouver le client Stripe associé.'
+ )
+ }
+
+ const session = await stripe.checkout.sessions.create(
+ withCheckoutNavigationParams(
+ {
+ mode: 'payment',
+ customer: customerId,
+ line_items: lineItems,
+ allow_promotion_codes: false,
+ metadata: {
+ firebaseUID: uid,
+ purchaseType: 'PLAYBACK_DOWNLOAD',
+ projectId,
+ },
+ },
+ {
+ uiMode,
+ successUrl,
+ cancelUrl,
+ }
+ )
+ )
+
+ return formatCheckoutSessionResponse(session)
+ } catch (error) {
+ console.error('[subscription-createPlaybackDownloadCheckoutSession] error', error)
+ if (error instanceof HttpsError) {
+ throw error
+ }
+ throw mapStripeErrorToHttps(error, "Impossible de créer la session d'achat.")
+ }
+})
+
const createCoinPackCheckoutSession = onCall({ region: REGION }, async (request) => {
try {
const uid = request?.auth?.uid
@@ -352,6 +431,7 @@ const createCoinPackCheckoutSession = onCall({ region: REGION }, async (request)
module.exports = {
createSubscriptionCheckoutSession,
createSongDownloadCheckoutSession,
+ createPlaybackDownloadCheckoutSession,
createCoinPackCheckoutSession,
resolveCheckoutUiMode,
}
diff --git a/functions/src/subscription/index.js b/functions/src/subscription/index.js
index 94d987c..caeea93 100644
--- a/functions/src/subscription/index.js
+++ b/functions/src/subscription/index.js
@@ -2,6 +2,7 @@ const { listSubscriptionPlans, listCoinPacks } = require('./catalog')
const {
createSubscriptionCheckoutSession,
createSongDownloadCheckoutSession,
+ createPlaybackDownloadCheckoutSession,
createCoinPackCheckoutSession,
} = require('./checkout')
const { cancelActiveSubscription, getActiveSubscription } = require('./management')
@@ -12,6 +13,7 @@ module.exports = {
listSubscriptionPlans,
createSubscriptionCheckoutSession,
createSongDownloadCheckoutSession,
+ createPlaybackDownloadCheckoutSession,
cancelActiveSubscription,
getActiveSubscription,
listCoinPacks,
diff --git a/functions/src/subscription/management.js b/functions/src/subscription/management.js
index aa0bcf5..b51f749 100644
--- a/functions/src/subscription/management.js
+++ b/functions/src/subscription/management.js
@@ -137,7 +137,7 @@ const getActiveSubscription = onCall({ region: REGION }, async (request) => {
if (subscriptionId) {
const subscription = await stripe.subscriptions.retrieve(subscriptionId, {
- expand: ['items.data.price.product'],
+ expand: ['items.data.price'],
})
if (subscription) {
return {
@@ -152,7 +152,7 @@ const getActiveSubscription = onCall({ region: REGION }, async (request) => {
customer: customerId,
status: 'all',
limit: 5,
- expand: ['data.items.data.price.product'],
+ expand: ['data.items.data.price'],
})
const [subscription] = response?.data || []
if (subscription && ACTIVE_SUBSCRIPTION_STATUSES.has(subscription.status)) {
diff --git a/functions/src/subscription/webhooks.js b/functions/src/subscription/webhooks.js
index 21eb0ed..588a39d 100644
--- a/functions/src/subscription/webhooks.js
+++ b/functions/src/subscription/webhooks.js
@@ -235,6 +235,91 @@ const handleCheckoutSessionCompleted = async (session, event, { stripe } = {}) =
}
}
}
+
+ if (
+ session.mode === 'payment' &&
+ (session.payment_status === 'paid' || session.payment_status === 'no_payment_required') &&
+ session.metadata?.purchaseType === 'PLAYBACK_DOWNLOAD'
+ ) {
+ const rawProjectId = session.metadata?.projectId
+ const projectId = typeof rawProjectId === 'string' ? rawProjectId.trim() : ''
+
+ if (projectId && paymentDocRef) {
+ let paymentSnapshot = null
+ try {
+ paymentSnapshot = await paymentDocRef.get()
+ } catch (error) {
+ console.warn(
+ '[subscription-handleCheckoutSessionCompleted] Unable to read payment doc',
+ session.id,
+ error?.message || error
+ )
+ }
+
+ const alreadyGranted = Boolean(
+ paymentSnapshot?.exists && paymentSnapshot.data()?.playbackDownloadGrantedAt
+ )
+
+ if (!alreadyGranted) {
+ const projectRef = refsList?.projects?.doc(projectId) || null
+ let shouldGrant = true
+
+ if (projectRef) {
+ try {
+ const projectSnapshot = await projectRef.get()
+ if (!projectSnapshot.exists) {
+ shouldGrant = false
+ } else {
+ const projectData = projectSnapshot.data() || {}
+ const ownerId =
+ typeof projectData?.userId === 'string' ? projectData.userId.trim() : ''
+ const targetUserId = uid || firebaseUid || userRef?.id || null
+ if (ownerId && targetUserId && ownerId !== targetUserId) {
+ console.warn(
+ '[subscription-handleCheckoutSessionCompleted] Project owner mismatch',
+ projectId
+ )
+ shouldGrant = false
+ }
+ }
+ } catch (error) {
+ console.warn(
+ '[subscription-handleCheckoutSessionCompleted] Unable to read project',
+ projectId,
+ error?.message || error
+ )
+ }
+ } else {
+ shouldGrant = false
+ }
+
+ if (shouldGrant && projectRef) {
+ await projectRef.set(
+ {
+ playbackDownloadPurchase: {
+ status: 'paid',
+ paymentId: session.id || null,
+ paymentIntentId:
+ typeof session.payment_intent === 'string' ? session.payment_intent : null,
+ amount: session.amount_total ?? null,
+ currency: session.currency || null,
+ paidAt: getServerTimestamp(),
+ },
+ },
+ { merge: true }
+ )
+
+ await paymentDocRef.set(
+ {
+ playbackDownloadGrantedAt: getServerTimestamp(),
+ playbackDownloadProjectId: projectId,
+ },
+ { merge: true }
+ )
+ }
+ }
+ }
+ }
}
const handleCustomerSubscriptionEvent = async (subscription, event, { stripe } = {}) => {
diff --git a/src/providers/StripeProvider.js b/src/providers/StripeProvider.js
index 0552a34..d6410be 100644
--- a/src/providers/StripeProvider.js
+++ b/src/providers/StripeProvider.js
@@ -242,6 +242,38 @@ const StripeProvider = ({ children }) => {
[canUseEmbeddedCheckout, runCheckoutSession]
)
+ const createPlaybackDownloadCheckout = React.useCallback(
+ async (projectId) => {
+ if (!projectId) {
+ throw new Error('Aucun projet sélectionné.')
+ }
+ if (isWeb && !canUseEmbeddedCheckout) {
+ console.error('[StripeProvider] checkout blocked (missing embedded support)', {
+ hasStripeKey: Boolean(stripePublishableKey),
+ isClientSecretReady: false,
+ })
+ throw new Error(
+ 'Le paiement intégré Stripe est indisponible pour le moment (clé Stripe ou client secret absent).'
+ )
+ }
+ const shouldUseEmbeddedCheckout = canUseEmbeddedCheckout
+ await runCheckoutSession({
+ callableName: 'subscription-createPlaybackDownloadCheckoutSession',
+ payload: {
+ projectId,
+ returnUrls: {
+ successUrl: STRIPE_SUCCESS_URL,
+ cancelUrl: STRIPE_CANCEL_URL,
+ },
+ uiMode: shouldUseEmbeddedCheckout ? 'embedded' : 'hosted',
+ },
+ logTag: 'playback download checkout error',
+ useEmbeddedFlow: shouldUseEmbeddedCheckout,
+ })
+ },
+ [canUseEmbeddedCheckout, runCheckoutSession]
+ )
+
const createCoinPackCheckout = React.useCallback(
async (productId) => {
if (!productId) {
@@ -376,6 +408,7 @@ const StripeProvider = ({ children }) => {
refreshCatalog: fetchStripeCatalog,
createSubscriptionCheckout,
createSongDownloadCheckout,
+ createPlaybackDownloadCheckout,
createCoinPackCheckout,
openEmbeddedCheckout: setClientSecret,
closeEmbeddedCheckout,
@@ -393,6 +426,7 @@ const StripeProvider = ({ children }) => {
fetchStripeCatalog,
createSubscriptionCheckout,
createSongDownloadCheckout,
+ createPlaybackDownloadCheckout,
createCoinPackCheckout,
setClientSecret,
closeEmbeddedCheckout,
diff --git a/src/screens/Production/PlaybackDownload.js b/src/screens/Production/PlaybackDownload.js
index 3cf39bc..fdc0913 100644
--- a/src/screens/Production/PlaybackDownload.js
+++ b/src/screens/Production/PlaybackDownload.js
@@ -1,11 +1,12 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'
+import { Alert, Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'
import { MaterialCommunityIcons } from '@expo/vector-icons'
import useMinuit from 'react-native-minuit/src/hooks/useMinuit.js'
import * as FileSystem from 'expo-file-system'
import { shareAsync } from 'expo-sharing'
import FullscreenIntroVideo from '../../components/FullscreenIntroVideo'
import BorderGradientButton from '../../components/BorderGradientButton'
+import GradientButton from '../../components/GradientButton'
import MusicLandHeader from '../../components/MusicLandHeader'
import AppCheckbox from '../../components/AppCheckbox'
import firebase, { projectsRef, serverTimestamp } from '../../config/firebase'
@@ -14,6 +15,7 @@ import Page from '../../layouts/Page'
import { Routes } from '../../navigation'
import { goBack, navigate } from '../../navigation/NavigationService'
import { useUserData, useUser } from '../../providers/UserDataProvider'
+import { useStripe } from '../../providers/StripeProvider'
import { Palette } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts'
import { size } from '../../styles/Style'
@@ -134,26 +136,44 @@ const uploadSourceRecording = async ({ uri, uid, projectId }) => {
}
const PlaybackDownload = ({ route }) => {
- const { currentUID } = useUserData()
+ const { currentUID, selectedProject } = useUserData()
const { hasActiveSubscription, videos } = useUser() || {}
- const { action: routeAction, uri, project } = route.params || {}
+ const { createPlaybackDownloadCheckout } = useStripe()
+ const { action: routeAction, uri, project: routeProject } = route.params || {}
const action = routeAction || 'playback'
console.log('[PlaybackDownload] route params', {
action,
- projectId: project?.id,
+ projectId: routeProject?.id,
hasUri: Boolean(uri),
currentUID,
})
const { setIsLoading, setTooltip } = useMinuit()
const [isAfterPlaybackVideoVisible, setIsAfterPlaybackVideoVisible] = useState(false)
const [showConfirmModal, setShowConfirmModal] = useState(false)
- const [pendingPlaybackUrl, setPendingPlaybackUrl] = useState(project?.playbackUrl || null)
+ const [pendingPlaybackUrl, setPendingPlaybackUrl] = useState(routeProject?.playbackUrl || null)
const [isPublishing, setIsPublishing] = useState(false)
const [isDownloading, setIsDownloading] = useState(false)
+ const [isCheckoutLaunching, setIsCheckoutLaunching] = useState(false)
+ const [downloadPaymentPending, setDownloadPaymentPending] = useState(false)
const [hasAcceptedPublication, setHasAcceptedPublication] = useState(true)
const hasShownAfterPlaybackRef = useRef(false)
const publishSuccessMessage = action === 'playback' ? 'Playback publié !' : 'Chanson publiée !'
+ const projectForDownload = useMemo(() => {
+ if (routeProject?.id && selectedProject?.id && routeProject.id === selectedProject.id) {
+ return selectedProject
+ }
+ return routeProject || selectedProject || null
+ }, [routeProject, selectedProject])
+
+ const playbackDownloadStatus = projectForDownload?.playbackDownloadPurchase?.status || null
+ const hasPaidPlaybackDownload = playbackDownloadStatus === 'paid'
+ const canDownloadPlayback = hasActiveSubscription || hasPaidPlaybackDownload
+ const downloadLabel = canDownloadPlayback
+ ? 'Télécharger le playback'
+ : 'Acheter le playback pour 1,99€'
+ const showSubscriptionCta = !hasActiveSubscription
+
const afterPlaybackUrl = useMemo(() => {
if (!videos) return null
return videos?.afterPlayback || null
@@ -168,28 +188,30 @@ const PlaybackDownload = ({ route }) => {
}, [action, afterPlaybackUrl])
useEffect(() => {
- if (project?.playbackUrl) {
- setPendingPlaybackUrl(project.playbackUrl)
+ if (projectForDownload?.playbackUrl) {
+ setPendingPlaybackUrl(projectForDownload.playbackUrl)
}
- }, [project?.playbackUrl])
+ }, [projectForDownload?.playbackUrl])
const resolveAudioUrl = useCallback(() => {
- if (typeof project?.songUrl === 'string' && project.songUrl.trim()) {
- return project.songUrl.trim()
+ if (typeof projectForDownload?.songUrl === 'string' && projectForDownload.songUrl.trim()) {
+ return projectForDownload.songUrl.trim()
}
- const urls = Array.isArray(project?.musicUrls) ? project.musicUrls : []
+ const urls = Array.isArray(projectForDownload?.musicUrls) ? projectForDownload.musicUrls : []
if (!urls.length) return null
- const idx = Number.isFinite(Number(project?.songIndex)) ? Number(project.songIndex) : 0
+ const idx = Number.isFinite(Number(projectForDownload?.songIndex))
+ ? Number(projectForDownload.songIndex)
+ : 0
const candidate = urls[idx]
if (typeof candidate === 'string' && candidate.trim()) {
return candidate.trim()
}
return null
- }, [project])
+ }, [projectForDownload])
- const handleDownloadUri = async () => {
+ const handleDownloadUri = useCallback(async () => {
if (isPublishing || isDownloading) return
if (action !== 'playback') {
setTooltip({
@@ -199,18 +221,18 @@ const PlaybackDownload = ({ route }) => {
return
}
- const playbackUrlToDownload = pendingPlaybackUrl || project?.playbackUrl || null
+ const playbackUrlToDownload = pendingPlaybackUrl || projectForDownload?.playbackUrl || null
if (playbackUrlToDownload) {
setIsDownloading(true)
try {
- await triggerDownload(playbackUrlToDownload, project?.title)
+ await triggerDownload(playbackUrlToDownload, projectForDownload?.title)
} finally {
setIsDownloading(false)
}
return
}
- if (!project?.id) {
+ if (!projectForDownload?.id) {
setTooltip({
type: 'error',
text: 'Projet introuvable pour ce playback',
@@ -243,9 +265,8 @@ const PlaybackDownload = ({ route }) => {
return
}
- // Publication du playback
console.log('[PlaybackDownload] handleDownloadUri playback', {
- projectId: project.id,
+ projectId: projectForDownload.id,
uri,
currentUID,
})
@@ -257,17 +278,17 @@ const PlaybackDownload = ({ route }) => {
const { sourcePath, videoUrl } = await uploadSourceRecording({
uri,
uid: currentUID,
- projectId: project.id,
+ projectId: projectForDownload.id,
})
tempSourcePath = sourcePath
const callable = firebase.functions().httpsCallable('upload-mergeVideoAndAudio')
const payload = {
- projectId: project?.id,
+ projectId: projectForDownload?.id,
videoUrl,
audioUrl,
- storagePath: `users/${currentUID}/projects/${project.id}/playback.mp4`,
+ storagePath: `users/${currentUID}/projects/${projectForDownload.id}/playback.mp4`,
}
console.log('[PlaybackDownload] calling upload-mergeVideoAndAudio', payload)
@@ -284,7 +305,7 @@ const PlaybackDownload = ({ route }) => {
type: 'success',
text: 'Playback prêt à télécharger',
})
- await triggerDownload(resultURI, project?.title)
+ await triggerDownload(resultURI, projectForDownload?.title)
if (tempSourcePath) {
try {
await firebase.storage().ref(tempSourcePath).delete()
@@ -322,7 +343,92 @@ const PlaybackDownload = ({ route }) => {
setIsDownloading(false)
setIsLoading(false)
}
- }
+ }, [
+ action,
+ currentUID,
+ isDownloading,
+ isPublishing,
+ pendingPlaybackUrl,
+ projectForDownload,
+ resolveAudioUrl,
+ setIsLoading,
+ setTooltip,
+ uri,
+ ])
+
+ const startPlaybackDownloadCheckout = useCallback(
+ async ({ force = false } = {}) => {
+ if (!projectForDownload?.id) {
+ setTooltip({
+ type: 'error',
+ text: "Impossible d'identifier le projet pour le paiement.",
+ })
+ return
+ }
+
+ if (!force && (isCheckoutLaunching || downloadPaymentPending)) {
+ return
+ }
+
+ setIsCheckoutLaunching(true)
+ setDownloadPaymentPending(true)
+ try {
+ await createPlaybackDownloadCheckout(projectForDownload.id)
+ } catch (error) {
+ setDownloadPaymentPending(false)
+ setTooltip({
+ type: 'error',
+ text: error?.message || "Une erreur est survenue lors du paiement.",
+ })
+ } finally {
+ setIsCheckoutLaunching(false)
+ }
+ },
+ [
+ createPlaybackDownloadCheckout,
+ downloadPaymentPending,
+ isCheckoutLaunching,
+ projectForDownload,
+ setTooltip,
+ ]
+ )
+
+ const handleDownloadPress = useCallback(() => {
+ if (isDownloading || isCheckoutLaunching) {
+ return
+ }
+ if (canDownloadPlayback) {
+ handleDownloadUri()
+ return
+ }
+ if (downloadPaymentPending) {
+ Alert.alert(
+ 'Paiement en attente',
+ "Le paiement n'est pas encore confirmé. Si tu as déjà payé, patiente quelques instants.",
+ [
+ { text: 'Attendre', style: 'cancel' },
+ { text: 'Relancer le paiement', onPress: () => startPlaybackDownloadCheckout({ force: true }) },
+ ]
+ )
+ return
+ }
+ startPlaybackDownloadCheckout()
+ }, [
+ canDownloadPlayback,
+ downloadPaymentPending,
+ handleDownloadUri,
+ isCheckoutLaunching,
+ isDownloading,
+ startPlaybackDownloadCheckout,
+ ])
+
+ useEffect(() => {
+ if (!downloadPaymentPending || !hasPaidPlaybackDownload || isDownloading) {
+ return
+ }
+ setDownloadPaymentPending(false)
+ handleDownloadUri()
+ }, [downloadPaymentPending, handleDownloadUri, hasPaidPlaybackDownload, isDownloading])
const handlePublish = async () => {
if (isPublishing) return
@@ -333,7 +439,7 @@ const PlaybackDownload = ({ route }) => {
})
return
}
- if (!project?.id) {
+ if (!projectForDownload?.id) {
setTooltip({
type: 'success',
text: publishSuccessMessage,
@@ -342,7 +448,7 @@ const PlaybackDownload = ({ route }) => {
return
}
let tempSourcePath = null
- let playbackUrlToSave = pendingPlaybackUrl || project?.playbackUrl || null
+ let playbackUrlToSave = pendingPlaybackUrl || projectForDownload?.playbackUrl || null
const audioUrl = resolveAudioUrl()
if (!audioUrl && !playbackUrlToSave) {
setTooltip({
@@ -367,17 +473,17 @@ const PlaybackDownload = ({ route }) => {
const { sourcePath, videoUrl } = await uploadSourceRecording({
uri,
uid: currentUID,
- projectId: project.id,
+ projectId: projectForDownload.id,
})
tempSourcePath = sourcePath
const callable = firebase.functions().httpsCallable('upload-mergeVideoAndAudio')
const payload = {
- projectId: project?.id,
+ projectId: projectForDownload?.id,
videoUrl,
audioUrl,
- storagePath: `users/${currentUID}/projects/${project.id}/playback.mp4`,
+ storagePath: `users/${currentUID}/projects/${projectForDownload.id}/playback.mp4`,
}
console.log('[PlaybackDownload] publish: calling merge', payload)
@@ -391,7 +497,7 @@ const PlaybackDownload = ({ route }) => {
setPendingPlaybackUrl(playbackUrlToSave)
}
- await projectsRef.doc(project.id).set(
+ await projectsRef.doc(projectForDownload.id).set(
{
playbackUrl: playbackUrlToSave,
updatedAt: serverTimestamp(),
@@ -452,9 +558,9 @@ const PlaybackDownload = ({ route }) => {
- {project?.coverUrl ? (
+ {projectForDownload?.coverUrl ? (
@@ -465,15 +571,40 @@ const PlaybackDownload = ({ route }) => {
)}
- Télécharger le playback
+ {downloadLabel}
+ {!canDownloadPlayback && downloadPaymentPending ? (
+
+ Paiement en attente de confirmation...
+
+ ) : null}
+ {showSubscriptionCta ? (
+
+
+ Avec un abonnement, le téléchargement est gratuit.
+
+ navigate(Routes.Payments)}
+ containerStyle={styles.subscriptionButton}
+ />
+
+ ) : (
+
+ Avec ton abonnement, le téléchargement est gratuit.
+
+ )}
+
@@ -547,6 +678,29 @@ const styles = StyleSheet.create({
fontSize: 15,
color: Palette.white,
},
+ downloadPendingText: {
+ marginTop: 6,
+ fontFamily: FONT_FAMILY.InterRegular,
+ fontSize: 12,
+ color: Palette.white,
+ opacity: 0.8,
+ },
+ subscriptionCta: {
+ alignItems: 'center',
+ gap: 10,
+ marginTop: 6,
+ },
+ subscriptionNote: {
+ fontFamily: FONT_FAMILY.InterRegular,
+ fontSize: 13,
+ color: Palette.white,
+ opacity: 0.8,
+ textAlign: 'center',
+ },
+ subscriptionButton: {
+ width: '100%',
+ maxWidth: 280,
+ },
clubCardSpacing: {
marginTop: 10,
},
diff --git a/src/screens/cover/SongDownload.js b/src/screens/cover/SongDownload.js
index 5d4bb17..4d39f19 100644
--- a/src/screens/cover/SongDownload.js
+++ b/src/screens/cover/SongDownload.js
@@ -13,6 +13,7 @@ import {
import useMinuit from 'react-native-minuit/src/hooks/useMinuit.js'
import * as FileSystem from 'expo-file-system'
import * as Sharing from 'expo-sharing'
+import GradientButton from '../../components/GradientButton'
import MusicLandHeader from '../../components/MusicLandHeader'
import Page from '../../layouts/Page'
import { Routes } from '../../navigation/Routes'
@@ -103,6 +104,7 @@ const SongDownload = ({ route }) => {
const downloadLabel = canDownloadDirectly
? 'Télécharger mon morceau'
: 'Acheter ce morceau pour 1,99€'
+ const showSubscriptionCta = !hasActiveSubscription
const shouldShowDownloadPage = adventureChoice === 'stop'
useEffect(() => {
@@ -530,6 +532,23 @@ const SongDownload = ({ route }) => {
+ {showSubscriptionCta ? (
+
+
+ Avec un abonnement, le téléchargement est gratuit.
+
+ navigate(Routes.Payments)}
+ containerStyle={styles.subscriptionButton}
+ />
+
+ ) : (
+
+ Avec ton abonnement, le téléchargement est gratuit.
+
+ )}
+
) : (
@@ -620,6 +639,22 @@ const styles = StyleSheet.create({
color: Palette.white,
opacity: 0.8,
},
+ subscriptionCta: {
+ alignItems: 'center',
+ gap: gutters * 0.6,
+ marginTop: gutters * 0.4,
+ },
+ subscriptionNote: {
+ fontFamily: FONT_FAMILY.InterRegular,
+ fontSize: 13,
+ color: Palette.white,
+ opacity: 0.8,
+ textAlign: 'center',
+ },
+ subscriptionButton: {
+ width: '100%',
+ maxWidth: 280,
+ },
adventureGatePlaceholder: {
flex: 1,
},
diff --git a/src/screens/cover/ValidateCover.js b/src/screens/cover/ValidateCover.js
index f7da2ad..4f434f0 100644
--- a/src/screens/cover/ValidateCover.js
+++ b/src/screens/cover/ValidateCover.js
@@ -5,6 +5,7 @@ import useMinuit from 'react-native-minuit/src/hooks/useMinuit.js'
import { background } from '../../assets'
import GradientButton from '../../components/GradientButton'
import MusicLandHeader from '../../components/MusicLandHeader'
+import { isWeb } from '../../hooks/useLayoutType'
import Page from '../../layouts/Page'
import { Routes } from '../../navigation'
import { goBack, navigate } from '../../navigation/NavigationService'