feat: refine creation and registration flows
This commit is contained in:
@@ -13,7 +13,7 @@ import firebase, { usersRef } from '../config/firebase'
|
||||
import Page from '../layouts/Page'
|
||||
import { isWeb } from '../hooks/useLayoutType'
|
||||
import { Routes } from '../navigation'
|
||||
import { navigate } from '../navigation/NavigationService'
|
||||
import { reset } from '../navigation/NavigationService'
|
||||
import { Palette } from '../styles'
|
||||
import { FONT_FAMILY } from '../styles/Fonts'
|
||||
|
||||
@@ -115,13 +115,14 @@ const CreatePassword = () => {
|
||||
...(birthDate ? { birthDate } : {}),
|
||||
...(country?.code ? { countryCode: country.code } : {}),
|
||||
...(country?.name ? { countryName: country.name } : {}),
|
||||
registrationFlowRequired: true,
|
||||
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
|
||||
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
|
||||
},
|
||||
{ merge: true }
|
||||
)
|
||||
setTooltip({ text: 'Compte créé, continuons', type: 'success' })
|
||||
navigate(Routes.BottomTab)
|
||||
reset({ index: 0, routes: [{ name: Routes.RegistrationChoice }] })
|
||||
} catch (e) {
|
||||
console.log('Register error', e?.message)
|
||||
const message =
|
||||
|
||||
@@ -295,7 +295,7 @@ const Home = ({ navigation, route }) => {
|
||||
if (selectedProject?.id !== currentProject.id) {
|
||||
selectProject(currentProject.id)
|
||||
}
|
||||
const targetRoute = beatmakerStage?.route || Routes.Compose
|
||||
const targetRoute = beatmakerStage?.route || Routes.ComposeSong
|
||||
navigate(targetRoute, beatmakerStage?.params)
|
||||
},
|
||||
},
|
||||
@@ -324,7 +324,7 @@ const Home = ({ navigation, route }) => {
|
||||
const songwriterAction = useMemo(() => getStageAction('songwriter', null), [])
|
||||
|
||||
const handleStartNew = useCallback(async () => {
|
||||
const targetRoute = songwriterAction?.route || Routes.WritingLyrics
|
||||
const targetRoute = songwriterAction?.route || Routes.CreateLyricsWithAi
|
||||
const targetParams = songwriterAction?.params
|
||||
|
||||
try {
|
||||
|
||||
@@ -15,6 +15,8 @@ const ClubCard = ({ onPress, containerStyle = null, variant = 'compact' }) => {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Club MusicLand"
|
||||
style={({ pressed }) => [
|
||||
styles.card,
|
||||
cardBaseStyle,
|
||||
|
||||
@@ -174,8 +174,8 @@ const StageCard = ({
|
||||
isMobileVariant ? { textAlign: 'center' } : { textAlign: 'left', marginRight: 10 },
|
||||
{
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 13,
|
||||
lineHeight: 20,
|
||||
fontSize: 14,
|
||||
lineHeight: 21,
|
||||
color: Palette.white,
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { background } from '../assets'
|
||||
import GradientButton from '../components/GradientButton.js'
|
||||
import { Input } from '../components/Input.js'
|
||||
import ItemContainer from '../components/ItemContainer/ItemContainer'
|
||||
import firebase from '../config/firebase'
|
||||
import firebase, { usersRef } from '../config/firebase'
|
||||
import useSocialAuth from '../hooks/useSocialAuth'
|
||||
import { isWeb } from '../hooks/useLayoutType'
|
||||
import Page from '../layouts/Page.js'
|
||||
@@ -17,16 +17,22 @@ import { Routes } from '../navigation'
|
||||
import { navigate } from '../navigation/NavigationService.js'
|
||||
import { FONT_FAMILY } from '../styles/Fonts.js'
|
||||
import Palette from '../styles/Palette.js'
|
||||
import { getRouteAfterAuthentication } from '../utils/registrationFlow'
|
||||
|
||||
export default ({ navigation }) => {
|
||||
const [, setTooltip] = useGlobal('_tooltip')
|
||||
const [email, setEmail] = useState(__DEV__ ? 'az@az.az' : '')
|
||||
const [password, setPassword] = useState(__DEV__ ? 'Minuit33' : '')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const afterLoginNavigate = useCallback(async () => {
|
||||
const afterLoginNavigate = useCallback(async (authResult) => {
|
||||
const uid = firebase.auth().currentUser?.uid
|
||||
if (!uid) throw new Error('Aucun utilisateur après connexion')
|
||||
navigation.reset({ index: 0, routes: [{ name: Routes.BottomTab }] })
|
||||
const userData =
|
||||
authResult?.userData || (await usersRef.doc(uid).get()).data() || {}
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: getRouteAfterAuthentication(userData) }],
|
||||
})
|
||||
}, [navigation])
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { Image, StyleSheet, View } from 'react-native'
|
||||
import { ai, background } from '../../assets'
|
||||
import React, { useCallback } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { background } from '../../assets'
|
||||
import BackgroundVideo from '../../components/BackgroundVideo'
|
||||
import BorderGradientButton from '../../components/BorderGradientButton'
|
||||
import FullscreenIntroVideo from '../../components/FullscreenIntroVideo'
|
||||
import GradientButton from '../../components/GradientButton'
|
||||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||||
import Page from '../../layouts/Page'
|
||||
@@ -15,13 +15,7 @@ import { gutters } from '../../styles'
|
||||
const Playback = ({ route, navigation }) => {
|
||||
const { project, returnToAdventureModal = false } = route.params || {}
|
||||
const { videos } = useUser()
|
||||
// const benhaiUrl = isWeb ? videos?.benhaiWeb || null : videos?.benhai;
|
||||
const theoUrl = isWeb ? videos?.theoWeb : videos?.theo
|
||||
const [showIntro, setShowIntro] = useState(theoUrl)
|
||||
|
||||
const handleCloseIntro = () => {
|
||||
setShowIntro(null)
|
||||
}
|
||||
|
||||
const handleBackPress = useCallback(() => {
|
||||
if (returnToAdventureModal) {
|
||||
@@ -39,6 +33,13 @@ const Playback = ({ route, navigation }) => {
|
||||
<Page
|
||||
headerType="NONE"
|
||||
backgroundImg={isWeb ? background.playbackWeb : background.playbackMobile}
|
||||
backgroundContent={
|
||||
<BackgroundVideo
|
||||
source={theoUrl}
|
||||
soundButtonStyle={isWeb ? styles.rightSideControl : undefined}
|
||||
/>
|
||||
}
|
||||
containerStyle={isWeb ? styles.videoPage : undefined}
|
||||
>
|
||||
{/* <Image source={ai.theo} style={styles.img} resizeMode="contain" /> */}
|
||||
<MusicLandHeader onPressBack={handleBackPress} progress={25} />
|
||||
@@ -58,7 +59,6 @@ const Playback = ({ route, navigation }) => {
|
||||
{/* <BorderGradientButton title="Importer une vidéo" /> */}
|
||||
</View>
|
||||
</View>
|
||||
<FullscreenIntroVideo url={showIntro} visible={showIntro} onClose={handleCloseIntro} />
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
@@ -66,10 +66,12 @@ const Playback = ({ route, navigation }) => {
|
||||
export default Playback
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
img: {
|
||||
width: '100%',
|
||||
height: '70%',
|
||||
position: 'absolute',
|
||||
bottom: -40,
|
||||
videoPage: {
|
||||
alignSelf: 'flex-start',
|
||||
marginLeft: '4%',
|
||||
},
|
||||
rightSideControl: {
|
||||
right: 24,
|
||||
left: 'auto',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Linking, StyleSheet, Text, View } from 'react-native'
|
||||
import useMinuit from 'react-native-minuit/src/hooks/useMinuit.js'
|
||||
import { background } from '../../assets'
|
||||
import AppCheckbox from '../../components/AppCheckbox'
|
||||
import BackgroundVideo from '../../components/BackgroundVideo'
|
||||
import BorderGradientButton from '../../components/BorderGradientButton'
|
||||
import GradientButton from '../../components/GradientButton'
|
||||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||||
@@ -15,11 +16,12 @@ import { Routes } from '../../navigation'
|
||||
import { useUser } from '../../providers/UserDataProvider'
|
||||
import { Palette, gutters } from '../../styles'
|
||||
import { FONT_FAMILY } from '../../styles/Fonts'
|
||||
import { isWeb } from '../../hooks/useLayoutType'
|
||||
|
||||
const PublishYoutube = () => {
|
||||
const route = useRoute()
|
||||
const routeProjectId = route?.params?.projectId ?? null
|
||||
const { userProjects = [], selectedProject } = useUser()
|
||||
const { userProjects = [], selectedProject, videos } = useUser()
|
||||
const { setTooltip } = useMinuit()
|
||||
|
||||
const project = useMemo(() => {
|
||||
@@ -33,6 +35,7 @@ const PublishYoutube = () => {
|
||||
}, [routeProjectId, selectedProject, userProjects])
|
||||
|
||||
const projectId = project?.id || routeProjectId || null
|
||||
const introVideo = isWeb ? videos?.benhaiWeb : videos?.benhai
|
||||
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [hasAcceptedPublication, setHasAcceptedPublication] = useState(true)
|
||||
@@ -194,7 +197,17 @@ const PublishYoutube = () => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Page backgroundImg={background.playbackBG2} headerType="NONE">
|
||||
<Page
|
||||
backgroundImg={background.playbackBG2}
|
||||
backgroundContent={
|
||||
<BackgroundVideo
|
||||
source={introVideo}
|
||||
soundButtonStyle={isWeb ? styles.rightSideControl : undefined}
|
||||
/>
|
||||
}
|
||||
containerStyle={isWeb ? styles.videoPage : undefined}
|
||||
headerType="NONE"
|
||||
>
|
||||
<MusicLandHeader progress={100} onPressBack={goBack} />
|
||||
<View
|
||||
style={{
|
||||
@@ -293,6 +306,13 @@ const PublishYoutube = () => {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
videoPage: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
rightSideControl: {
|
||||
right: 24,
|
||||
left: 'auto',
|
||||
},
|
||||
consentContainer: {
|
||||
gap: 6,
|
||||
},
|
||||
|
||||
@@ -27,6 +27,7 @@ import { Routes } from '../navigation'
|
||||
import { navigate, reset } from '../navigation/NavigationService'
|
||||
import { Palette } from '../styles'
|
||||
import { FONT_FAMILY } from '../styles/Fonts'
|
||||
import { getRouteAfterAuthentication } from '../utils/registrationFlow'
|
||||
|
||||
const LANGUAGE_STORAGE_KEY = 'preferredLanguage'
|
||||
|
||||
@@ -39,10 +40,13 @@ const Register = () => {
|
||||
const [showDatePicker, setShowDatePicker] = useState(false)
|
||||
const [, setTooltip] = useGlobal('_tooltip')
|
||||
|
||||
const afterSocialAuth = useCallback(async () => {
|
||||
const afterSocialAuth = useCallback(async (authResult) => {
|
||||
const uid = firebase.auth().currentUser?.uid
|
||||
if (!uid) throw new Error('Aucun utilisateur après connexion')
|
||||
reset({ index: 0, routes: [{ name: Routes.BottomTab }] })
|
||||
reset({
|
||||
index: 0,
|
||||
routes: [{ name: getRouteAfterAuthentication(authResult?.userData) }],
|
||||
})
|
||||
}, [])
|
||||
|
||||
const {
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import { useGlobal } from 'reactn'
|
||||
import { background } from '../assets'
|
||||
import BorderGradientButton from '../components/BorderGradientButton'
|
||||
import GradientButton from '../components/GradientButton'
|
||||
import ItemContainer from '../components/ItemContainer/ItemContainer'
|
||||
import { isWeb } from '../hooks/useLayoutType'
|
||||
import Page from '../layouts/Page'
|
||||
import { Routes } from '../navigation'
|
||||
import { navigate, reset } from '../navigation/NavigationService'
|
||||
import { Palette } from '../styles'
|
||||
import { FONT_FAMILY } from '../styles/Fonts'
|
||||
import {
|
||||
completeRegistrationFlow,
|
||||
getExperienceReset,
|
||||
isGlobalListeningLaunchPeriodActive,
|
||||
} from '../utils/registrationFlow'
|
||||
|
||||
const RegistrationChoice = () => {
|
||||
const [loadingChoice, setLoadingChoice] = useState(null)
|
||||
const [, setTooltip] = useGlobal('_tooltip')
|
||||
|
||||
const openPayment = useCallback((experience) => {
|
||||
navigate(Routes.Payments, {
|
||||
registrationFlow: true,
|
||||
registrationChoice: experience,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleListen = useCallback(async () => {
|
||||
if (!isGlobalListeningLaunchPeriodActive()) {
|
||||
openPayment('listen')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setLoadingChoice('listen')
|
||||
await completeRegistrationFlow('listen')
|
||||
reset(getExperienceReset('listen'))
|
||||
} catch (error) {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: error?.message || 'Impossible d’ouvrir l’espace Écoute',
|
||||
})
|
||||
} finally {
|
||||
setLoadingChoice(null)
|
||||
}
|
||||
}, [openPayment, setTooltip])
|
||||
|
||||
const handleCreation = useCallback(() => {
|
||||
openPayment('creation')
|
||||
}, [openPayment])
|
||||
|
||||
return (
|
||||
<Page
|
||||
width={isWeb ? 720 : null}
|
||||
backgroundImg={isWeb ? background.loginBgWeb : background.homeBG}
|
||||
headerType="NONE"
|
||||
title="Choisis ton expérience"
|
||||
>
|
||||
<View style={styles.pageContent}>
|
||||
<ItemContainer
|
||||
height={isWeb ? 480 : undefined}
|
||||
disableKeyboardHeight
|
||||
style={styles.container}
|
||||
>
|
||||
<View style={styles.content}>
|
||||
<View style={styles.heading}>
|
||||
<Text style={styles.title}>Que veux-tu faire en premier ?</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
Tu pourras passer librement de la création à l’écoute après cette étape.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.choice}>
|
||||
<View style={styles.choiceText}>
|
||||
<Text style={styles.choiceTitle}>Création</Text>
|
||||
<Text style={styles.choiceDescription}>
|
||||
Commence ton parcours avec les 10 crédits offerts à ton inscription.
|
||||
</Text>
|
||||
</View>
|
||||
<BorderGradientButton
|
||||
title="Créer"
|
||||
onPress={handleCreation}
|
||||
disabled={loadingChoice !== null}
|
||||
containerStyle={styles.button}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.divider} />
|
||||
|
||||
<View style={styles.choice}>
|
||||
<View style={styles.choiceText}>
|
||||
<Text style={styles.choiceTitle}>Écoute</Text>
|
||||
<Text style={styles.choiceDescription}>
|
||||
Retrouve les créations de la communauté dans l’espace Streaming.
|
||||
</Text>
|
||||
</View>
|
||||
<GradientButton
|
||||
title={loadingChoice === 'listen' ? 'Ouverture...' : 'Écouter'}
|
||||
onPress={handleListen}
|
||||
disabled={loadingChoice !== null}
|
||||
containerStyle={styles.button}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
pageContent: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
container: {
|
||||
width: '100%',
|
||||
},
|
||||
content: {
|
||||
gap: 24,
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
heading: {
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
marginBottom: 4,
|
||||
},
|
||||
title: {
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
fontSize: 24,
|
||||
textAlign: 'center',
|
||||
},
|
||||
subtitle: {
|
||||
color: Palette.gray,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
textAlign: 'center',
|
||||
},
|
||||
choice: {
|
||||
gap: 16,
|
||||
},
|
||||
choiceText: {
|
||||
gap: 6,
|
||||
},
|
||||
choiceTitle: {
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
fontSize: 19,
|
||||
},
|
||||
choiceDescription: {
|
||||
color: Palette.gray,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
},
|
||||
button: {
|
||||
width: '100%',
|
||||
},
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: Palette.ultraLightWhite,
|
||||
},
|
||||
})
|
||||
|
||||
export default RegistrationChoice
|
||||
@@ -6,6 +6,7 @@ import { Routes } from '../navigation'
|
||||
|
||||
import { SplashAnimationContext } from '../providers/SplashAnimationProvider'
|
||||
import { UserDataContext } from '../providers/UserDataProvider'
|
||||
import { getRouteAfterAuthentication } from '../utils/registrationFlow'
|
||||
|
||||
export default ({ navigation }) => {
|
||||
const { setCurrentUserData } = useContext(UserDataContext)
|
||||
@@ -28,7 +29,7 @@ export default ({ navigation }) => {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: Routes.BottomTab,
|
||||
name: getRouteAfterAuthentication(user),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useRoute } from '@react-navigation/native'
|
||||
import { Dimensions, Modal, Text, View } from 'react-native'
|
||||
import SwiperFlatList from 'react-native-swiper-flatlist'
|
||||
import { background, icons } from '../../assets'
|
||||
import BackgroundVideo from '../../components/BackgroundVideo'
|
||||
import BorderGradientButton from '../../components/BorderGradientButton'
|
||||
import GradientButton from '../../components/GradientButton'
|
||||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||||
@@ -41,8 +42,14 @@ const ComposeSong = () => {
|
||||
const [progress, setProgress] = useState(18)
|
||||
const [containerLayout, setContainerLayout] = useState(null)
|
||||
const route = useRoute()
|
||||
const { selectedProjectId, selectedProject, updateProjectData, currentUserData, currentUID } =
|
||||
useUser()
|
||||
const {
|
||||
selectedProjectId,
|
||||
selectedProject,
|
||||
updateProjectData,
|
||||
currentUserData,
|
||||
currentUID,
|
||||
videos,
|
||||
} = useUser()
|
||||
|
||||
// Selections state
|
||||
const [genres, setGenres] = useState([])
|
||||
@@ -286,7 +293,11 @@ const ComposeSong = () => {
|
||||
])
|
||||
|
||||
return (
|
||||
<Page backgroundImg={background.studioBG2} headerType="NONE">
|
||||
<Page
|
||||
backgroundImg={background.studioBG2}
|
||||
backgroundContent={<BackgroundVideo source={videos?.malik} />}
|
||||
headerType="NONE"
|
||||
>
|
||||
<MusicLandHeader onPressBack={onPressBack} progress={progress} logo={icons.musicLandStudio} />
|
||||
<View style={{ flex: 1, paddingBottom: gutters, gap: 48 }}>
|
||||
<View
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useRoute } from '@react-navigation/native'
|
||||
import { FlatList, Modal, Text, View, useWindowDimensions } from 'react-native'
|
||||
import { FlatList, Modal, StyleSheet, Text, View, useWindowDimensions } from 'react-native'
|
||||
import { background } from '../../assets'
|
||||
import BackgroundVideo from '../../components/BackgroundVideo'
|
||||
import BorderGradientButton from '../../components/BorderGradientButton'
|
||||
import GradientButton from '../../components/GradientButton'
|
||||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||||
@@ -47,8 +48,14 @@ const ComposeSong = () => {
|
||||
}, [windowWidth])
|
||||
const containerWidth = parentLayout?.width || estimatedContainerWidth
|
||||
const route = useRoute()
|
||||
const { selectedProjectId, selectedProject, updateProjectData, currentUserData, currentUID } =
|
||||
useUser()
|
||||
const {
|
||||
selectedProjectId,
|
||||
selectedProject,
|
||||
updateProjectData,
|
||||
currentUserData,
|
||||
currentUID,
|
||||
videos,
|
||||
} = useUser()
|
||||
|
||||
const [genres, setGenres] = useState([])
|
||||
const [voice, setVoice] = useState({})
|
||||
@@ -288,7 +295,18 @@ const ComposeSong = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Page backgroundImg={background.studioBG2} headerType="NONE">
|
||||
<Page
|
||||
backgroundImg={background.studioBG2}
|
||||
backgroundContent={
|
||||
<BackgroundVideo
|
||||
source={videos?.malikWeb}
|
||||
soundButtonStyle={styles.rightSideControl}
|
||||
/>
|
||||
}
|
||||
coinBadgeContainerStyle={styles.rightSideCredits}
|
||||
containerStyle={styles.videoQuestionPage}
|
||||
headerType="NONE"
|
||||
>
|
||||
<MusicLandHeader
|
||||
onPressBack={onPressBack}
|
||||
progress={progress}
|
||||
@@ -479,3 +497,19 @@ const ComposeSong = () => {
|
||||
}
|
||||
|
||||
export default ComposeSong
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
videoQuestionPage: {
|
||||
alignSelf: 'flex-start',
|
||||
marginLeft: '4%',
|
||||
},
|
||||
rightSideControl: {
|
||||
right: 24,
|
||||
left: 'auto',
|
||||
},
|
||||
rightSideCredits: {
|
||||
right: 24,
|
||||
left: 'auto',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -111,7 +111,7 @@ const Studio = () => {
|
||||
} else {
|
||||
if (!selectedProject?.id) return
|
||||
selectProject(selectedProject.id)
|
||||
navigate(Routes.Compose)
|
||||
navigate(Routes.ComposeSong)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -130,7 +130,7 @@ const Studio = () => {
|
||||
/>
|
||||
{!selectedProject?.coverUrl && (
|
||||
<GradientButton
|
||||
title="Générer la pochette"
|
||||
title="Voir ma pochette"
|
||||
containerStyle={{
|
||||
marginTop: responsiveHeight(2),
|
||||
}}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { BlurView } from 'expo-blur'
|
||||
import { Image as ExpoImage } from 'expo-image'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import GradientButton from '../components/GradientButton'
|
||||
import BorderGradientButton from '../components/BorderGradientButton'
|
||||
import CreditAmount from '../components/CreditAmount'
|
||||
import Page from '../layouts/Page'
|
||||
import { background, icons, planBadges } from '../assets'
|
||||
@@ -22,7 +23,7 @@ import { FONT_FAMILY } from '../styles/Fonts'
|
||||
import { isWeb } from '../hooks/useLayoutType'
|
||||
import { useStripe } from '../providers/StripeProvider'
|
||||
import { useUserData } from '../providers/UserDataProvider'
|
||||
import { goBack, navigate } from '../navigation/NavigationService'
|
||||
import { goBack, navigate, reset } from '../navigation/NavigationService'
|
||||
import { Routes } from '../navigation/Routes'
|
||||
import {
|
||||
COIN_PACK_DETAILS,
|
||||
@@ -36,6 +37,7 @@ import {
|
||||
SUBSCRIPTION_PLAN_SYNONYMS,
|
||||
SUBSCRIPTION_PRICE_ID_BY_PERIOD,
|
||||
} from '../utils/subscriptionCardDisplay'
|
||||
import { completeRegistrationFlow, getExperienceReset } from '../utils/registrationFlow'
|
||||
|
||||
const formatCurrency = (amount, currency = 'eur') => {
|
||||
if (typeof amount !== 'number') {
|
||||
@@ -302,6 +304,10 @@ export default function Subscriptions() {
|
||||
const route = useRoute()
|
||||
const insets = useSafeAreaInsets()
|
||||
const isMobile = !isWeb
|
||||
const isRegistrationFlow = route?.params?.registrationFlow === true
|
||||
const registrationChoice = route?.params?.registrationChoice
|
||||
const canSkipRegistrationPayment =
|
||||
isRegistrationFlow && registrationChoice === 'creation'
|
||||
const initialPack = React.useMemo(() => {
|
||||
const rawPack = route?.params?.pack
|
||||
return typeof rawPack === 'string' ? rawPack.toLowerCase() : null
|
||||
@@ -318,12 +324,13 @@ export default function Subscriptions() {
|
||||
createSubscriptionCheckout,
|
||||
createCoinPackCheckout,
|
||||
} = useStripe()
|
||||
const { currentUserData } = useUserData()
|
||||
const { currentUserData, hasActiveSubscription } = useUserData()
|
||||
const [selectedPeriodKey, setSelectedPeriodKey] = React.useState('monthly')
|
||||
const [selectedPriceId, setSelectedPriceId] = React.useState(null)
|
||||
const [processingPriceId, setProcessingPriceId] = React.useState(null)
|
||||
const [errorMessage, setErrorMessage] = React.useState(null)
|
||||
const initialPackHandledRef = React.useRef(false)
|
||||
const registrationCompletedRef = React.useRef(false)
|
||||
|
||||
const normalizedPlansByPeriod = React.useMemo(
|
||||
() => ({
|
||||
@@ -344,7 +351,43 @@ export default function Subscriptions() {
|
||||
const hasAnyPlan = availablePeriods.length > 0
|
||||
const isLoadingPlans = isCatalogLoading && !hasAnyPlan
|
||||
const combinedErrorMessage = errorMessage || catalogError
|
||||
const screenTitle = isCoinPackView ? COIN_PACK_SECTION_TITLE : 'Rejoignez le club MusicLand'
|
||||
const screenTitle = isCoinPackView
|
||||
? COIN_PACK_SECTION_TITLE
|
||||
: isRegistrationFlow
|
||||
? 'Choisis ton accès MusicLand'
|
||||
: 'Rejoignez le club MusicLand'
|
||||
|
||||
const finishRegistrationFlow = React.useCallback(async () => {
|
||||
if (
|
||||
!isRegistrationFlow ||
|
||||
registrationCompletedRef.current ||
|
||||
(registrationChoice !== 'listen' && registrationChoice !== 'creation')
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
registrationCompletedRef.current = true
|
||||
try {
|
||||
await completeRegistrationFlow(registrationChoice)
|
||||
reset(getExperienceReset(registrationChoice))
|
||||
} catch (error) {
|
||||
registrationCompletedRef.current = false
|
||||
setErrorMessage(error?.message || 'Impossible de terminer ton inscription.')
|
||||
}
|
||||
}, [isRegistrationFlow, registrationChoice])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isRegistrationFlow && hasActiveSubscription) {
|
||||
finishRegistrationFlow()
|
||||
}
|
||||
}, [finishRegistrationFlow, hasActiveSubscription, isRegistrationFlow])
|
||||
|
||||
const handleSkipRegistrationPayment = React.useCallback(() => {
|
||||
if (!canSkipRegistrationPayment) {
|
||||
return
|
||||
}
|
||||
finishRegistrationFlow()
|
||||
}, [canSkipRegistrationPayment, finishRegistrationFlow])
|
||||
|
||||
React.useEffect(() => {
|
||||
initialPackHandledRef.current = false
|
||||
@@ -721,9 +764,23 @@ export default function Subscriptions() {
|
||||
disabled={isActionDisabled}
|
||||
gradientStyle={[styles.actionButtonGradient, styles.mobileActionButtonGradient]}
|
||||
/>
|
||||
{canSkipRegistrationPayment ? (
|
||||
<BorderGradientButton
|
||||
title="Continuer avec mes 10 crédits"
|
||||
onPress={handleSkipRegistrationPayment}
|
||||
containerStyle={styles.skipPaymentButton}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
}, [actionButtonTitle, handleCheckout, isActionDisabled, mobileActionSafePadding])
|
||||
}, [
|
||||
actionButtonTitle,
|
||||
canSkipRegistrationPayment,
|
||||
handleCheckout,
|
||||
handleSkipRegistrationPayment,
|
||||
isActionDisabled,
|
||||
mobileActionSafePadding,
|
||||
])
|
||||
|
||||
const renderWebBackButton = React.useCallback(() => {
|
||||
if (isMobile) return null
|
||||
@@ -803,7 +860,7 @@ export default function Subscriptions() {
|
||||
))}
|
||||
</View>
|
||||
|
||||
{coinPacks && coinPacks.length > 0 ? (
|
||||
{!isRegistrationFlow && coinPacks && coinPacks.length > 0 ? (
|
||||
<View style={styles.creditsSection}>
|
||||
<Text style={styles.sectionTitle}>{COIN_PACK_SECTION_TITLE}</Text>
|
||||
<View style={styles.creditsGrid}>
|
||||
@@ -871,6 +928,13 @@ export default function Subscriptions() {
|
||||
disabled={isActionDisabled}
|
||||
gradientStyle={styles.actionButtonGradient}
|
||||
/>
|
||||
{canSkipRegistrationPayment ? (
|
||||
<BorderGradientButton
|
||||
title="Continuer avec mes 10 crédits"
|
||||
onPress={handleSkipRegistrationPayment}
|
||||
containerStyle={styles.skipPaymentButton}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<Text style={styles.disclaimer}>{SUBSCRIPTION_DISCLAIMER}</Text>
|
||||
@@ -1440,6 +1504,9 @@ const styles = StyleSheet.create({
|
||||
shadowRadius: 12,
|
||||
elevation: 12,
|
||||
},
|
||||
skipPaymentButton: {
|
||||
width: '100%',
|
||||
},
|
||||
mobileActionButtonGradient: {
|
||||
width: '100%',
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Dimensions, KeyboardAvoidingView, Platform, View } from 'react-native'
|
||||
import { responsiveHeight } from 'react-native-responsive-dimensions'
|
||||
import { SwiperFlatList } from 'react-native-swiper-flatlist'
|
||||
import { background } from '../../assets'
|
||||
import BackgroundVideo from '../../components/BackgroundVideo'
|
||||
import GradientButton from '../../components/GradientButton'
|
||||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||||
import { OTHER_OBJECTIVE_OPTION, OTHER_STYLE_OPTION } from '../../data/data'
|
||||
@@ -26,7 +27,7 @@ const CUSTOM_SANITIZE_OPTIONS = { autoInjectPreChorus: false }
|
||||
const CTA_BUTTON_MAX_WIDTH = 500
|
||||
|
||||
const CreateLyricsWithAi = () => {
|
||||
const { selectedProject } = useUser()
|
||||
const { selectedProject, videos } = useUser()
|
||||
const scrollRef = useRef(null)
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
const [progress, setProgress] = useState(16)
|
||||
@@ -341,9 +342,16 @@ const CreateLyricsWithAi = () => {
|
||||
}, [selectedIndex, parentLayout?.height])
|
||||
|
||||
const isCtaVisible = selectedIndex !== 8
|
||||
const isQuestionStep = selectedIndex < MAX_STEP_INDEX
|
||||
|
||||
return (
|
||||
<Page headerType="NONE" backgroundImg={background.writingMobileBackground}>
|
||||
<Page
|
||||
headerType="NONE"
|
||||
backgroundImg={isQuestionStep ? null : background.writingMobileBackground}
|
||||
backgroundContent={
|
||||
isQuestionStep ? <BackgroundVideo source={videos?.celine} /> : null
|
||||
}
|
||||
>
|
||||
<View
|
||||
onLayout={(event) => {
|
||||
const nextHeight = event?.nativeEvent?.layout?.height
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { Platform, View } from 'react-native'
|
||||
import { Platform, StyleSheet, View } from 'react-native'
|
||||
import { responsiveHeight } from 'react-native-responsive-dimensions'
|
||||
import { background } from '../../assets'
|
||||
import BackgroundVideo from '../../components/BackgroundVideo'
|
||||
import GradientButton from '../../components/GradientButton'
|
||||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||||
import { OTHER_OBJECTIVE_OPTION, OTHER_STYLE_OPTION } from '../../data/data'
|
||||
@@ -24,7 +25,7 @@ const MAX_STEP_INDEX = 8
|
||||
const CUSTOM_SANITIZE_OPTIONS = { autoInjectPreChorus: false }
|
||||
|
||||
const CreateLyricsWithAi = () => {
|
||||
const { selectedProject } = useUser()
|
||||
const { selectedProject, videos } = useUser()
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
const handleLyricsError = React.useCallback(() => {
|
||||
setSelectedIndex(7)
|
||||
@@ -385,13 +386,18 @@ const CreateLyricsWithAi = () => {
|
||||
]
|
||||
|
||||
const currentStep = steps[selectedIndex]
|
||||
const isQuestionStep = selectedIndex < MAX_STEP_INDEX
|
||||
|
||||
return (
|
||||
<Page
|
||||
headerType="NONE"
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
keyboardVerticalOffset={Platform.OS === 'ios' ? 64 : 100}
|
||||
backgroundImg={background.libraryBgWeb}
|
||||
backgroundImg={isQuestionStep ? null : background.libraryBgWeb}
|
||||
backgroundContent={
|
||||
isQuestionStep ? <BackgroundVideo source={videos?.celineWeb} /> : null
|
||||
}
|
||||
containerStyle={isQuestionStep ? styles.videoQuestionPage : undefined}
|
||||
>
|
||||
<MusicLandHeader onPressBack={onPressBack} progress={progress} />
|
||||
<View
|
||||
@@ -429,3 +435,10 @@ const CreateLyricsWithAi = () => {
|
||||
}
|
||||
|
||||
export default CreateLyricsWithAi
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
videoQuestionPage: {
|
||||
alignSelf: 'flex-end',
|
||||
marginRight: '4%',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -328,7 +328,7 @@ const Lyrics = ({ navigation }) => {
|
||||
return
|
||||
}
|
||||
const handleNavigateToStudio = () => {
|
||||
const targetRoute = beatmakerStage?.route || Routes.Compose
|
||||
const targetRoute = beatmakerStage?.route || Routes.ComposeSong
|
||||
navigate(targetRoute, {
|
||||
...(beatmakerStage?.params || {}),
|
||||
fromLyrics: true,
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Modal, Platform, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import useMinuit from 'react-native-minuit/src/hooks/useMinuit'
|
||||
import BorderGradientButton from '../../components/BorderGradientButton'
|
||||
import FullscreenIntroVideo from '../../components/FullscreenIntroVideo'
|
||||
import GradientButton from '../../components/GradientButton'
|
||||
import { Input } from '../../components/Input'
|
||||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||||
@@ -19,7 +18,6 @@ import { background } from '../../assets'
|
||||
|
||||
export const ChooseCoverType = () => {
|
||||
const isIOS = Platform.OS === 'ios'
|
||||
const [showIntro, setShowIntro] = useState(true)
|
||||
const [choiceVisible, setChoiceVisible] = useState(false)
|
||||
const [pseudoVisible, setPseudoVisible] = useState(false)
|
||||
const [choiceDismissAction, setChoiceDismissAction] = useState(null)
|
||||
@@ -33,12 +31,6 @@ export const ChooseCoverType = () => {
|
||||
const { setTooltip } = useMinuit()
|
||||
|
||||
const projectId = selectedProject?.id || selectedProjectId || null
|
||||
const hasFinalCover = !!selectedProject?.coverUrl
|
||||
const hasGeneratedOptions = Array.isArray(selectedProject?.cover?.options)
|
||||
? selectedProject.cover.options.length > 0
|
||||
: false
|
||||
const generateCoverLabel = hasFinalCover ? 'Modifier la pochette' : 'Générer la pochette'
|
||||
|
||||
const hasArtistPreference = useMemo(() => {
|
||||
if (currentUserData?.userName) return true
|
||||
const pref = currentUserData?.artistNamePreference
|
||||
@@ -108,13 +100,9 @@ export const ChooseCoverType = () => {
|
||||
[currentUID, projectId]
|
||||
)
|
||||
|
||||
const handleGenerateCover = useCallback(() => {
|
||||
if (hasFinalCover || hasGeneratedOptions) {
|
||||
navigate(Routes.ValidateCover)
|
||||
return
|
||||
}
|
||||
const handleShowCover = useCallback(() => {
|
||||
ensureArtistPreference(() => navigate(Routes.PouchReady))
|
||||
}, [ensureArtistPreference, hasFinalCover, hasGeneratedOptions])
|
||||
}, [ensureArtistPreference])
|
||||
|
||||
const closeChoiceModal = useCallback(() => {
|
||||
setChoiceVisible(false)
|
||||
@@ -276,15 +264,11 @@ export const ChooseCoverType = () => {
|
||||
onPress={handlePickUserImage}
|
||||
/> */}
|
||||
<GradientButton
|
||||
title={generateCoverLabel}
|
||||
onPress={handleGenerateCover}
|
||||
title="Voir ma pochette"
|
||||
onPress={handleShowCover}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{/*<FullscreenIntroVideo*/}
|
||||
{/* visible={showIntro}*/}
|
||||
{/* onClose={() => setShowIntro(false)}*/}
|
||||
{/*/>*/}
|
||||
<Modal
|
||||
visible={choiceVisible}
|
||||
transparent
|
||||
|
||||
+215
-873
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user