pay for music
This commit is contained in:
@@ -152,6 +152,85 @@ const createSubscriptionCheckoutSession = onCall({ region: REGION }, async (requ
|
||||
}
|
||||
})
|
||||
|
||||
const createSongDownloadCheckoutSession = onCall({ region: REGION }, async (request) => {
|
||||
try {
|
||||
const uid = request?.auth?.uid
|
||||
if (!uid) {
|
||||
throw new HttpsError('unauthenticated', 'Connecte-toi pour télécharger ton morceau.')
|
||||
}
|
||||
|
||||
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 Musicland',
|
||||
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: 'SONG_DOWNLOAD',
|
||||
projectId,
|
||||
},
|
||||
},
|
||||
{
|
||||
uiMode,
|
||||
successUrl,
|
||||
cancelUrl,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
return formatCheckoutSessionResponse(session)
|
||||
} catch (error) {
|
||||
console.error('[subscription-createSongDownloadCheckoutSession] 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
|
||||
@@ -272,6 +351,7 @@ const createCoinPackCheckoutSession = onCall({ region: REGION }, async (request)
|
||||
|
||||
module.exports = {
|
||||
createSubscriptionCheckoutSession,
|
||||
createSongDownloadCheckoutSession,
|
||||
createCoinPackCheckoutSession,
|
||||
resolveCheckoutUiMode,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
const { listSubscriptionPlans, listCoinPacks } = require('./catalog')
|
||||
const { createSubscriptionCheckoutSession, createCoinPackCheckoutSession } = require('./checkout')
|
||||
const {
|
||||
createSubscriptionCheckoutSession,
|
||||
createSongDownloadCheckoutSession,
|
||||
createCoinPackCheckoutSession,
|
||||
} = require('./checkout')
|
||||
const { cancelActiveSubscription, getActiveSubscription } = require('./management')
|
||||
const { handleStripeWebhook } = require('./webhooks')
|
||||
const { processAnnualSubscriptionAllowances } = require('./schedule')
|
||||
@@ -7,6 +11,7 @@ const { processAnnualSubscriptionAllowances } = require('./schedule')
|
||||
module.exports = {
|
||||
listSubscriptionPlans,
|
||||
createSubscriptionCheckoutSession,
|
||||
createSongDownloadCheckoutSession,
|
||||
cancelActiveSubscription,
|
||||
getActiveSubscription,
|
||||
listCoinPacks,
|
||||
|
||||
@@ -6,6 +6,7 @@ const { getStripeClient } = require('../../helpers/stripe')
|
||||
const { REGION } = require('./config')
|
||||
const {
|
||||
paymentsCollection,
|
||||
refsList,
|
||||
resolveStripeWebhookSecret,
|
||||
getServerTimestamp,
|
||||
toFirestoreTimestamp,
|
||||
@@ -149,6 +150,91 @@ const handleCheckoutSessionCompleted = async (session, event, { stripe } = {}) =
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
session.mode === 'payment' &&
|
||||
(session.payment_status === 'paid' || session.payment_status === 'no_payment_required') &&
|
||||
session.metadata?.purchaseType === 'SONG_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()?.downloadGrantedAt
|
||||
)
|
||||
|
||||
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(
|
||||
{
|
||||
downloadPurchase: {
|
||||
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(
|
||||
{
|
||||
downloadGrantedAt: getServerTimestamp(),
|
||||
downloadProjectId: projectId,
|
||||
},
|
||||
{ merge: true }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleCustomerSubscriptionEvent = async (subscription, event, { stripe } = {}) => {
|
||||
|
||||
@@ -210,6 +210,38 @@ const StripeProvider = ({ children }) => {
|
||||
[canUseEmbeddedCheckout, runCheckoutSession]
|
||||
)
|
||||
|
||||
const createSongDownloadCheckout = 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-createSongDownloadCheckoutSession',
|
||||
payload: {
|
||||
projectId,
|
||||
returnUrls: {
|
||||
successUrl: STRIPE_SUCCESS_URL,
|
||||
cancelUrl: STRIPE_CANCEL_URL,
|
||||
},
|
||||
uiMode: shouldUseEmbeddedCheckout ? 'embedded' : 'hosted',
|
||||
},
|
||||
logTag: 'song download checkout error',
|
||||
useEmbeddedFlow: shouldUseEmbeddedCheckout,
|
||||
})
|
||||
},
|
||||
[canUseEmbeddedCheckout, runCheckoutSession]
|
||||
)
|
||||
|
||||
const createCoinPackCheckout = React.useCallback(
|
||||
async (productId) => {
|
||||
if (!productId) {
|
||||
@@ -343,6 +375,7 @@ const StripeProvider = ({ children }) => {
|
||||
catalogError,
|
||||
refreshCatalog: fetchStripeCatalog,
|
||||
createSubscriptionCheckout,
|
||||
createSongDownloadCheckout,
|
||||
createCoinPackCheckout,
|
||||
openEmbeddedCheckout: setClientSecret,
|
||||
closeEmbeddedCheckout,
|
||||
@@ -359,6 +392,7 @@ const StripeProvider = ({ children }) => {
|
||||
catalogError,
|
||||
fetchStripeCatalog,
|
||||
createSubscriptionCheckout,
|
||||
createSongDownloadCheckout,
|
||||
createCoinPackCheckout,
|
||||
setClientSecret,
|
||||
closeEmbeddedCheckout,
|
||||
|
||||
+198
-107
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Image as ExpoImage } from 'expo-image'
|
||||
import {
|
||||
Linking,
|
||||
@@ -13,13 +13,12 @@ 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 AppCheckbox from '../../components/AppCheckbox'
|
||||
import BorderGradientButton from '../../components/BorderGradientButton'
|
||||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||||
import Page from '../../layouts/Page'
|
||||
import { Routes } from '../../navigation/Routes'
|
||||
import { goBack, navigate } from '../../navigation/NavigationService'
|
||||
import { useUser } from '../../providers/UserDataProvider'
|
||||
import { useStripe } from '../../providers/StripeProvider'
|
||||
import { gutters, Palette, Style } from '../../styles'
|
||||
import { FONT_FAMILY } from '../../styles/Fonts'
|
||||
import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader'
|
||||
@@ -31,7 +30,6 @@ import { MaterialCommunityIcons } from '@expo/vector-icons'
|
||||
import { isWeb } from '../../hooks/useLayoutType'
|
||||
import { getArtistDisplayName } from '../../utils/artistName'
|
||||
import { toDate } from '../../utils/dateFormatting'
|
||||
import SubscriptionConfirmModal from '../../components/SubscriptionConfirmModal'
|
||||
import FullscreenIntroVideo from '../../components/FullscreenIntroVideo'
|
||||
import AdventureChoiceModal from './components/AdventureChoiceModal'
|
||||
import ShareAndCreditsModal from './components/ShareAndCreditsModal'
|
||||
@@ -43,21 +41,26 @@ const SongDownload = ({ route }) => {
|
||||
coverOptions: routeCoverOptions,
|
||||
} = route?.params || {}
|
||||
const { selectedProject, updateProjectData, hasActiveSubscription } = useUser()
|
||||
const { createSongDownloadCheckout } = useStripe()
|
||||
const { setTooltip } = useMinuit()
|
||||
const { setLoading } = useGlobalLoading()
|
||||
const [isDownloading, setIsDownloading] = useState(false)
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
||||
const [hasAcceptedPublication, setHasAcceptedPublication] = useState(true)
|
||||
const [isCheckoutLaunching, setIsCheckoutLaunching] = useState(false)
|
||||
const [downloadPaymentPending, setDownloadPaymentPending] = useState(false)
|
||||
const { videos } = useUser()
|
||||
const part1Url = isWeb ? videos?.benaiPart1 : videos?.benaiPart1
|
||||
const [showIntro, setShowIntro] = useState(null)
|
||||
const [showAdventureModal, setShowAdventureModal] = useState(false)
|
||||
const [showShareModal, setShowShareModal] = useState(false)
|
||||
const [adventureChoice, setAdventureChoice] = useState('pending')
|
||||
const adventureGateTriggeredRef = useRef(false)
|
||||
|
||||
const projectForStage = useMemo(
|
||||
() => routeProject || selectedProject || null,
|
||||
[routeProject, selectedProject]
|
||||
)
|
||||
const projectForStage = useMemo(() => {
|
||||
if (routeProject?.id && selectedProject?.id && routeProject.id === selectedProject.id) {
|
||||
return selectedProject
|
||||
}
|
||||
return routeProject || selectedProject || null
|
||||
}, [routeProject, selectedProject])
|
||||
|
||||
const coverOptions = useMemo(() => {
|
||||
if (Array.isArray(routeCoverOptions) && routeCoverOptions.length) {
|
||||
@@ -93,6 +96,47 @@ const SongDownload = ({ route }) => {
|
||||
typeof projectForStage?.title === 'string' && projectForStage.title.trim()
|
||||
? projectForStage.title.trim()
|
||||
: 'Musicland Track'
|
||||
const projectId = projectForStage?.id || null
|
||||
const downloadPurchaseStatus = projectForStage?.downloadPurchase?.status || null
|
||||
const hasPaidDownload = downloadPurchaseStatus === 'paid'
|
||||
const canDownloadDirectly = hasActiveSubscription || hasPaidDownload
|
||||
const downloadLabel = canDownloadDirectly
|
||||
? 'Télécharger mon morceau'
|
||||
: 'Acheter ce morceau pour 1,99€'
|
||||
const shouldShowDownloadPage = adventureChoice === 'stop'
|
||||
|
||||
useEffect(() => {
|
||||
if (adventureGateTriggeredRef.current) {
|
||||
return
|
||||
}
|
||||
if (adventureChoice !== 'pending') {
|
||||
return
|
||||
}
|
||||
if (typeof videos === 'undefined') {
|
||||
return
|
||||
}
|
||||
adventureGateTriggeredRef.current = true
|
||||
if (part1Url) {
|
||||
setShowIntro(part1Url)
|
||||
} else {
|
||||
setShowAdventureModal(true)
|
||||
}
|
||||
}, [adventureChoice, part1Url, videos])
|
||||
|
||||
useEffect(() => {
|
||||
if (adventureChoice !== 'pending') {
|
||||
return
|
||||
}
|
||||
if (!adventureGateTriggeredRef.current) {
|
||||
return
|
||||
}
|
||||
if (showIntro) {
|
||||
return
|
||||
}
|
||||
if (!showAdventureModal) {
|
||||
setShowAdventureModal(true)
|
||||
}
|
||||
}, [adventureChoice, showAdventureModal, showIntro])
|
||||
|
||||
const continueFlow = useCallback(async () => {
|
||||
if (!selectedOption) {
|
||||
@@ -137,32 +181,6 @@ const SongDownload = ({ route }) => {
|
||||
updateProjectData,
|
||||
])
|
||||
|
||||
const handleContinue = useCallback(() => {
|
||||
if (!hasAcceptedPublication) {
|
||||
if (setTooltip) {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: 'Confirme la diffusion sur Musicland et YouTube avant de continuer',
|
||||
})
|
||||
} else {
|
||||
Alert.alert(
|
||||
'Confirmation requise',
|
||||
'Confirme la diffusion sur Musicland et YouTube avant de continuer'
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (hasActiveSubscription) {
|
||||
if (part1Url) {
|
||||
setShowIntro(part1Url)
|
||||
} else {
|
||||
continueFlow()
|
||||
}
|
||||
return
|
||||
}
|
||||
setShowConfirmModal(true)
|
||||
}, [continueFlow, hasAcceptedPublication, hasActiveSubscription, part1Url, setTooltip])
|
||||
|
||||
const handleCloseIntro = useCallback(() => {
|
||||
setShowIntro(null)
|
||||
setShowAdventureModal(true)
|
||||
@@ -170,14 +188,10 @@ const SongDownload = ({ route }) => {
|
||||
|
||||
const handleContinueAdventure = useCallback(() => {
|
||||
setShowAdventureModal(false)
|
||||
setAdventureChoice('continue')
|
||||
continueFlow()
|
||||
}, [continueFlow])
|
||||
|
||||
const handleStopAdventure = useCallback(() => {
|
||||
setShowAdventureModal(false)
|
||||
setShowShareModal(true)
|
||||
}, [])
|
||||
|
||||
const handleDownload = useCallback(async () => {
|
||||
const downloadUrl =
|
||||
projectForStage?.songUrl ||
|
||||
@@ -387,64 +401,140 @@ const SongDownload = ({ route }) => {
|
||||
}
|
||||
}, [coverUrl, isDownloading, projectForStage, selectedOption, trackTitle])
|
||||
|
||||
const startSongDownloadCheckout = useCallback(
|
||||
async ({ force = false } = {}) => {
|
||||
if (!projectId) {
|
||||
if (setTooltip) {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: "Impossible d'identifier le projet pour le paiement.",
|
||||
})
|
||||
} else {
|
||||
Alert.alert('Paiement', "Impossible d'identifier le projet pour le paiement.")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && (isCheckoutLaunching || downloadPaymentPending)) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsCheckoutLaunching(true)
|
||||
setDownloadPaymentPending(true)
|
||||
try {
|
||||
await createSongDownloadCheckout(projectId)
|
||||
} catch (error) {
|
||||
setDownloadPaymentPending(false)
|
||||
if (setTooltip) {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: error?.message || "Une erreur est survenue lors du paiement.",
|
||||
})
|
||||
} else {
|
||||
Alert.alert('Paiement', error?.message || "Une erreur est survenue lors du paiement.")
|
||||
}
|
||||
} finally {
|
||||
setIsCheckoutLaunching(false)
|
||||
}
|
||||
},
|
||||
[
|
||||
createSongDownloadCheckout,
|
||||
downloadPaymentPending,
|
||||
isCheckoutLaunching,
|
||||
projectId,
|
||||
setTooltip,
|
||||
]
|
||||
)
|
||||
|
||||
const handleDownloadPress = useCallback(() => {
|
||||
if (isDownloading || isCheckoutLaunching) {
|
||||
return
|
||||
}
|
||||
if (canDownloadDirectly) {
|
||||
handleDownload()
|
||||
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: () => startSongDownloadCheckout({ force: true }) },
|
||||
]
|
||||
)
|
||||
return
|
||||
}
|
||||
startSongDownloadCheckout()
|
||||
}, [
|
||||
canDownloadDirectly,
|
||||
downloadPaymentPending,
|
||||
handleDownload,
|
||||
isCheckoutLaunching,
|
||||
isDownloading,
|
||||
startSongDownloadCheckout,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (!downloadPaymentPending || !hasPaidDownload || isDownloading) {
|
||||
return
|
||||
}
|
||||
setDownloadPaymentPending(false)
|
||||
handleDownload()
|
||||
}, [downloadPaymentPending, handleDownload, hasPaidDownload, isDownloading])
|
||||
|
||||
const handleStopAdventure = useCallback(() => {
|
||||
setShowAdventureModal(false)
|
||||
setAdventureChoice('stop')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Page backgroundImg={background.studioBG2} headerType="NONE">
|
||||
<MusicLandHeader onPressBack={goBack} progress={84} />
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
styles.container,
|
||||
!isWeb && styles.containerMobile,
|
||||
styles.scrollContent,
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<CreateLyricsHeader title="Ta pochette est validée" />
|
||||
<View style={styles.coverRow}>
|
||||
{coverUrl ? (
|
||||
<ExpoImage source={{ uri: coverUrl }} style={styles.coverImage} contentFit="cover" />
|
||||
) : (
|
||||
<View style={[styles.coverImage, styles.coverPlaceholder]}>
|
||||
<Text style={styles.placeholderText}>Aucune pochette</Text>
|
||||
{shouldShowDownloadPage ? (
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
styles.container,
|
||||
!isWeb && styles.containerMobile,
|
||||
styles.scrollContent,
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<CreateLyricsHeader title="Ta pochette est validée" />
|
||||
<View style={styles.coverRow}>
|
||||
{coverUrl ? (
|
||||
<ExpoImage source={{ uri: coverUrl }} style={styles.coverImage} contentFit="cover" />
|
||||
) : (
|
||||
<View style={[styles.coverImage, styles.coverPlaceholder]}>
|
||||
<Text style={styles.placeholderText}>Aucune pochette</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.downloadColumn}>
|
||||
<Pressable
|
||||
style={[
|
||||
styles.downloadTile,
|
||||
(isDownloading || isCheckoutLaunching) && { opacity: 0.6 },
|
||||
]}
|
||||
onPress={handleDownloadPress}
|
||||
disabled={isDownloading || isCheckoutLaunching}
|
||||
>
|
||||
<MaterialCommunityIcons name="download" size={22} color={Palette.white} />
|
||||
<Text style={styles.downloadText}>{downloadLabel}</Text>
|
||||
</Pressable>
|
||||
{!canDownloadDirectly && downloadPaymentPending ? (
|
||||
<Text style={styles.downloadPendingText}>
|
||||
Paiement en attente de confirmation...
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Pressable style={styles.downloadTile} onPress={handleDownload}>
|
||||
<MaterialCommunityIcons name="download" size={22} color={Palette.white} />
|
||||
<Text style={styles.downloadText}>Acheter ce morceau pour 1,99€</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<ClubAdvantagesCard style={styles.clubCardSpacing} />
|
||||
|
||||
<View style={styles.consentContainer}>
|
||||
<AppCheckbox
|
||||
selected={hasAcceptedPublication}
|
||||
onPress={() => setHasAcceptedPublication((prev) => !prev)}
|
||||
label="J'accepte la diffusion de mon contenu sur Musicland et YouTube."
|
||||
/>
|
||||
<Text style={styles.consentDescription}>
|
||||
Cette confirmation est requise pour continuer.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<BorderGradientButton
|
||||
title={hasActiveSubscription ? 'Continuer' : 'Continuer sans générer de revenus'}
|
||||
onPress={handleContinue}
|
||||
containerStyle={styles.continueButton}
|
||||
/>
|
||||
</ScrollView>
|
||||
<SubscriptionConfirmModal
|
||||
isVisible={showConfirmModal}
|
||||
setIsVisible={setShowConfirmModal}
|
||||
onJoinClub={() => navigate(Routes.Payments)}
|
||||
onContinue={() => {
|
||||
if (part1Url) {
|
||||
setShowIntro(part1Url)
|
||||
} else {
|
||||
continueFlow()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ClubAdvantagesCard style={styles.clubCardSpacing} />
|
||||
</ScrollView>
|
||||
) : (
|
||||
<View style={styles.adventureGatePlaceholder} />
|
||||
)}
|
||||
<FullscreenIntroVideo url={showIntro} visible={!!showIntro} onClose={handleCloseIntro} />
|
||||
<AdventureChoiceModal
|
||||
isVisible={showAdventureModal}
|
||||
@@ -483,16 +573,6 @@ const styles = StyleSheet.create({
|
||||
scrollContent: {
|
||||
paddingBottom: gutters * 2.6,
|
||||
},
|
||||
consentContainer: {
|
||||
gap: 6,
|
||||
marginTop: 10,
|
||||
},
|
||||
consentDescription: {
|
||||
fontSize: 13,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
color: Palette.white,
|
||||
opacity: 0.8,
|
||||
},
|
||||
coverRow: {
|
||||
flexDirection: isWeb ? 'row' : 'column',
|
||||
alignItems: 'center',
|
||||
@@ -524,15 +604,26 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: '#8C4BFF',
|
||||
borderWidth: 0,
|
||||
},
|
||||
downloadColumn: {
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
},
|
||||
downloadText: {
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
fontSize: 15,
|
||||
color: Palette.white,
|
||||
},
|
||||
clubCardSpacing: {
|
||||
marginTop: gutters * 0.5,
|
||||
downloadPendingText: {
|
||||
marginTop: 6,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 12,
|
||||
color: Palette.white,
|
||||
opacity: 0.8,
|
||||
},
|
||||
continueButton: {
|
||||
adventureGatePlaceholder: {
|
||||
flex: 1,
|
||||
},
|
||||
clubCardSpacing: {
|
||||
marginTop: gutters * 0.5,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import FullscreenIntroVideo from '../../components/FullscreenIntroVideo'
|
||||
import { Image as ExpoImage } from 'expo-image'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
@@ -6,19 +5,14 @@ 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'
|
||||
import { useUser, useUserData } from '../../providers/UserDataProvider'
|
||||
import { useUserData } from '../../providers/UserDataProvider'
|
||||
import { gutters, Palette, Style } from '../../styles'
|
||||
import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader'
|
||||
const ValidateCover = () => {
|
||||
const { selectedProject, updateProjectData } = useUserData()
|
||||
const { videos } = useUser()
|
||||
const benhaiUrl = isWeb ? videos?.benhaiWeb || null : videos?.benhai
|
||||
const [showIntro, setShowIntro] = useState(null)
|
||||
|
||||
const { setIsLoading } = useMinuit()
|
||||
|
||||
const coverOptions = useMemo(() => {
|
||||
@@ -91,17 +85,12 @@ const ValidateCover = () => {
|
||||
if (!selectedOption) {
|
||||
return
|
||||
}
|
||||
setShowIntro(benhaiUrl)
|
||||
}
|
||||
|
||||
const handleCloseIntro = useCallback(() => {
|
||||
setShowIntro(null)
|
||||
navigate(Routes.SongDownload, {
|
||||
project: selectedProject,
|
||||
selectedOption,
|
||||
coverOptions,
|
||||
})
|
||||
}, [coverOptions, selectedOption, selectedProject])
|
||||
}
|
||||
|
||||
return (
|
||||
<Page backgroundImg={background.studioBG2} headerType="NONE">
|
||||
@@ -231,7 +220,6 @@ const ValidateCover = () => {
|
||||
disabled={!selectedOption || isSelecting}
|
||||
/>
|
||||
</View>
|
||||
<FullscreenIntroVideo url={showIntro} visible={!!showIntro} onClose={handleCloseIntro} />
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user