feat: coin packs

This commit is contained in:
2026-01-15 15:13:03 +01:00
parent 86c3dccf1a
commit 49ff374529
2 changed files with 127 additions and 20 deletions
+62 -13
View File
@@ -35,7 +35,7 @@ const formatCurrency = (amount, currency = 'eur') => {
currency: upperCurrency, currency: upperCurrency,
minimumFractionDigits: 2, minimumFractionDigits: 2,
}).format(normalized) }).format(normalized)
} catch (_error) {} } catch (_error) { }
} }
return `${normalized.toFixed(2)} ${upperCurrency}` return `${normalized.toFixed(2)} ${upperCurrency}`
@@ -45,6 +45,33 @@ const FALLBACK_BASE_PRICE_PER_COIN = 12 // ~0,12€ par jeton (valeur indicatif
const FALLBACK_DISCOUNT_STEPS = [0, 12, 18, 26, 32] const FALLBACK_DISCOUNT_STEPS = [0, 12, 18, 26, 32]
const STATIC_DISCOUNTS = [0, 30, 50] 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 = []) => { const computeCoinPackPricing = (packs = []) => {
if (!Array.isArray(packs) || !packs.length) { if (!Array.isArray(packs) || !packs.length) {
return {} return {}
@@ -160,6 +187,11 @@ function CoinPackCard({ pack, selected, pricingDetail, onSelect, staticDiscountP
? `-${discountPercent}% vs pack de base` ? `-${discountPercent}% vs pack de base`
: null : null
const packKey = getCoinPackKey(pack)
const details = packKey ? COIN_PACK_DETAILS[packKey] : null
const displayName = details?.label || pack.name
const creditsText = details?.creditsDisplay
return ( return (
<Pressable <Pressable
onPress={handleSelect} onPress={handleSelect}
@@ -172,18 +204,18 @@ function CoinPackCard({ pack, selected, pricingDetail, onSelect, staticDiscountP
accessibilityState={{ selected }} accessibilityState={{ selected }}
> >
<BlurView intensity={20} tint="dark" style={styles.cardBlur}> <BlurView intensity={20} tint="dark" style={styles.cardBlur}>
{/*{pricingDetail?.isBestValue ? (*/}
{/* <View style={styles.tagBestValue}>*/}
{/* <Text style={styles.tagBestValueText}>Meilleure offre</Text>*/}
{/* </View>*/}
{/*) : null}*/}
<View style={styles.cardHeader}> <View style={styles.cardHeader}>
<CreditAmount {creditsText ? (
value={pack?.coinAmount} <Text style={styles.coinAmountCustom}>{creditsText}</Text>
style={styles.coinRow} ) : (
textStyle={styles.coinAmount} <CreditAmount
iconSize={26} value={pack?.coinAmount}
/> style={styles.coinRow}
textStyle={styles.coinAmount}
iconSize={26}
/>
)}
{discountLabel ? ( {discountLabel ? (
<View <View
style={{ style={{
@@ -197,7 +229,10 @@ function CoinPackCard({ pack, selected, pricingDetail, onSelect, staticDiscountP
</View> </View>
</View> </View>
) : null} ) : null}
{pack?.name ? <Text style={styles.packName}>{pack.name}</Text> : null} <Text style={styles.packName}>{displayName}</Text>
{details?.disclaimer ? (
<Text style={styles.packDisclaimer}>{details.disclaimer}</Text>
) : null}
</View> </View>
<View style={styles.priceBlock}> <View style={styles.priceBlock}>
{formattedPrice ? <Text style={styles.packPrice}>{formattedPrice}</Text> : null} {formattedPrice ? <Text style={styles.packPrice}>{formattedPrice}</Text> : null}
@@ -486,6 +521,20 @@ const styles = StyleSheet.create({
fontSize: 20, fontSize: 20,
color: Palette.white, color: Palette.white,
}, },
coinAmountCustom: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 20,
color: Palette.white,
textAlign: 'center',
},
packDisclaimer: {
fontFamily: FONT_FAMILY.InterRegular,
fontSize: 10,
color: 'rgba(255, 255, 255, 0.6)',
fontStyle: 'italic',
textAlign: 'center',
marginTop: 4,
},
packName: { packName: {
fontFamily: FONT_FAMILY.InterMedium, fontFamily: FONT_FAMILY.InterMedium,
fontSize: 16, fontSize: 16,
+65 -7
View File
@@ -240,6 +240,33 @@ const PLAN_DISPLAY_NAME_BY_KEY = {
premium: 'Play Backer Gold', premium: 'Play Backer Gold',
} }
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 PLAN_DETAILS = { const PLAN_DETAILS = {
starter: { starter: {
creditsLabel: '50 Crédits / Mois', creditsLabel: '50 Crédits / Mois',
@@ -746,15 +773,28 @@ export default function Subscriptions() {
const price = formatCurrency(rawAmount, pack.currency) const price = formatCurrency(rawAmount, pack.currency)
// Assuming pack structure: { id, name, unitAmount/amount, currency, metadata: { coins: 100 } } // Assuming pack structure: { id, name, unitAmount/amount, currency, metadata: { coins: 100 } }
const coins = Number(pack.metadata?.coins || 0) const coins = Number(pack.metadata?.coins || 0)
const packKey = getCoinPackKey(pack)
const details = packKey ? COIN_PACK_DETAILS[packKey] : null
const displayName = details?.label || pack.name
const creditsText = details?.creditsDisplay
return ( return (
<View key={pack.productId} style={styles.creditCard}> <View key={pack.productId} style={styles.creditCard}>
<View> <View style={styles.creditInfoContainer}>
<Text style={styles.creditName}>{pack.name}</Text> <Text style={styles.creditName}>{displayName}</Text>
<CreditAmount {creditsText ? (
value={coins} <Text style={styles.creditAmountCustom}>{creditsText}</Text>
iconSize={24} ) : (
textStyle={styles.creditAmountText} <CreditAmount
/> value={coins}
iconSize={24}
textStyle={styles.creditAmountText}
/>
)}
{details?.disclaimer ? (
<Text style={styles.packDisclaimer}>{details.disclaimer}</Text>
) : null}
</View> </View>
<GradientButton <GradientButton
title={ title={
@@ -974,6 +1014,24 @@ const styles = StyleSheet.create({
textAlign: 'center', textAlign: 'center',
marginBottom: 4, marginBottom: 4,
}, },
creditInfoContainer: {
alignItems: 'center',
gap: 4,
},
creditAmountCustom: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 16,
color: Palette.white,
textAlign: 'center',
},
packDisclaimer: {
fontFamily: FONT_FAMILY.InterRegular,
fontSize: 10,
color: 'rgba(255, 255, 255, 0.6)',
fontStyle: 'italic',
textAlign: 'center',
marginTop: 4,
},
creditAmountText: { creditAmountText: {
fontSize: 20, fontSize: 20,
}, },