feat: add packs to subscriptions
This commit is contained in:
@@ -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
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user