feat(ui): refine club, packs, and download flows
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { Platform, Pressable, StyleSheet } from 'react-native'
|
||||
import CreditAmount from './CreditAmount'
|
||||
import CoinPackModal from './modal/CoinPackModal'
|
||||
import { useUser } from '../providers/UserDataProvider'
|
||||
import { Palette } from '../styles'
|
||||
import { FONT_FAMILY } from '../styles/Fonts'
|
||||
import { openCoinPackModal } from '../utils/coinPackModal'
|
||||
import { navigate } from '../navigation/NavigationService'
|
||||
import { Routes } from '../navigation/Routes'
|
||||
|
||||
const MobileCoinBadge = ({ style, textStyle, iconSize = 20, iconPosition = 'left' }) => {
|
||||
const { currentUID, currentUserData } = useUser() || {}
|
||||
const [isModalVisible, setModalVisible] = useState(false)
|
||||
|
||||
const coinBalance = useMemo(() => {
|
||||
const value = currentUserData?.coins
|
||||
@@ -26,15 +25,7 @@ const MobileCoinBadge = ({ style, textStyle, iconSize = 20, iconPosition = 'left
|
||||
}, [currentUserData?.coins])
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
if (Platform.OS === 'web') {
|
||||
openCoinPackModal()
|
||||
return
|
||||
}
|
||||
setModalVisible(true)
|
||||
}, [])
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setModalVisible(false)
|
||||
navigate(Routes.Payments)
|
||||
}, [])
|
||||
|
||||
if (!currentUID || Platform.OS === 'web') {
|
||||
@@ -42,18 +33,15 @@ const MobileCoinBadge = ({ style, textStyle, iconSize = 20, iconPosition = 'left
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Pressable onPress={handlePress} accessibilityRole="button" style={[styles.container, style]}>
|
||||
<CreditAmount
|
||||
value={coinBalance}
|
||||
style={styles.content}
|
||||
textStyle={[styles.text, textStyle]}
|
||||
iconSize={iconSize}
|
||||
iconPosition={iconPosition}
|
||||
/>
|
||||
</Pressable>
|
||||
<CoinPackModal visible={isModalVisible} onClose={handleClose} />
|
||||
</>
|
||||
<Pressable onPress={handlePress} accessibilityRole="button" style={[styles.container, style]}>
|
||||
<CreditAmount
|
||||
value={coinBalance}
|
||||
style={styles.content}
|
||||
textStyle={[styles.text, textStyle]}
|
||||
iconSize={iconSize}
|
||||
iconPosition={iconPosition}
|
||||
/>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ import { FONT_FAMILY } from '../../styles/Fonts'
|
||||
import { isWeb } from '../../hooks/useLayoutType'
|
||||
import CreditAmount from '../CreditAmount'
|
||||
import { useStripe } from '../../providers/StripeProvider'
|
||||
import {
|
||||
COIN_PACK_DETAILS,
|
||||
COIN_PACK_SECTION_TITLE,
|
||||
getCoinPackKey,
|
||||
} from '../../utils/coinPackDisplay'
|
||||
|
||||
const WEB_MODAL_MAX_WIDTH = 820
|
||||
const formatCurrency = (amount, currency = 'eur') => {
|
||||
@@ -41,37 +46,10 @@ const formatCurrency = (amount, currency = 'eur') => {
|
||||
return `${normalized.toFixed(2)} ${upperCurrency}`
|
||||
}
|
||||
|
||||
const FALLBACK_BASE_PRICE_PER_COIN = 12 // ~0,12€ par jeton (valeur indicatif pour l'affichage)
|
||||
const FALLBACK_BASE_PRICE_PER_COIN = 12 // ~0,12€ par credit (valeur indicative pour l'affichage)
|
||||
const FALLBACK_DISCOUNT_STEPS = [0, 12, 18, 26, 32]
|
||||
const STATIC_DISCOUNTS = [0, 30, 50]
|
||||
|
||||
const COIN_PACK_DETAILS = {
|
||||
starter: {
|
||||
label: 'Pack starter',
|
||||
creditsDisplay: '50 Crédits',
|
||||
disclaimer: '*Non-Eligible au concours Chanson/Vidéo',
|
||||
},
|
||||
medium: {
|
||||
label: 'Pack Medium',
|
||||
creditsDisplay: '100 Crédits + 50 offerts',
|
||||
disclaimer: '*Non-Eligible au concours Chanson/Vidéo',
|
||||
},
|
||||
premium: {
|
||||
label: 'Pack Premium',
|
||||
creditsDisplay: '150 Crédits + 100 offerts',
|
||||
disclaimer: '*Non-Eligible au concours Chanson/Vidéo',
|
||||
},
|
||||
}
|
||||
|
||||
const getCoinPackKey = (pack) => {
|
||||
const name = (pack?.name || '').toLowerCase()
|
||||
if (name.includes('starter')) return 'starter'
|
||||
if (name.includes('medium')) return 'medium'
|
||||
if (name.includes('premium') || name.includes('prenium') || name.includes('creator'))
|
||||
return 'premium'
|
||||
return null
|
||||
}
|
||||
|
||||
const computeCoinPackPricing = (packs = []) => {
|
||||
if (!Array.isArray(packs) || !packs.length) {
|
||||
return {}
|
||||
@@ -191,6 +169,8 @@ function CoinPackCard({ pack, selected, pricingDetail, onSelect, staticDiscountP
|
||||
const details = packKey ? COIN_PACK_DETAILS[packKey] : null
|
||||
const displayName = details?.label || pack.name
|
||||
const creditsText = details?.creditsDisplay
|
||||
const displayPrice = details?.priceDisplay || formattedPrice
|
||||
const isPopular = details?.popular === true
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
@@ -235,8 +215,13 @@ function CoinPackCard({ pack, selected, pricingDetail, onSelect, staticDiscountP
|
||||
) : null}
|
||||
</View>
|
||||
<View style={styles.priceBlock}>
|
||||
{formattedPrice ? <Text style={styles.packPrice}>{formattedPrice}</Text> : null}
|
||||
{displayPrice ? <Text style={styles.packPrice}>{displayPrice}</Text> : null}
|
||||
</View>
|
||||
{isPopular ? (
|
||||
<View style={styles.popularBadge}>
|
||||
<Text style={styles.popularBadgeText}>Le plus populaire !</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</BlurView>
|
||||
</Pressable>
|
||||
)
|
||||
@@ -324,7 +309,7 @@ const CoinPackModal = ({ visible, onClose }) => {
|
||||
|
||||
if (!coinPacks.length) {
|
||||
return (
|
||||
<Text style={styles.emptyState}>Aucun pack de pièces n'est disponible pour le moment.</Text>
|
||||
<Text style={styles.emptyState}>Aucun pack de crédits n'est disponible pour le moment.</Text>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,9 +350,9 @@ const CoinPackModal = ({ visible, onClose }) => {
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Acheter des pièces</Text>
|
||||
<Text style={styles.title}>{COIN_PACK_SECTION_TITLE}</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
Choisis un pack et finalise ton achat pour continuer.
|
||||
Crée tes chansons et tes vidéos.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -378,8 +363,7 @@ const CoinPackModal = ({ visible, onClose }) => {
|
||||
<View style={styles.content}>{renderContent()}</View>
|
||||
|
||||
<Text style={styles.indicativeNote}>
|
||||
Tarifs indicatifs : les prix et quantités de jetons sont amenés à évoluer. Les remises
|
||||
sont affichées vs le pack de base pour mieux valoriser les offres volumineuses.
|
||||
Téléchargement gratuit de tes créations.
|
||||
</Text>
|
||||
|
||||
<View style={styles.actions}>
|
||||
@@ -394,7 +378,7 @@ const CoinPackModal = ({ visible, onClose }) => {
|
||||
</View>
|
||||
|
||||
<Text style={styles.disclaimer}>
|
||||
Les pièces sont créditées dès que le paiement Stripe est validé.
|
||||
Les crédits sont ajoutés dès que le paiement Stripe est validé.
|
||||
</Text>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
@@ -548,9 +532,24 @@ const styles = StyleSheet.create({
|
||||
textAlign: 'center',
|
||||
},
|
||||
priceBlock: {
|
||||
marginTop: 'auto',
|
||||
gap: 2,
|
||||
alignItems: 'center',
|
||||
},
|
||||
popularBadge: {
|
||||
alignSelf: 'center',
|
||||
marginTop: 12,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 999,
|
||||
backgroundColor: Palette.primary,
|
||||
},
|
||||
popularBadgeText: {
|
||||
fontFamily: FONT_FAMILY.InterBold,
|
||||
fontSize: 11,
|
||||
color: Palette.white,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
packPrice: {
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
fontSize: 18,
|
||||
|
||||
Reference in New Issue
Block a user