fix favicon, playlist subscription only
This commit is contained in:
@@ -82,7 +82,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"bundler": "metro"
|
"bundler": "metro",
|
||||||
|
"favicon": "./assets/favicon.png"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"react-native-compressor",
|
"react-native-compressor",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
@@ -25,7 +25,7 @@ const MobileCoinBadge = ({ style, textStyle, iconSize = 20, iconPosition = 'left
|
|||||||
}, [currentUserData?.coins])
|
}, [currentUserData?.coins])
|
||||||
|
|
||||||
const handlePress = useCallback(() => {
|
const handlePress = useCallback(() => {
|
||||||
navigate(Routes.Payments)
|
navigate(Routes.Payments, { pack: 'packs' })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!currentUID || Platform.OS === 'web') {
|
if (!currentUID || Platform.OS === 'web') {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { Dimensions, FlatList, Image, Pressable, Text, TextInput, View } from 'react-native'
|
import { Dimensions, FlatList, Image, Pressable, Text, TextInput, View } from 'react-native'
|
||||||
import { SheetManager } from 'react-native-actions-sheet'
|
import { SheetManager } from 'react-native-actions-sheet'
|
||||||
import useMinuit from 'react-native-minuit/src/hooks/useMinuit'
|
import useMinuit from 'react-native-minuit/src/hooks/useMinuit'
|
||||||
import SwiperFlatList from 'react-native-swiper-flatlist'
|
import SwiperFlatList from 'react-native-swiper-flatlist'
|
||||||
import { icons } from '../../assets'
|
import { icons } from '../../assets'
|
||||||
|
import usePlaylistCreationGuard from '../../hooks/usePlaylistCreationGuard'
|
||||||
import { useUserData } from '../../providers/UserDataProvider'
|
import { useUserData } from '../../providers/UserDataProvider'
|
||||||
import { addMusicToPlaylist, createPlaylist } from '../../screens/Library/Playlists/playlist'
|
import { addMusicToPlaylist, createPlaylist } from '../../screens/Library/Playlists/playlist'
|
||||||
import { Palette } from '../../styles'
|
import { Palette } from '../../styles'
|
||||||
@@ -20,8 +21,9 @@ const PlaylistModal = (props) => {
|
|||||||
const [selectedId, setSelectedId] = useState('')
|
const [selectedId, setSelectedId] = useState('')
|
||||||
const [playlist, setPlaylist] = useState('')
|
const [playlist, setPlaylist] = useState('')
|
||||||
const [isCreating, setIsCreating] = useState(false)
|
const [isCreating, setIsCreating] = useState(false)
|
||||||
const { currentUID, userPlaylists = [] } = useUserData()
|
const { currentUID, userPlaylists = [], hasActiveSubscription } = useUserData()
|
||||||
const startAtCreate = props?.payload?.startAtCreate || false
|
const startAtCreate = props?.payload?.startAtCreate || false
|
||||||
|
const ensureCanCreatePlaylist = usePlaylistCreationGuard()
|
||||||
const { setTooltip } = useMinuit()
|
const { setTooltip } = useMinuit()
|
||||||
|
|
||||||
const sanitizedPlaylists = useMemo(
|
const sanitizedPlaylists = useMemo(
|
||||||
@@ -35,17 +37,27 @@ const PlaylistModal = (props) => {
|
|||||||
return sanitizedPlaylists.some((p) => (p?.name || '').trim().toLowerCase() === lower)
|
return sanitizedPlaylists.some((p) => (p?.name || '').trim().toLowerCase() === lower)
|
||||||
}, [playlist, sanitizedPlaylists])
|
}, [playlist, sanitizedPlaylists])
|
||||||
|
|
||||||
const hideSheet = () => {
|
const hideSheet = useCallback(() => {
|
||||||
SheetManager.hide('Playlist')
|
SheetManager.hide('Playlist')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!startAtCreate || hasActiveSubscription) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ensureCanCreatePlaylist()) {
|
||||||
|
hideSheet()
|
||||||
|
}
|
||||||
|
}, [ensureCanCreatePlaylist, hasActiveSubscription, hideSheet, startAtCreate])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppActionSheet id="Playlist">
|
<AppActionSheet id="Playlist">
|
||||||
<SwiperFlatList
|
<SwiperFlatList
|
||||||
style={{ width, left: -14 }}
|
style={{ width, left: -14 }}
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
disableGesture
|
disableGesture
|
||||||
index={startAtCreate ? 1 : 0}
|
index={startAtCreate && hasActiveSubscription ? 1 : 0}
|
||||||
>
|
>
|
||||||
<View style={{ width, paddingHorizontal: 14 }}>
|
<View style={{ width, paddingHorizontal: 14 }}>
|
||||||
<View style={{ gap: 20 }}>
|
<View style={{ gap: 20 }}>
|
||||||
@@ -70,12 +82,17 @@ const PlaylistModal = (props) => {
|
|||||||
...Style.containerRow,
|
...Style.containerRow,
|
||||||
gap: 10,
|
gap: 10,
|
||||||
}}
|
}}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
if (!ensureCanCreatePlaylist()) {
|
||||||
|
hideSheet()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
scrollRef.current?.scrollToIndex({
|
scrollRef.current?.scrollToIndex({
|
||||||
index: 1,
|
index: 1,
|
||||||
animated: true,
|
animated: true,
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
>
|
>
|
||||||
<Image source={icons.add} style={size({ size: 15 })} />
|
<Image source={icons.add} style={size({ size: 15 })} />
|
||||||
<Text
|
<Text
|
||||||
@@ -212,6 +229,11 @@ const PlaylistModal = (props) => {
|
|||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (!playlist?.trim()?.length || !currentUID) return
|
if (!playlist?.trim()?.length || !currentUID) return
|
||||||
try {
|
try {
|
||||||
|
if (!ensureCanCreatePlaylist()) {
|
||||||
|
hideSheet()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setIsCreating(true)
|
setIsCreating(true)
|
||||||
const name = playlist.trim()
|
const name = playlist.trim()
|
||||||
const lower = name.toLowerCase()
|
const lower = name.toLowerCase()
|
||||||
@@ -232,7 +254,7 @@ const PlaylistModal = (props) => {
|
|||||||
musics: props?.payload?.projectId ? [props.payload.projectId] : [],
|
musics: props?.payload?.projectId ? [props.payload.projectId] : [],
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
})
|
}, { hasActiveSubscription })
|
||||||
|
|
||||||
setTooltip({
|
setTooltip({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Portal } from '@gorhom/portal'
|
import { Portal } from '@gorhom/portal'
|
||||||
import { BlurView } from 'expo-blur'
|
import { BlurView } from 'expo-blur'
|
||||||
import React, { useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { FlatList, Image, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'
|
import { FlatList, Image, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'
|
||||||
import { SheetManager, useProviderContext } from 'react-native-actions-sheet'
|
import { SheetManager, useProviderContext } from 'react-native-actions-sheet'
|
||||||
import { actionSheetEventManager } from 'react-native-actions-sheet/dist/src/eventmanager'
|
import { actionSheetEventManager } from 'react-native-actions-sheet/dist/src/eventmanager'
|
||||||
import useMinuit from 'react-native-minuit/src/hooks/useMinuit'
|
import useMinuit from 'react-native-minuit/src/hooks/useMinuit'
|
||||||
import { icons } from '../../assets'
|
import { icons } from '../../assets'
|
||||||
|
import usePlaylistCreationGuard from '../../hooks/usePlaylistCreationGuard'
|
||||||
import { useUserData } from '../../providers/UserDataProvider'
|
import { useUserData } from '../../providers/UserDataProvider'
|
||||||
import { addMusicToPlaylist, createPlaylist } from '../../screens/Library/Playlists/playlist'
|
import { addMusicToPlaylist, createPlaylist } from '../../screens/Library/Playlists/playlist'
|
||||||
import { Palette } from '../../styles'
|
import { Palette } from '../../styles'
|
||||||
@@ -18,16 +19,17 @@ const PlaylistModal = (props) => {
|
|||||||
const [selectedId, setSelectedId] = useState('')
|
const [selectedId, setSelectedId] = useState('')
|
||||||
const [playlist, setPlaylist] = useState('')
|
const [playlist, setPlaylist] = useState('')
|
||||||
const [isCreating, setIsCreating] = useState(false)
|
const [isCreating, setIsCreating] = useState(false)
|
||||||
const { currentUID, userPlaylists = [] } = useUserData()
|
const { currentUID, userPlaylists = [], hasActiveSubscription } = useUserData()
|
||||||
|
const ensureCanCreatePlaylist = usePlaylistCreationGuard()
|
||||||
const { setTooltip } = useMinuit()
|
const { setTooltip } = useMinuit()
|
||||||
const startAtCreate = props?.payload?.startAtCreate || false
|
const startAtCreate = props?.payload?.startAtCreate || false
|
||||||
const [mode, setMode] = useState(startAtCreate ? 'create' : 'select')
|
const [mode, setMode] = useState(startAtCreate && hasActiveSubscription ? 'create' : 'select')
|
||||||
const [visible, setVisible] = useState(true)
|
const [visible, setVisible] = useState(true)
|
||||||
const providerContext = useProviderContext()
|
const providerContext = useProviderContext()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMode(startAtCreate ? 'create' : 'select')
|
setMode(startAtCreate && hasActiveSubscription ? 'create' : 'select')
|
||||||
}, [startAtCreate])
|
}, [hasActiveSubscription, startAtCreate])
|
||||||
|
|
||||||
const sanitizedPlaylists = useMemo(
|
const sanitizedPlaylists = useMemo(
|
||||||
() => (Array.isArray(userPlaylists) ? userPlaylists : []),
|
() => (Array.isArray(userPlaylists) ? userPlaylists : []),
|
||||||
@@ -44,7 +46,7 @@ const PlaylistModal = (props) => {
|
|||||||
setVisible(true)
|
setVisible(true)
|
||||||
}, [props?.sheetId, props?.payload])
|
}, [props?.sheetId, props?.payload])
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = useCallback(() => {
|
||||||
setVisible(false)
|
setVisible(false)
|
||||||
props?.payload?.onClose?.()
|
props?.payload?.onClose?.()
|
||||||
|
|
||||||
@@ -56,8 +58,18 @@ const PlaylistModal = (props) => {
|
|||||||
// close event manually to let the SheetProvider unmount it.
|
// close event manually to let the SheetProvider unmount it.
|
||||||
actionSheetEventManager.publish(`onclose_${sheetId}`, undefined, providerContext)
|
actionSheetEventManager.publish(`onclose_${sheetId}`, undefined, providerContext)
|
||||||
}
|
}
|
||||||
|
}, [props?.payload, props?.sheetId, providerContext])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!startAtCreate || hasActiveSubscription) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ensureCanCreatePlaylist()) {
|
||||||
|
hideModal()
|
||||||
|
}
|
||||||
|
}, [ensureCanCreatePlaylist, hasActiveSubscription, hideModal, startAtCreate])
|
||||||
|
|
||||||
const handleValidateSelection = async () => {
|
const handleValidateSelection = async () => {
|
||||||
if (!selectedId) return
|
if (!selectedId) return
|
||||||
try {
|
try {
|
||||||
@@ -84,6 +96,10 @@ const PlaylistModal = (props) => {
|
|||||||
|
|
||||||
const handleCreatePlaylist = async () => {
|
const handleCreatePlaylist = async () => {
|
||||||
if (!playlist?.trim()?.length || !currentUID) return
|
if (!playlist?.trim()?.length || !currentUID) return
|
||||||
|
if (!ensureCanCreatePlaylist()) {
|
||||||
|
hideModal()
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
setIsCreating(true)
|
setIsCreating(true)
|
||||||
const name = playlist.trim()
|
const name = playlist.trim()
|
||||||
@@ -103,7 +119,7 @@ const PlaylistModal = (props) => {
|
|||||||
musics: props?.payload?.projectId ? [props.payload.projectId] : [],
|
musics: props?.payload?.projectId ? [props.payload.projectId] : [],
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
})
|
}, { hasActiveSubscription })
|
||||||
|
|
||||||
setTooltip({
|
setTooltip({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@@ -152,6 +168,10 @@ const PlaylistModal = (props) => {
|
|||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
if (!ensureCanCreatePlaylist()) {
|
||||||
|
hideModal()
|
||||||
|
return
|
||||||
|
}
|
||||||
setPlaylist('')
|
setPlaylist('')
|
||||||
setMode('create')
|
setMode('create')
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import useMinuit from 'react-native-minuit/src/hooks/useMinuit'
|
||||||
|
import { Routes } from '../navigation'
|
||||||
|
import { navigate } from '../navigation/NavigationService'
|
||||||
|
import { useUserData } from '../providers/UserDataProvider'
|
||||||
|
import {
|
||||||
|
canCreatePlaylist,
|
||||||
|
PLAYLIST_CREATION_SUBSCRIPTION_MESSAGE,
|
||||||
|
} from '../utils/playlistCreationAccess'
|
||||||
|
|
||||||
|
const usePlaylistCreationGuard = () => {
|
||||||
|
const { hasActiveSubscription } = useUserData() || {}
|
||||||
|
const { setTooltip } = useMinuit()
|
||||||
|
|
||||||
|
return useCallback(() => {
|
||||||
|
if (canCreatePlaylist(hasActiveSubscription)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
setTooltip?.({
|
||||||
|
type: 'error',
|
||||||
|
text: PLAYLIST_CREATION_SUBSCRIPTION_MESSAGE,
|
||||||
|
})
|
||||||
|
navigate(Routes.Payments)
|
||||||
|
return false
|
||||||
|
}, [hasActiveSubscription, setTooltip])
|
||||||
|
}
|
||||||
|
|
||||||
|
export default usePlaylistCreationGuard
|
||||||
+1
-1
@@ -122,7 +122,7 @@ export default ({
|
|||||||
|
|
||||||
const handleCoinPress = React.useCallback(() => {
|
const handleCoinPress = React.useCallback(() => {
|
||||||
if (!showCoinBadge) return
|
if (!showCoinBadge) return
|
||||||
navigate(Routes.Payments)
|
navigate(Routes.Payments, { pack: 'packs' })
|
||||||
}, [showCoinBadge])
|
}, [showCoinBadge])
|
||||||
|
|
||||||
const handleCloseCoinModal = React.useCallback(() => {
|
const handleCloseCoinModal = React.useCallback(() => {
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ const Home = ({ navigation, route }) => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleOpenCoinModal = useCallback(() => {
|
const handleOpenCoinModal = useCallback(() => {
|
||||||
navigate(Routes.Payments)
|
navigate(Routes.Payments, { pack: 'packs' })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const stageCardContainerStyle = isWeb ? styles.cardsGrid : styles.cardsStack
|
const stageCardContainerStyle = isWeb ? styles.cardsGrid : styles.cardsStack
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from 'react'
|
|||||||
import { Image, Platform, Pressable, Text, View } from 'react-native'
|
import { Image, Platform, Pressable, Text, View } from 'react-native'
|
||||||
import { SheetManager } from 'react-native-actions-sheet'
|
import { SheetManager } from 'react-native-actions-sheet'
|
||||||
import { background, icons } from '../../assets'
|
import { background, icons } from '../../assets'
|
||||||
|
import usePlaylistCreationGuard from '../../hooks/usePlaylistCreationGuard'
|
||||||
import Page from '../../layouts/Page'
|
import Page from '../../layouts/Page'
|
||||||
import { Routes } from '../../navigation'
|
import { Routes } from '../../navigation'
|
||||||
import { navigate } from '../../navigation/NavigationService'
|
import { navigate } from '../../navigation/NavigationService'
|
||||||
@@ -12,15 +13,23 @@ import { FONT_FAMILY } from '../../styles/Fonts'
|
|||||||
import { size } from '../../styles/Style'
|
import { size } from '../../styles/Style'
|
||||||
const AllMyPlaylist = () => {
|
const AllMyPlaylist = () => {
|
||||||
const { userPlaylists = [] } = useUser()
|
const { userPlaylists = [] } = useUser()
|
||||||
|
const ensureCanCreatePlaylist = usePlaylistCreationGuard()
|
||||||
|
|
||||||
|
const handleCreatePlaylistPress = () => {
|
||||||
|
if (!ensureCanCreatePlaylist()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SheetManager.show('Playlist', { payload: { startAtCreate: true } })
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
headerType="NAVIGATION"
|
headerType="NAVIGATION"
|
||||||
backgroundImg={background.libraryBG}
|
backgroundImg={background.libraryBG}
|
||||||
title="Mes Playlists"
|
title="Mes Playlists"
|
||||||
rightComponent={() => (
|
rightComponent={() => (
|
||||||
<Pressable
|
<Pressable onPress={handleCreatePlaylistPress}>
|
||||||
onPress={() => SheetManager.show('Playlist', { payload: { startAtCreate: true } })}
|
|
||||||
>
|
|
||||||
<Image source={icons.add} style={size({ size: 20 })} resizeMode="contain" />
|
<Image source={icons.add} style={size({ size: 20 })} resizeMode="contain" />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { background, icons } from '../../assets'
|
|||||||
import MoreMenu from '../../components/MoreMenu'
|
import MoreMenu from '../../components/MoreMenu'
|
||||||
import { playlistsRef, projectsRef } from '../../config/firebase'
|
import { playlistsRef, projectsRef } from '../../config/firebase'
|
||||||
import useDataFromArrayDocId from '../../hooks/useDataFromArrayId'
|
import useDataFromArrayDocId from '../../hooks/useDataFromArrayId'
|
||||||
|
import usePlaylistCreationGuard from '../../hooks/usePlaylistCreationGuard'
|
||||||
import useDataFromRef from '../../hooks/useDataFromRef'
|
import useDataFromRef from '../../hooks/useDataFromRef'
|
||||||
import useNavigateToMusicDetails from '../../hooks/useNavigateToMusicDetails'
|
import useNavigateToMusicDetails from '../../hooks/useNavigateToMusicDetails'
|
||||||
import usePlayer from '../../hooks/usePlayer'
|
import usePlayer from '../../hooks/usePlayer'
|
||||||
@@ -22,6 +23,7 @@ const AllMyPlaylist = ({ route }) => {
|
|||||||
const { playListId } = route?.params || {}
|
const { playListId } = route?.params || {}
|
||||||
const [, setTooltip] = useGlobal('_tooltip')
|
const [, setTooltip] = useGlobal('_tooltip')
|
||||||
const { userPlaylists = [] } = useUser()
|
const { userPlaylists = [] } = useUser()
|
||||||
|
const ensureCanCreatePlaylist = usePlaylistCreationGuard()
|
||||||
const [selectedPlaylistId, setSelectedPlaylistId] = useState(playListId || null)
|
const [selectedPlaylistId, setSelectedPlaylistId] = useState(playListId || null)
|
||||||
const { data: playlist } = useDataFromRef({
|
const { data: playlist } = useDataFromRef({
|
||||||
ref: selectedPlaylistId ? playlistsRef.doc(selectedPlaylistId) : null,
|
ref: selectedPlaylistId ? playlistsRef.doc(selectedPlaylistId) : null,
|
||||||
@@ -140,6 +142,16 @@ const AllMyPlaylist = ({ route }) => {
|
|||||||
})
|
})
|
||||||
}, [playlist?.musics, selectedPlaylistId])
|
}, [playlist?.musics, selectedPlaylistId])
|
||||||
|
|
||||||
|
const handleCreatePlaylistPress = useCallback(() => {
|
||||||
|
if (!ensureCanCreatePlaylist()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SheetManager.show('Playlist', {
|
||||||
|
payload: { startAtCreate: true },
|
||||||
|
})
|
||||||
|
}, [ensureCanCreatePlaylist])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
headerType="NAVIGATION"
|
headerType="NAVIGATION"
|
||||||
@@ -233,13 +245,7 @@ const AllMyPlaylist = ({ route }) => {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
<Pressable
|
<Pressable onPress={handleCreatePlaylistPress}>
|
||||||
onPress={() =>
|
|
||||||
SheetManager.show('Playlist', {
|
|
||||||
payload: { startAtCreate: true },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { arrayUnion, playlistsRef } from '../../../config/firebase'
|
import { arrayUnion, playlistsRef } from '../../../config/firebase'
|
||||||
|
import { assertCanCreatePlaylist } from '../../../utils/playlistCreationAccess'
|
||||||
|
|
||||||
export const createPlaylist = async (payload = {}) => {
|
export const createPlaylist = async (payload = {}, options = {}) => {
|
||||||
try {
|
try {
|
||||||
|
assertCanCreatePlaylist(options?.hasActiveSubscription)
|
||||||
|
|
||||||
const createdBy = payload?.createdBy
|
const createdBy = payload?.createdBy
|
||||||
const name = payload?.name?.trim?.()
|
const name = payload?.name?.trim?.()
|
||||||
const musics = Array.isArray(payload?.musics) ? payload.musics : []
|
const musics = Array.isArray(payload?.musics) ? payload.musics : []
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { SheetManager } from 'react-native-actions-sheet'
|
|||||||
import { icons } from '../../../assets'
|
import { icons } from '../../../assets'
|
||||||
import GradientButton from '../../../components/GradientButton'
|
import GradientButton from '../../../components/GradientButton'
|
||||||
import useLayoutType from '../../../hooks/useLayoutType'
|
import useLayoutType from '../../../hooks/useLayoutType'
|
||||||
|
import usePlaylistCreationGuard from '../../../hooks/usePlaylistCreationGuard'
|
||||||
import { Routes } from '../../../navigation'
|
import { Routes } from '../../../navigation'
|
||||||
import { navigate } from '../../../navigation/NavigationService'
|
import { navigate } from '../../../navigation/NavigationService'
|
||||||
import { useUser } from '../../../providers/UserDataProvider'
|
import { useUser } from '../../../providers/UserDataProvider'
|
||||||
@@ -16,16 +17,24 @@ const MyPlaylist = () => {
|
|||||||
const { userPlaylists = [] } = useUser()
|
const { userPlaylists = [] } = useUser()
|
||||||
const playlists = Array.isArray(userPlaylists) ? userPlaylists.slice(0, 2) : []
|
const playlists = Array.isArray(userPlaylists) ? userPlaylists.slice(0, 2) : []
|
||||||
const { isWeb } = useLayoutType()
|
const { isWeb } = useLayoutType()
|
||||||
|
const ensureCanCreatePlaylist = usePlaylistCreationGuard()
|
||||||
const hasPlaylists = playlists.length > 0
|
const hasPlaylists = playlists.length > 0
|
||||||
return (
|
|
||||||
<CardContainer
|
const handleCreatePlaylistPress = () => {
|
||||||
label="Mes Playlists"
|
if (!ensureCanCreatePlaylist()) {
|
||||||
onPress={() => navigate(Routes.AllMyPlaylist)}
|
return
|
||||||
onPressPlus={() =>
|
}
|
||||||
|
|
||||||
SheetManager.show('Playlist', {
|
SheetManager.show('Playlist', {
|
||||||
payload: { startAtCreate: true },
|
payload: { startAtCreate: true },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardContainer
|
||||||
|
label="Mes Playlists"
|
||||||
|
onPress={() => navigate(Routes.AllMyPlaylist)}
|
||||||
|
onPressPlus={handleCreatePlaylistPress}
|
||||||
>
|
>
|
||||||
<View style={{ gap: 8 }}>
|
<View style={{ gap: 8 }}>
|
||||||
{hasPlaylists ? (
|
{hasPlaylists ? (
|
||||||
@@ -91,11 +100,7 @@ const MyPlaylist = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
<GradientButton
|
<GradientButton
|
||||||
containerStyle={{ padding: 2 }}
|
containerStyle={{ padding: 2 }}
|
||||||
onPress={() =>
|
onPress={handleCreatePlaylistPress}
|
||||||
SheetManager.show('Playlist', {
|
|
||||||
payload: { startAtCreate: true },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
title=" Créer une playlist"
|
title=" Créer une playlist"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
+104
-16
@@ -183,6 +183,55 @@ function SubscriptionCard({ plan, selected, onSelect, isAnnual }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CoinPackCard({ pack, onBuy, isProcessing }) {
|
||||||
|
const rawAmount = pack?.unitAmount ?? pack?.amount ?? pack?.price ?? 0
|
||||||
|
const formattedPrice = formatCurrency(rawAmount, pack?.currency)
|
||||||
|
const coins = Number(pack?.coinAmount ?? pack?.metadata?.coins ?? 0)
|
||||||
|
const packKey = getCoinPackKey(pack)
|
||||||
|
const details = packKey ? COIN_PACK_DETAILS[packKey] : null
|
||||||
|
const displayName = details?.label || pack?.name || 'Pack'
|
||||||
|
const creditsText = details?.creditsDisplay
|
||||||
|
const displayPrice = details?.priceDisplay || formattedPrice || `${rawAmount / 100}€`
|
||||||
|
const isPopular = details?.popular === true
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.cardWrapper}>
|
||||||
|
<BlurView intensity={18} tint="dark" style={styles.cardBlur}>
|
||||||
|
<View style={styles.cardContent}>
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<Text style={styles.planName}>{displayName}</Text>
|
||||||
|
{creditsText ? (
|
||||||
|
<Text style={styles.coinPackAmount}>{creditsText}</Text>
|
||||||
|
) : (
|
||||||
|
<CreditAmount value={coins} iconSize={24} textStyle={styles.coinPackAmountValue} />
|
||||||
|
)}
|
||||||
|
{details?.disclaimer ? (
|
||||||
|
<Text style={styles.packDisclaimer}>{details.disclaimer}</Text>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.priceBlock}>
|
||||||
|
{displayPrice ? <Text style={styles.priceValue}>{displayPrice}</Text> : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{isPopular ? (
|
||||||
|
<View style={styles.popularBadge}>
|
||||||
|
<Text style={styles.popularBadgeText}>Le plus populaire !</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<GradientButton
|
||||||
|
title={isProcessing ? 'Redirection...' : 'Acheter ce pack'}
|
||||||
|
onPress={() => onBuy(pack)}
|
||||||
|
disabled={isProcessing}
|
||||||
|
gradientStyle={styles.coinPackButtonGradient}
|
||||||
|
/>
|
||||||
|
</BlurView>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const PRICE_PRIORITY_BY_PERIOD = {
|
const PRICE_PRIORITY_BY_PERIOD = {
|
||||||
monthly: [
|
monthly: [
|
||||||
'price_1SPgitCzf2o5bDRdbnhLFx6f', // Starter
|
'price_1SPgitCzf2o5bDRdbnhLFx6f', // Starter
|
||||||
@@ -248,6 +297,7 @@ export default function Subscriptions() {
|
|||||||
const rawPack = route?.params?.pack
|
const rawPack = route?.params?.pack
|
||||||
return typeof rawPack === 'string' ? rawPack.toLowerCase() : null
|
return typeof rawPack === 'string' ? rawPack.toLowerCase() : null
|
||||||
}, [route?.params?.pack])
|
}, [route?.params?.pack])
|
||||||
|
const isCoinPackView = isMobile && initialPack === 'packs'
|
||||||
|
|
||||||
const initialSubscriptionPack = initialPack && initialPack !== 'packs' ? initialPack : null
|
const initialSubscriptionPack = initialPack && initialPack !== 'packs' ? initialPack : null
|
||||||
|
|
||||||
@@ -285,6 +335,7 @@ export default function Subscriptions() {
|
|||||||
const hasAnyPlan = availablePeriods.length > 0
|
const hasAnyPlan = availablePeriods.length > 0
|
||||||
const isLoadingPlans = isCatalogLoading && !hasAnyPlan
|
const isLoadingPlans = isCatalogLoading && !hasAnyPlan
|
||||||
const combinedErrorMessage = errorMessage || catalogError
|
const combinedErrorMessage = errorMessage || catalogError
|
||||||
|
const screenTitle = isCoinPackView ? COIN_PACK_SECTION_TITLE : 'Rejoignez le club MusicLand'
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
initialPackHandledRef.current = false
|
initialPackHandledRef.current = false
|
||||||
@@ -410,9 +461,9 @@ export default function Subscriptions() {
|
|||||||
|
|
||||||
const mobileListContentInset = React.useMemo(
|
const mobileListContentInset = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
paddingBottom: mobileActionSafePadding + gutters * 3,
|
paddingBottom: isCoinPackView ? gutters * 2 : mobileActionSafePadding + gutters * 3,
|
||||||
}),
|
}),
|
||||||
[mobileActionSafePadding]
|
[isCoinPackView, mobileActionSafePadding]
|
||||||
)
|
)
|
||||||
const handleBackPress = React.useCallback(() => {
|
const handleBackPress = React.useCallback(() => {
|
||||||
goBack()
|
goBack()
|
||||||
@@ -455,7 +506,7 @@ export default function Subscriptions() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={styles.title}>Rejoignez le club MusicLand</Text>
|
<Text style={styles.title}>{screenTitle}</Text>
|
||||||
<View style={styles.balanceContainer}>
|
<View style={styles.balanceContainer}>
|
||||||
<Text style={styles.balanceLabel}>Votre solde actuel :</Text>
|
<Text style={styles.balanceLabel}>Votre solde actuel :</Text>
|
||||||
<CreditAmount value={currentUserData?.coins || 0} iconSize={20} showPlus={false} />
|
<CreditAmount value={currentUserData?.coins || 0} iconSize={20} showPlus={false} />
|
||||||
@@ -464,9 +515,13 @@ export default function Subscriptions() {
|
|||||||
{combinedErrorMessage ? <Text style={styles.errorText}>{combinedErrorMessage}</Text> : null}
|
{combinedErrorMessage ? <Text style={styles.errorText}>{combinedErrorMessage}</Text> : null}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}, [combinedErrorMessage])
|
}, [combinedErrorMessage, currentUserData?.coins, screenTitle])
|
||||||
|
|
||||||
const renderSegmentedControl = React.useCallback(() => {
|
const renderSegmentedControl = React.useCallback(() => {
|
||||||
|
if (isCoinPackView) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const segments = [
|
const segments = [
|
||||||
{ key: 'monthly', label: 'Mensuel', plans: normalizedPlansByPeriod?.monthly },
|
{ key: 'monthly', label: 'Mensuel', plans: normalizedPlansByPeriod?.monthly },
|
||||||
{ key: 'annual', label: 'Annuel', plans: normalizedPlansByPeriod?.annual },
|
{ key: 'annual', label: 'Annuel', plans: normalizedPlansByPeriod?.annual },
|
||||||
@@ -504,7 +559,7 @@ export default function Subscriptions() {
|
|||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}, [handlePeriodChange, isMobile, normalizedPlansByPeriod, selectedPeriodKey])
|
}, [handlePeriodChange, isCoinPackView, isMobile, normalizedPlansByPeriod, selectedPeriodKey])
|
||||||
|
|
||||||
const renderMobilePlanItem = React.useCallback(
|
const renderMobilePlanItem = React.useCallback(
|
||||||
({ item }) => (
|
({ item }) => (
|
||||||
@@ -520,30 +575,51 @@ export default function Subscriptions() {
|
|||||||
[handleSelect, selectedPeriodKey, selectedPriceId]
|
[handleSelect, selectedPeriodKey, selectedPriceId]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const renderMobileCoinPackItem = React.useCallback(
|
||||||
|
({ item }) => (
|
||||||
|
<View style={styles.mobileCard}>
|
||||||
|
<CoinPackCard
|
||||||
|
pack={item}
|
||||||
|
onBuy={handleBuyCredits}
|
||||||
|
isProcessing={processingPriceId === item.productId}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
),
|
||||||
|
[handleBuyCredits, processingPriceId]
|
||||||
|
)
|
||||||
|
|
||||||
const renderMobileEmptyComponent = React.useCallback(() => {
|
const renderMobileEmptyComponent = React.useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.mobileEmptyWrapper}>
|
<View style={styles.mobileEmptyWrapper}>
|
||||||
{isLoadingPlans ? (
|
{(isCoinPackView ? isCatalogLoading && !coinPacks.length : isLoadingPlans) ? (
|
||||||
<ActivityIndicator color={Palette.white} />
|
<ActivityIndicator color={Palette.white} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.emptyState}>Aucun abonnement Stripe disponible pour le moment.</Text>
|
<Text style={styles.emptyState}>
|
||||||
|
{isCoinPackView
|
||||||
|
? "Aucun pack de crédits n'est disponible pour le moment."
|
||||||
|
: 'Aucun abonnement Stripe disponible pour le moment.'}
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}, [isLoadingPlans])
|
}, [coinPacks.length, isCatalogLoading, isCoinPackView, isLoadingPlans])
|
||||||
|
|
||||||
const renderMobileFooterComponent = React.useCallback(() => {
|
const renderMobileFooterComponent = React.useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.mobileFooter}>
|
<View style={styles.mobileFooter}>
|
||||||
{isLoadingPlans && currentPlanCount > 0 ? (
|
{(isCoinPackView ? isCatalogLoading && coinPacks.length > 0 : isLoadingPlans && currentPlanCount > 0) ? (
|
||||||
<View style={styles.inlineLoader}>
|
<View style={styles.inlineLoader}>
|
||||||
<ActivityIndicator color={Palette.white} size="small" />
|
<ActivityIndicator color={Palette.white} size="small" />
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
<Text style={styles.disclaimer}>{SUBSCRIPTION_DISCLAIMER}</Text>
|
<Text style={styles.disclaimer}>
|
||||||
|
{isCoinPackView
|
||||||
|
? 'Les crédits sont ajoutés dès que le paiement Stripe est validé.'
|
||||||
|
: SUBSCRIPTION_DISCLAIMER}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}, [currentPlanCount, isLoadingPlans])
|
}, [coinPacks.length, currentPlanCount, isCatalogLoading, isCoinPackView, isLoadingPlans])
|
||||||
|
|
||||||
const renderMobileTopSticky = React.useCallback(() => {
|
const renderMobileTopSticky = React.useCallback(() => {
|
||||||
return (
|
return (
|
||||||
@@ -566,6 +642,7 @@ export default function Subscriptions() {
|
|||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}, [actionButtonTitle, handleCheckout, isActionDisabled, mobileActionSafePadding])
|
}, [actionButtonTitle, handleCheckout, isActionDisabled, mobileActionSafePadding])
|
||||||
|
|
||||||
const renderWebBackButton = React.useCallback(() => {
|
const renderWebBackButton = React.useCallback(() => {
|
||||||
if (isMobile) return null
|
if (isMobile) return null
|
||||||
return (
|
return (
|
||||||
@@ -586,7 +663,7 @@ export default function Subscriptions() {
|
|||||||
<View style={styles.root}>
|
<View style={styles.root}>
|
||||||
<Page
|
<Page
|
||||||
headerType="NAVIGATE"
|
headerType="NAVIGATE"
|
||||||
title="Abonnements"
|
title={isCoinPackView ? COIN_PACK_SECTION_TITLE : 'Abonnements'}
|
||||||
scrollEnabled={isWeb}
|
scrollEnabled={isWeb}
|
||||||
width={isWeb ? 960 : undefined}
|
width={isWeb ? 960 : undefined}
|
||||||
containerStyle={styles.page}
|
containerStyle={styles.page}
|
||||||
@@ -601,9 +678,9 @@ export default function Subscriptions() {
|
|||||||
<View style={[styles.content, isMobile && styles.mobileContent]}>
|
<View style={[styles.content, isMobile && styles.mobileContent]}>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={currentPlans}
|
data={isCoinPackView ? coinPacks : currentPlans}
|
||||||
keyExtractor={(plan) => plan.priceId}
|
keyExtractor={(item) => item.productId || item.priceId}
|
||||||
renderItem={renderMobilePlanItem}
|
renderItem={isCoinPackView ? renderMobileCoinPackItem : renderMobilePlanItem}
|
||||||
style={styles.mobileList}
|
style={styles.mobileList}
|
||||||
contentContainerStyle={[styles.mobileListContent, mobileListContentInset]}
|
contentContainerStyle={[styles.mobileListContent, mobileListContentInset]}
|
||||||
ListEmptyComponent={renderMobileEmptyComponent}
|
ListEmptyComponent={renderMobileEmptyComponent}
|
||||||
@@ -716,7 +793,7 @@ export default function Subscriptions() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Page>
|
</Page>
|
||||||
{isMobile ? renderMobileBottomActions() : null}
|
{isMobile && !isCoinPackView ? renderMobileBottomActions() : null}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1162,6 +1239,17 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: isWeb ? 22 : 20,
|
fontSize: isWeb ? 22 : 20,
|
||||||
color: Palette.white,
|
color: Palette.white,
|
||||||
},
|
},
|
||||||
|
coinPackAmount: {
|
||||||
|
fontFamily: FONT_FAMILY.InterBold,
|
||||||
|
fontSize: 16,
|
||||||
|
color: Palette.white,
|
||||||
|
},
|
||||||
|
coinPackAmountValue: {
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
coinPackButtonGradient: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
period: {
|
period: {
|
||||||
fontFamily: FONT_FAMILY.InterRegular,
|
fontFamily: FONT_FAMILY.InterRegular,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export const PLAYLIST_CREATION_SUBSCRIPTION_MESSAGE =
|
||||||
|
'La création de playlist est réservée aux abonnés.'
|
||||||
|
|
||||||
|
export const canCreatePlaylist = (hasActiveSubscription = false) => hasActiveSubscription === true
|
||||||
|
|
||||||
|
export const assertCanCreatePlaylist = (hasActiveSubscription = false) => {
|
||||||
|
if (!canCreatePlaylist(hasActiveSubscription)) {
|
||||||
|
throw new Error(PLAYLIST_CREATION_SUBSCRIPTION_MESSAGE)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user