pay for playback download checkout
This commit is contained in:
@@ -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 }) => {
|
||||
<CreateLyricsHeader title={'Publier ton playback'} />
|
||||
|
||||
<View style={styles.coverRow}>
|
||||
{project?.coverUrl ? (
|
||||
{projectForDownload?.coverUrl ? (
|
||||
<ExpoImage
|
||||
source={{ uri: project?.coverUrl }}
|
||||
source={{ uri: projectForDownload?.coverUrl }}
|
||||
style={styles.coverImage}
|
||||
contentFit="cover"
|
||||
/>
|
||||
@@ -465,15 +571,40 @@ const PlaybackDownload = ({ route }) => {
|
||||
)}
|
||||
|
||||
<Pressable
|
||||
style={[styles.downloadTile, (isPublishing || isDownloading) && { opacity: 0.6 }]}
|
||||
onPress={handleDownloadUri}
|
||||
disabled={isPublishing || isDownloading}
|
||||
style={[
|
||||
styles.downloadTile,
|
||||
(isPublishing || isDownloading || isCheckoutLaunching) && { opacity: 0.6 },
|
||||
]}
|
||||
onPress={handleDownloadPress}
|
||||
disabled={isPublishing || isDownloading || isCheckoutLaunching}
|
||||
>
|
||||
<MaterialCommunityIcons name="download" size={22} color={Palette.white} />
|
||||
<Text style={styles.downloadText}>Télécharger le playback</Text>
|
||||
<Text style={styles.downloadText}>{downloadLabel}</Text>
|
||||
</Pressable>
|
||||
{!canDownloadPlayback && downloadPaymentPending ? (
|
||||
<Text style={styles.downloadPendingText}>
|
||||
Paiement en attente de confirmation...
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{showSubscriptionCta ? (
|
||||
<View style={styles.subscriptionCta}>
|
||||
<Text style={styles.subscriptionNote}>
|
||||
Avec un abonnement, le téléchargement est gratuit.
|
||||
</Text>
|
||||
<GradientButton
|
||||
title="Voir les abonnements"
|
||||
onPress={() => navigate(Routes.Payments)}
|
||||
containerStyle={styles.subscriptionButton}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.subscriptionNote}>
|
||||
Avec ton abonnement, le téléchargement est gratuit.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<ClubAdvantagesCard style={styles.clubCardSpacing} />
|
||||
|
||||
<View style={styles.publishConsentContainer}>
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user