feat: add packs to subscriptions

This commit is contained in:
2026-01-15 14:15:52 +01:00
parent 9f3b713716
commit 4df675fd69
2 changed files with 160 additions and 18 deletions
+3 -3
View File
@@ -49,9 +49,9 @@ const SongCard = ({
...Style.containerRow, ...Style.containerRow,
}} }}
tint="dark" tint="dark"
// experimentalBlurMethod={ // experimentalBlurMethod={
// Platform.OS !== "ios" ? "dimezisBlurView" : "none" // Platform.OS !== "ios" ? "dimezisBlurView" : "none"
// } // }
> >
<View style={{ flex: 1, ...Style.containerRow, gap: 15 }}> <View style={{ flex: 1, ...Style.containerRow, gap: 15 }}>
<Text <Text
+157 -15
View File
@@ -12,7 +12,9 @@ import { Palette, gutters } from '../styles'
import { FONT_FAMILY } from '../styles/Fonts' import { FONT_FAMILY } from '../styles/Fonts'
import { isWeb } from '../hooks/useLayoutType' import { isWeb } from '../hooks/useLayoutType'
import { useStripe } from '../providers/StripeProvider' import { useStripe } from '../providers/StripeProvider'
import { goBack } from '../navigation/NavigationService' import { useUserData } from '../providers/UserDataProvider'
import { goBack, navigate } from '../navigation/NavigationService'
import { Routes } from '../navigation/Routes'
const formatCurrency = (amount, currency = 'eur') => { const formatCurrency = (amount, currency = 'eur') => {
if (typeof amount !== 'number') { if (typeof amount !== 'number') {
@@ -139,6 +141,11 @@ function SubscriptionCard({ plan, selected, onSelect, isAnnual }) {
) : null} ) : null}
</View> </View>
) : null} ) : null}
<View style={styles.cardBenefits}>
<Text style={styles.cardBenefitsItem}> Eligible au concours mensuel chanson/Vidéo</Text>
<Text style={styles.cardBenefitsItem}> Crédits gratuits tous les mois</Text>
</View>
</View> </View>
</BlurView> </BlurView>
{isAnnual ? ( {isAnnual ? (
@@ -278,7 +285,15 @@ export default function Subscriptions() {
const initialSubscriptionPack = initialPack && initialPack !== 'packs' ? initialPack : null const initialSubscriptionPack = initialPack && initialPack !== 'packs' ? initialPack : null
const { subscriptions, isCatalogLoading, catalogError, createSubscriptionCheckout } = useStripe() const {
subscriptions,
coinPacks,
isCatalogLoading,
catalogError,
createSubscriptionCheckout,
createCoinPackCheckout,
} = useStripe()
const { currentUserData } = useUserData()
const [selectedPeriodKey, setSelectedPeriodKey] = React.useState('annual') const [selectedPeriodKey, setSelectedPeriodKey] = React.useState('annual')
const [selectedPriceId, setSelectedPriceId] = React.useState(null) const [selectedPriceId, setSelectedPriceId] = React.useState(null)
const [processingPriceId, setProcessingPriceId] = React.useState(null) const [processingPriceId, setProcessingPriceId] = React.useState(null)
@@ -315,6 +330,7 @@ export default function Subscriptions() {
) )
if (!periodEntries.length) { if (!periodEntries.length) {
// Don't reset selectedPeriodKey here so it behaves more stably
setSelectedPriceId(null) setSelectedPriceId(null)
return return
} }
@@ -323,10 +339,11 @@ export default function Subscriptions() {
let matchedPriceId = null let matchedPriceId = null
let matchedPeriodKey = selectedPeriodKey let matchedPeriodKey = selectedPeriodKey
let nextPeriodKey = let nextPeriodKey = selectedPeriodKey
periodEntries.find(([periodKey]) => periodKey === selectedPeriodKey)?.[0] || // Only force switch if current selected period is empty and another is not
periodEntries[0]?.[0] || if ((normalizedPlansByPeriod[selectedPeriodKey]?.length || 0) === 0) {
'annual' nextPeriodKey = periodEntries[0]?.[0] || 'annual'
}
if (shouldApplyPack) { if (shouldApplyPack) {
const desired = initialSubscriptionPack.trim().toLowerCase() const desired = initialSubscriptionPack.trim().toLowerCase()
@@ -447,17 +464,35 @@ export default function Subscriptions() {
[selectedPeriodKey] [selectedPeriodKey]
) )
const handleBuyCredits = React.useCallback(
async (pack) => {
const targetId = pack?.productId
if (!targetId) return
setProcessingPriceId(targetId)
setErrorMessage(null)
try {
await createCoinPackCheckout(targetId)
} catch (error) {
console.error('[Subscriptions] coin checkout error', error)
setErrorMessage(
error?.message ||
'Une erreur est survenue lors de la création de la session Stripe pour les crédits.'
)
} finally {
setProcessingPriceId(null)
}
},
[createCoinPackCheckout]
)
const renderHeaderSection = React.useCallback(() => { const renderHeaderSection = React.useCallback(() => {
return ( return (
<> <>
<View style={styles.header}> <View style={styles.header}>
<Text style={styles.title}>Rejoignez le club MusicLand</Text> <Text style={styles.title}>Rejoignez le club MusicLand</Text>
<View style={styles.benefitsBox}> <View style={styles.balanceContainer}>
<Text style={styles.benefitsTitle}>Privilège Membre :</Text> <Text style={styles.balanceLabel}>Votre solde actuel :</Text>
<View style={styles.benefitsList}> <CreditAmount value={currentUserData?.coins || 0} iconSize={20} showPlus={false} />
<Text style={styles.benefitsItem}>- Eligible au concours mensuel chanson/Vidéo</Text>
<Text style={styles.benefitsItem}>- Crédits gratuits tous les mois</Text>
</View>
</View> </View>
</View> </View>
{combinedErrorMessage ? <Text style={styles.errorText}>{combinedErrorMessage}</Text> : null} {combinedErrorMessage ? <Text style={styles.errorText}>{combinedErrorMessage}</Text> : null}
@@ -639,6 +674,45 @@ export default function Subscriptions() {
))} ))}
</View> </View>
{coinPacks && coinPacks.length > 0 ? (
<View style={styles.creditsSection}>
<Text style={styles.sectionTitle}>Recharger vos crédits</Text>
<View style={styles.creditsGrid}>
{coinPacks.map((pack) => {
// Attempt to find amount in commonly used Stripe fields
const rawAmount = pack.unitAmount ?? pack.amount ?? pack.price ?? 0
const price = formatCurrency(rawAmount, pack.currency)
// Assuming pack structure: { id, name, unitAmount/amount, currency, metadata: { coins: 100 } }
const coins = Number(pack.metadata?.coins || 0)
return (
<View key={pack.productId} style={styles.creditCard}>
<View>
<Text style={styles.creditName}>{pack.name}</Text>
<CreditAmount
value={coins}
iconSize={24}
textStyle={styles.creditAmountText}
/>
</View>
<GradientButton
title={
processingPriceId === pack.productId
? '...'
: price || `${rawAmount / 100}`
}
onPress={() => handleBuyCredits(pack)}
disabled={Boolean(processingPriceId)}
style={styles.creditButton}
textStyle={styles.creditButtonText}
gradientStyle={{ paddingVertical: 8, paddingHorizontal: 16 }}
/>
</View>
)
})}
</View>
</View>
) : null}
{isLoadingPlans && currentPlanCount > 0 ? ( {isLoadingPlans && currentPlanCount > 0 ? (
<View style={styles.inlineLoader}> <View style={styles.inlineLoader}>
<ActivityIndicator color={Palette.white} size="small" /> <ActivityIndicator color={Palette.white} size="small" />
@@ -742,11 +816,79 @@ const styles = StyleSheet.create({
benefitsList: { benefitsList: {
gap: 2, gap: 2,
}, },
benefitsItem: {
cardBenefits: {
marginTop: 12,
borderTopWidth: 1,
borderTopColor: 'rgba(255, 255, 255, 0.1)',
paddingTop: 8,
gap: 4,
},
cardBenefitsItem: {
fontFamily: FONT_FAMILY.InterMedium, fontFamily: FONT_FAMILY.InterMedium,
fontSize: 13, fontSize: 12,
color: 'rgba(255, 255, 255, 0.85)', color: 'rgba(255, 255, 255, 0.7)',
},
balanceContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.08)',
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 20,
gap: 10,
marginTop: 8,
},
balanceLabel: {
fontFamily: FONT_FAMILY.InterMedium,
fontSize: 14,
color: Palette.white,
},
creditsSection: {
width: '100%',
marginTop: 40,
gap: 16,
alignItems: 'center',
},
sectionTitle: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 20,
color: Palette.white,
},
creditsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 16,
justifyContent: 'center',
},
creditCard: {
backgroundColor: 'rgba(255, 255, 255, 0.05)',
borderRadius: 16,
padding: 16,
alignItems: 'center',
justifyContent: 'space-between',
width: isWeb ? 200 : '45%',
gap: 12,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.1)',
},
creditName: {
fontFamily: FONT_FAMILY.InterSemiBold,
fontSize: 14,
color: Palette.white,
textAlign: 'center', textAlign: 'center',
marginBottom: 4,
},
creditAmountText: {
fontSize: 20,
},
creditButton: {
width: '100%',
height: 36,
minHeight: 36,
},
creditButtonText: {
fontSize: 13,
}, },
webBackContainer: { webBackContainer: {
alignSelf: 'flex-start', alignSelf: 'flex-start',