fix favicon, playlist subscription only
This commit is contained in:
@@ -434,7 +434,7 @@ const Home = ({ navigation, route }) => {
|
||||
}, [])
|
||||
|
||||
const handleOpenCoinModal = useCallback(() => {
|
||||
navigate(Routes.Payments)
|
||||
navigate(Routes.Payments, { pack: 'packs' })
|
||||
}, [])
|
||||
|
||||
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 { SheetManager } from 'react-native-actions-sheet'
|
||||
import { background, icons } from '../../assets'
|
||||
import usePlaylistCreationGuard from '../../hooks/usePlaylistCreationGuard'
|
||||
import Page from '../../layouts/Page'
|
||||
import { Routes } from '../../navigation'
|
||||
import { navigate } from '../../navigation/NavigationService'
|
||||
@@ -12,15 +13,23 @@ import { FONT_FAMILY } from '../../styles/Fonts'
|
||||
import { size } from '../../styles/Style'
|
||||
const AllMyPlaylist = () => {
|
||||
const { userPlaylists = [] } = useUser()
|
||||
const ensureCanCreatePlaylist = usePlaylistCreationGuard()
|
||||
|
||||
const handleCreatePlaylistPress = () => {
|
||||
if (!ensureCanCreatePlaylist()) {
|
||||
return
|
||||
}
|
||||
|
||||
SheetManager.show('Playlist', { payload: { startAtCreate: true } })
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
headerType="NAVIGATION"
|
||||
backgroundImg={background.libraryBG}
|
||||
title="Mes Playlists"
|
||||
rightComponent={() => (
|
||||
<Pressable
|
||||
onPress={() => SheetManager.show('Playlist', { payload: { startAtCreate: true } })}
|
||||
>
|
||||
<Pressable onPress={handleCreatePlaylistPress}>
|
||||
<Image source={icons.add} style={size({ size: 20 })} resizeMode="contain" />
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { background, icons } from '../../assets'
|
||||
import MoreMenu from '../../components/MoreMenu'
|
||||
import { playlistsRef, projectsRef } from '../../config/firebase'
|
||||
import useDataFromArrayDocId from '../../hooks/useDataFromArrayId'
|
||||
import usePlaylistCreationGuard from '../../hooks/usePlaylistCreationGuard'
|
||||
import useDataFromRef from '../../hooks/useDataFromRef'
|
||||
import useNavigateToMusicDetails from '../../hooks/useNavigateToMusicDetails'
|
||||
import usePlayer from '../../hooks/usePlayer'
|
||||
@@ -22,6 +23,7 @@ const AllMyPlaylist = ({ route }) => {
|
||||
const { playListId } = route?.params || {}
|
||||
const [, setTooltip] = useGlobal('_tooltip')
|
||||
const { userPlaylists = [] } = useUser()
|
||||
const ensureCanCreatePlaylist = usePlaylistCreationGuard()
|
||||
const [selectedPlaylistId, setSelectedPlaylistId] = useState(playListId || null)
|
||||
const { data: playlist } = useDataFromRef({
|
||||
ref: selectedPlaylistId ? playlistsRef.doc(selectedPlaylistId) : null,
|
||||
@@ -140,6 +142,16 @@ const AllMyPlaylist = ({ route }) => {
|
||||
})
|
||||
}, [playlist?.musics, selectedPlaylistId])
|
||||
|
||||
const handleCreatePlaylistPress = useCallback(() => {
|
||||
if (!ensureCanCreatePlaylist()) {
|
||||
return
|
||||
}
|
||||
|
||||
SheetManager.show('Playlist', {
|
||||
payload: { startAtCreate: true },
|
||||
})
|
||||
}, [ensureCanCreatePlaylist])
|
||||
|
||||
return (
|
||||
<Page
|
||||
headerType="NAVIGATION"
|
||||
@@ -233,13 +245,7 @@ const AllMyPlaylist = ({ route }) => {
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
SheetManager.show('Playlist', {
|
||||
payload: { startAtCreate: true },
|
||||
})
|
||||
}
|
||||
>
|
||||
<Pressable onPress={handleCreatePlaylistPress}>
|
||||
<View
|
||||
style={{
|
||||
borderRadius: 12,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { arrayUnion, playlistsRef } from '../../../config/firebase'
|
||||
import { assertCanCreatePlaylist } from '../../../utils/playlistCreationAccess'
|
||||
|
||||
export const createPlaylist = async (payload = {}) => {
|
||||
export const createPlaylist = async (payload = {}, options = {}) => {
|
||||
try {
|
||||
assertCanCreatePlaylist(options?.hasActiveSubscription)
|
||||
|
||||
const createdBy = payload?.createdBy
|
||||
const name = payload?.name?.trim?.()
|
||||
const musics = Array.isArray(payload?.musics) ? payload.musics : []
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SheetManager } from 'react-native-actions-sheet'
|
||||
import { icons } from '../../../assets'
|
||||
import GradientButton from '../../../components/GradientButton'
|
||||
import useLayoutType from '../../../hooks/useLayoutType'
|
||||
import usePlaylistCreationGuard from '../../../hooks/usePlaylistCreationGuard'
|
||||
import { Routes } from '../../../navigation'
|
||||
import { navigate } from '../../../navigation/NavigationService'
|
||||
import { useUser } from '../../../providers/UserDataProvider'
|
||||
@@ -16,16 +17,24 @@ const MyPlaylist = () => {
|
||||
const { userPlaylists = [] } = useUser()
|
||||
const playlists = Array.isArray(userPlaylists) ? userPlaylists.slice(0, 2) : []
|
||||
const { isWeb } = useLayoutType()
|
||||
const ensureCanCreatePlaylist = usePlaylistCreationGuard()
|
||||
const hasPlaylists = playlists.length > 0
|
||||
|
||||
const handleCreatePlaylistPress = () => {
|
||||
if (!ensureCanCreatePlaylist()) {
|
||||
return
|
||||
}
|
||||
|
||||
SheetManager.show('Playlist', {
|
||||
payload: { startAtCreate: true },
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<CardContainer
|
||||
label="Mes Playlists"
|
||||
onPress={() => navigate(Routes.AllMyPlaylist)}
|
||||
onPressPlus={() =>
|
||||
SheetManager.show('Playlist', {
|
||||
payload: { startAtCreate: true },
|
||||
})
|
||||
}
|
||||
onPressPlus={handleCreatePlaylistPress}
|
||||
>
|
||||
<View style={{ gap: 8 }}>
|
||||
{hasPlaylists ? (
|
||||
@@ -91,11 +100,7 @@ const MyPlaylist = () => {
|
||||
</Text>
|
||||
<GradientButton
|
||||
containerStyle={{ padding: 2 }}
|
||||
onPress={() =>
|
||||
SheetManager.show('Playlist', {
|
||||
payload: { startAtCreate: true },
|
||||
})
|
||||
}
|
||||
onPress={handleCreatePlaylistPress}
|
||||
title=" Créer une playlist"
|
||||
/>
|
||||
</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 = {
|
||||
monthly: [
|
||||
'price_1SPgitCzf2o5bDRdbnhLFx6f', // Starter
|
||||
@@ -248,6 +297,7 @@ export default function Subscriptions() {
|
||||
const rawPack = route?.params?.pack
|
||||
return typeof rawPack === 'string' ? rawPack.toLowerCase() : null
|
||||
}, [route?.params?.pack])
|
||||
const isCoinPackView = isMobile && initialPack === 'packs'
|
||||
|
||||
const initialSubscriptionPack = initialPack && initialPack !== 'packs' ? initialPack : null
|
||||
|
||||
@@ -285,6 +335,7 @@ 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'
|
||||
|
||||
React.useEffect(() => {
|
||||
initialPackHandledRef.current = false
|
||||
@@ -410,9 +461,9 @@ export default function Subscriptions() {
|
||||
|
||||
const mobileListContentInset = React.useMemo(
|
||||
() => ({
|
||||
paddingBottom: mobileActionSafePadding + gutters * 3,
|
||||
paddingBottom: isCoinPackView ? gutters * 2 : mobileActionSafePadding + gutters * 3,
|
||||
}),
|
||||
[mobileActionSafePadding]
|
||||
[isCoinPackView, mobileActionSafePadding]
|
||||
)
|
||||
const handleBackPress = React.useCallback(() => {
|
||||
goBack()
|
||||
@@ -455,7 +506,7 @@ export default function Subscriptions() {
|
||||
return (
|
||||
<>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Rejoignez le club MusicLand</Text>
|
||||
<Text style={styles.title}>{screenTitle}</Text>
|
||||
<View style={styles.balanceContainer}>
|
||||
<Text style={styles.balanceLabel}>Votre solde actuel :</Text>
|
||||
<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])
|
||||
}, [combinedErrorMessage, currentUserData?.coins, screenTitle])
|
||||
|
||||
const renderSegmentedControl = React.useCallback(() => {
|
||||
if (isCoinPackView) {
|
||||
return null
|
||||
}
|
||||
|
||||
const segments = [
|
||||
{ key: 'monthly', label: 'Mensuel', plans: normalizedPlansByPeriod?.monthly },
|
||||
{ key: 'annual', label: 'Annuel', plans: normalizedPlansByPeriod?.annual },
|
||||
@@ -504,7 +559,7 @@ export default function Subscriptions() {
|
||||
})}
|
||||
</View>
|
||||
)
|
||||
}, [handlePeriodChange, isMobile, normalizedPlansByPeriod, selectedPeriodKey])
|
||||
}, [handlePeriodChange, isCoinPackView, isMobile, normalizedPlansByPeriod, selectedPeriodKey])
|
||||
|
||||
const renderMobilePlanItem = React.useCallback(
|
||||
({ item }) => (
|
||||
@@ -520,30 +575,51 @@ export default function Subscriptions() {
|
||||
[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(() => {
|
||||
return (
|
||||
<View style={styles.mobileEmptyWrapper}>
|
||||
{isLoadingPlans ? (
|
||||
{(isCoinPackView ? isCatalogLoading && !coinPacks.length : isLoadingPlans) ? (
|
||||
<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>
|
||||
)
|
||||
}, [isLoadingPlans])
|
||||
}, [coinPacks.length, isCatalogLoading, isCoinPackView, isLoadingPlans])
|
||||
|
||||
const renderMobileFooterComponent = React.useCallback(() => {
|
||||
return (
|
||||
<View style={styles.mobileFooter}>
|
||||
{isLoadingPlans && currentPlanCount > 0 ? (
|
||||
{(isCoinPackView ? isCatalogLoading && coinPacks.length > 0 : isLoadingPlans && currentPlanCount > 0) ? (
|
||||
<View style={styles.inlineLoader}>
|
||||
<ActivityIndicator color={Palette.white} size="small" />
|
||||
</View>
|
||||
) : 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>
|
||||
)
|
||||
}, [currentPlanCount, isLoadingPlans])
|
||||
}, [coinPacks.length, currentPlanCount, isCatalogLoading, isCoinPackView, isLoadingPlans])
|
||||
|
||||
const renderMobileTopSticky = React.useCallback(() => {
|
||||
return (
|
||||
@@ -566,6 +642,7 @@ export default function Subscriptions() {
|
||||
</View>
|
||||
)
|
||||
}, [actionButtonTitle, handleCheckout, isActionDisabled, mobileActionSafePadding])
|
||||
|
||||
const renderWebBackButton = React.useCallback(() => {
|
||||
if (isMobile) return null
|
||||
return (
|
||||
@@ -586,7 +663,7 @@ export default function Subscriptions() {
|
||||
<View style={styles.root}>
|
||||
<Page
|
||||
headerType="NAVIGATE"
|
||||
title="Abonnements"
|
||||
title={isCoinPackView ? COIN_PACK_SECTION_TITLE : 'Abonnements'}
|
||||
scrollEnabled={isWeb}
|
||||
width={isWeb ? 960 : undefined}
|
||||
containerStyle={styles.page}
|
||||
@@ -601,9 +678,9 @@ export default function Subscriptions() {
|
||||
<View style={[styles.content, isMobile && styles.mobileContent]}>
|
||||
{isMobile ? (
|
||||
<FlatList
|
||||
data={currentPlans}
|
||||
keyExtractor={(plan) => plan.priceId}
|
||||
renderItem={renderMobilePlanItem}
|
||||
data={isCoinPackView ? coinPacks : currentPlans}
|
||||
keyExtractor={(item) => item.productId || item.priceId}
|
||||
renderItem={isCoinPackView ? renderMobileCoinPackItem : renderMobilePlanItem}
|
||||
style={styles.mobileList}
|
||||
contentContainerStyle={[styles.mobileListContent, mobileListContentInset]}
|
||||
ListEmptyComponent={renderMobileEmptyComponent}
|
||||
@@ -716,7 +793,7 @@ export default function Subscriptions() {
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
{isMobile ? renderMobileBottomActions() : null}
|
||||
{isMobile && !isCoinPackView ? renderMobileBottomActions() : null}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -1162,6 +1239,17 @@ const styles = StyleSheet.create({
|
||||
fontSize: isWeb ? 22 : 20,
|
||||
color: Palette.white,
|
||||
},
|
||||
coinPackAmount: {
|
||||
fontFamily: FONT_FAMILY.InterBold,
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
},
|
||||
coinPackAmountValue: {
|
||||
fontSize: 20,
|
||||
},
|
||||
coinPackButtonGradient: {
|
||||
width: '100%',
|
||||
},
|
||||
period: {
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 14,
|
||||
|
||||
Reference in New Issue
Block a user