fix by musicland ai and new club advantages

This commit is contained in:
2026-02-25 11:36:28 +01:00
parent 5bdf268ebf
commit 0618afa3a4
4 changed files with 193 additions and 257 deletions
+2 -2
View File
@@ -42,7 +42,8 @@ const buildBrandSvg = ({ width = 1024, height = 1024, text = BRAND_TEXT } = {})
const rectX = Math.max(0, Math.round(safeWidth - margin - rectWidth))
const rectY = Math.max(0, Math.round(safeHeight - margin - rectHeight))
const textX = rectX + rectWidth / 2
const textY = rectY + rectHeight / 2
const baselineShift = Math.round(fontSize * 0.3)
const textY = rectY + rectHeight / 2 + baselineShift
const rectRadius = Math.max(6, Math.round(rectHeight * 0.35))
return Buffer.from(`
@@ -65,7 +66,6 @@ const buildBrandSvg = ({ width = 1024, height = 1024, text = BRAND_TEXT } = {})
x="${textX}"
y="${textY}"
text-anchor="middle"
dominant-baseline="middle"
font-family="${BRAND_FONT_FAMILY}"
font-size="${fontSize}"
font-style="italic"
@@ -13,7 +13,6 @@ import { FONT_FAMILY } from '../../styles/Fonts'
import { formatDate, toDate } from '../../utils/dateFormatting'
import { Image as ExpoImage } from 'expo-image'
import { isWeb } from '../../hooks/useLayoutType'
import ClubAdvantagesCard from './components/ClubAdvantagesCard'
const FUNCTIONS_REGION = 'europe-west1'
@@ -654,7 +653,6 @@ const ManageSubscription = ({ navigation }) => {
/>
</View>
)}
{/*<ClubAdvantagesCard isDev style={styles.clubCardSpacing} />*/}
</View>
</Page>
)
@@ -1,160 +1,152 @@
import React from 'react'
import { StyleSheet, Switch, Text, View } from 'react-native'
import { MaterialCommunityIcons } from '@expo/vector-icons'
import { Image as ExpoImage } from 'expo-image'
import { StyleSheet, Text, View } from 'react-native'
import { useNavigation } from '@react-navigation/native'
import ClubCard from '../../Home/components/ClubCard'
import { gutters, Palette } from '../../../styles'
import { FONT_FAMILY } from '../../../styles/Fonts'
import { Routes } from '../../../navigation/Routes'
import { useUserData } from '../../../providers/UserDataProvider'
import { icons } from '../../../assets'
import GradientButton from '../../../components/GradientButton'
import CreditAmount from '../../../components/CreditAmount'
import { useStripe } from '../../../providers/StripeProvider'
import { isWeb } from '../../../hooks/useLayoutType'
const ICON_BASE_ACCENT = '#A96BFF'
const formatCurrency = (amount, currency = 'eur') => {
if (typeof amount !== 'number') {
return null
}
const normalized = amount / 100
const upperCurrency =
typeof currency === 'string' && currency.trim() ? currency.trim().toUpperCase() : 'EUR'
const CLUB_ADVANTAGES = [
{
title: 'Gagne des crédits avec ton abonnement',
description: 'Des crédits premium tous les mois pour créer plus.',
iconType: 'image',
icon: icons.coin,
accent: ICON_BASE_ACCENT,
},
{
title: 'Gagne le double de cashprice grace à ton adésion au club',
description: 'Cashprice doublé pour les membres du Club Musicland.',
iconType: 'vector',
iconName: 'chart-line',
accent: '#F8C24D',
},
]
if (typeof Intl !== 'undefined' && Intl.NumberFormat) {
try {
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: upperCurrency,
minimumFractionDigits: 2,
}).format(normalized)
} catch (_error) {
// Ignore
}
}
return `${normalized.toFixed(2)} ${upperCurrency}`
}
const ClubAdvantagesCard = ({ style, isDev = false }) => {
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 ClubAdvantagesCard = ({ style }) => {
const navigation = useNavigation()
const { hasActiveSubscription } = useUserData() || {}
const [useWebLayout, setUseWebLayout] = React.useState(isDev ? isWeb : false)
const advantages = CLUB_ADVANTAGES
React.useEffect(() => {
if (!isDev) {
setUseWebLayout(false)
}
}, [isDev])
const title = hasActiveSubscription
? 'Tu profites déjà du Club Musicland'
: 'Rejoins le Club Musicland'
const subtitle = hasActiveSubscription
? 'Grâce à ton abonnement, tu bénéficies de tous ces avantages.'
: 'Découvre ce que tu gagnes en passant au club premium.'
const { coinPacks, createCoinPackCheckout } = useStripe()
const [processingPriceId, setProcessingPriceId] = React.useState(null)
const [errorMessage, setErrorMessage] = React.useState(null)
const handleOpenPlans = () => {
navigation.navigate(Routes.Payments)
}
const handleBuyCredits = async (pack) => {
const targetId = pack?.productId
if (!targetId) return
setProcessingPriceId(targetId)
setErrorMessage(null)
try {
await createCoinPackCheckout(targetId)
} catch (error) {
setErrorMessage(error?.message || "Erreur lors de l'achat de crédits.")
} finally {
setProcessingPriceId(null)
}
}
return (
<View
style={[styles.card, hasActiveSubscription ? styles.cardActive : styles.cardInactive, style]}
>
{/* {isDev ? (
<View style={styles.testSwitches}>
<View style={styles.switchRow}>
<Text style={styles.switchLabel}>Test layout web</Text>
<Switch
value={useWebLayout}
onValueChange={setUseWebLayout}
trackColor={{
false: "rgba(255, 255, 255, 0.2)",
true: "rgba(52, 199, 89, 0.7)",
}}
thumbColor={useWebLayout ? Palette.white : Palette.grayMid}
/>
</View>
</View>
) : null} */}
<View style={styles.header}>
<View style={styles.titleBlock}>
<Text style={styles.title}>{title}</Text>
<Text style={styles.subtitle}>{subtitle}</Text>
</View>
</View>
<View
style={[
styles.advantagesList,
useWebLayout ? styles.advantagesListWeb : styles.advantagesListMobile,
]}
>
{advantages.map((advantage) => {
const accent = advantage.accent || Palette.white
const iconType = advantage.iconType || 'image'
<Text style={styles.title}>
{'Les abonnements et les packs permettent le téléchargement de vos créations'}
</Text>
<Text style={styles.subtitle}>{'Avec un abonnement le téléchargement est gratuit'}</Text>
{coinPacks && coinPacks.length > 0 ? (
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Recharger vos crédits</Text>
<View style={styles.creditsGrid}>
{coinPacks.map((pack) => {
const rawAmount = pack.unitAmount ?? pack.amount ?? pack.price ?? 0
const price = formatCurrency(rawAmount, pack.currency)
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 (
<View
key={advantage.title}
style={[
styles.advantageCard,
useWebLayout ? styles.advantageCardWeb : styles.advantageCardMobile,
]}
>
{hasActiveSubscription ? (
<MaterialCommunityIcons
name="check"
size={useWebLayout ? 22 : 20}
color={Palette.green}
style={[
styles.advantageCheck,
useWebLayout ? styles.advantageCheckWeb : styles.advantageCheckMobile,
]}
/>
) : null}
<View
style={[
styles.advantageIcon,
useWebLayout && styles.advantageIconWeb,
{
backgroundColor: `${ICON_BASE_ACCENT}1F`,
borderColor: `${ICON_BASE_ACCENT}55`,
},
]}
>
{iconType === 'vector' ? (
<MaterialCommunityIcons name={advantage.iconName} size={24} color={accent} />
<View key={pack.productId} style={styles.creditCard}>
<View style={styles.creditInfoContainer}>
<Text style={styles.creditName}>{displayName}</Text>
{creditsText ? (
<Text style={styles.creditAmountCustom}>{creditsText}</Text>
) : (
<ExpoImage
source={advantage.icon}
contentFit="contain"
style={[styles.advantageIconImage, { tintColor: accent }]}
<CreditAmount
value={coins}
iconSize={24}
textStyle={styles.creditAmountText}
/>
)}
{details?.disclaimer ? (
<Text style={styles.packDisclaimer}>{details.disclaimer}</Text>
) : null}
</View>
<View
style={[
styles.advantageContent,
useWebLayout && styles.advantageContentWeb,
hasActiveSubscription && !useWebLayout && { paddingRight: 10 },
]}
>
<Text style={[styles.advantageTitle, useWebLayout && styles.advantageTextCenter]}>
{advantage.title}
</Text>
<Text style={[styles.advantageText, useWebLayout && styles.advantageTextCenter]}>
{advantage.description}
</Text>
</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>
<ClubCard
image={icons.clubIcon}
onPress={handleOpenPlans}
hasActiveSubscription={hasActiveSubscription}
/>
{errorMessage ? <Text style={styles.errorText}>{errorMessage}</Text> : null}
</View>
) : null}
<GradientButton title="Voir les abonnements" onPress={handleOpenPlans} />
</View>
</View>
</View>
)
}
@@ -166,7 +158,6 @@ const styles = StyleSheet.create({
borderRadius: 24,
backgroundColor: '#252438',
borderWidth: 1,
gap: gutters,
borderColor: 'white',
},
cardInactive: {
@@ -179,28 +170,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: gutters,
},
testSwitches: {
gap: gutters * 0.75,
// backgroundColor: "rgba(255, 255, 255, 0.08)",
padding: gutters * 0.9,
borderRadius: 14,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.12)',
},
switchRow: {
flexDirection: 'row',
alignItems: 'center',
gap: gutters * 0.5,
justifyContent: 'space-between',
},
switchLabel: {
fontFamily: FONT_FAMILY.InterMedium,
fontSize: 11,
color: Palette.grayMid,
textTransform: 'uppercase',
letterSpacing: 0.6,
},
titleBlock: {
flex: 1,
@@ -211,7 +180,7 @@ const styles = StyleSheet.create({
fontFamily: FONT_FAMILY.InterSemiBold,
fontSize: 18,
color: Palette.white,
textAlign: 'center',
textAlign: "center",
},
subtitle: {
fontFamily: FONT_FAMILY.InterRegular,
@@ -220,96 +189,80 @@ const styles = StyleSheet.create({
lineHeight: 18,
textAlign: 'center',
},
advantagesList: {
gap: gutters * 0.75,
},
advantagesListWeb: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
},
advantagesListMobile: {
flexDirection: 'column',
},
advantageCard: {
flexDirection: 'row',
alignItems: 'flex-start',
gap: gutters * 0.65,
backgroundColor: 'rgba(255, 255, 255, 0.08)',
borderRadius: 16,
borderWidth: 0,
padding: gutters,
shadowColor: '#000',
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.2,
shadowRadius: 10,
elevation: 2,
},
advantageCardWeb: {
flexBasis: '31%',
maxWidth: '32%',
minWidth: 200,
flexGrow: 1,
flexShrink: 1,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-start',
gap: gutters * 0.5,
position: 'relative',
},
advantageCardMobile: {
sectionContainer: {
gap: 16,
width: '100%',
position: 'relative',
},
advantageIcon: {
width: 42,
height: 42,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderTopWidth: 1,
borderTopColor: 'rgba(255, 255, 255, 0.08)',
paddingTop: gutters,
marginTop: gutters * 0.6,
},
advantageIconWeb: {
alignSelf: 'center',
marginBottom: gutters * 0.15,
},
advantageIconImage: {
width: 24,
height: 24,
},
advantageContent: {
flex: 1,
gap: gutters * 0.2,
},
advantageContentWeb: {
alignItems: 'center',
gap: gutters * 0.4,
},
advantageTitle: {
fontFamily: FONT_FAMILY.InterSemiBold,
fontSize: 15,
sectionTitle: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 20,
color: Palette.white,
},
advantageText: {
fontFamily: FONT_FAMILY.InterRegular,
fontSize: 13,
lineHeight: 18,
color: 'rgba(255, 255, 255, 0.85)',
},
advantageTextCenter: {
textAlign: 'center',
},
advantageCheck: {
position: 'absolute',
creditsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 16,
justifyContent: 'center',
width: '100%',
},
advantageCheckWeb: {
top: 10,
right: 10,
creditCard: {
backgroundColor: 'rgba(255, 255, 255, 0.05)',
borderRadius: 16,
padding: 16,
alignItems: 'center',
justifyContent: 'space-between',
width: isWeb ? 220 : '100%',
minHeight: 160,
gap: 12,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.1)',
},
advantageCheckMobile: {
top: '50%',
right: 12,
transform: [{ translateY: -10 }],
creditInfoContainer: {
alignItems: 'center',
gap: 4,
},
creditName: {
fontFamily: FONT_FAMILY.InterSemiBold,
fontSize: 14,
color: Palette.white,
textAlign: 'center',
marginBottom: 4,
},
creditAmountCustom: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 16,
color: Palette.white,
textAlign: 'center',
},
creditAmountText: {
fontSize: 20,
},
packDisclaimer: {
fontFamily: FONT_FAMILY.InterRegular,
fontSize: 10,
color: 'rgba(255, 255, 255, 0.6)',
fontStyle: 'italic',
textAlign: 'center',
marginTop: 4,
},
creditButton: {
width: '100%',
height: 36,
minHeight: 36,
},
creditButtonText: {
fontSize: 13,
},
errorText: {
color: Palette.red,
textAlign: 'center',
marginTop: 10,
},
})
+1 -16
View File
@@ -549,22 +549,7 @@ const SongDownload = ({ route }) => {
</View>
</View>
{showSubscriptionCta ? (
<View style={styles.subscriptionCta}>
<Text style={styles.subscriptionNote}>
Avec un abonnement, le téléchargement est gratuit.
</Text>
<GradientButton
title="Voir les abonnements"
onPress={() => navigate(Routes.Payments)}
containerStyle={styles.subscriptionButton}
/>
</View>
) : (
<Text style={styles.subscriptionNote}>
Avec ton abonnement, le téléchargement est gratuit.
</Text>
)}
<ClubAdvantagesCard style={styles.clubCardSpacing} />
</ScrollView>