Files
musicland/src/screens/Profile/OrderHistory.js
T
2026-01-12 16:01:32 +01:00

274 lines
7.4 KiB
JavaScript

import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { ActivityIndicator, FlatList, StyleSheet, Text, View } from 'react-native'
import firebase from '../../config/firebase'
import { background } from '../../assets'
import Page from '../../layouts/Page'
import { useUser } from '../../providers/UserDataProvider'
import { gutters, Palette } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts'
import CreditAmount from '../../components/CreditAmount'
const ORDER_TYPE_LABELS = {
GIFT: 'Crédit offert',
COINS: 'Achat de coins',
SONG: 'Génération de musique',
SUBSCRIPTION: 'Abonnement',
}
const formatDateParts = (date) => {
if (!date) {
return {
dateLabel: 'En attente de confirmation',
timeLabel: '',
}
}
try {
const dateLabel = date.toLocaleDateString('fr-FR', {
day: '2-digit',
month: 'short',
year: 'numeric',
})
const timeLabel = date.toLocaleTimeString('fr-FR', {
hour: '2-digit',
minute: '2-digit',
})
return { dateLabel, timeLabel }
} catch (_error) {
return {
dateLabel: date.toString(),
timeLabel: '',
}
}
}
const mapOrderType = (type) => ORDER_TYPE_LABELS[type] || 'Opération'
const shortenIdentifier = (value, visible = 6) => {
if (typeof value !== 'string') return null
if (value.length <= visible + 2) return value
return `${value.slice(0, visible)}…`
}
const OrderHistory = () => {
const { currentUID } = useUser() || {}
const [orders, setOrders] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
if (!currentUID) {
setOrders([])
setLoading(false)
return
}
setLoading(true)
setError(null)
const unsubscribe = firebase
.firestore()
.collection('orders')
.where('userId', '==', currentUID)
.orderBy('createdAt', 'desc')
.onSnapshot(
(snapshot) => {
const nextOrders = snapshot.docs.map((doc) => {
const rawData = doc.data() || {}
const createdAt =
typeof rawData.createdAt?.toDate === 'function' ? rawData.createdAt.toDate() : null
return {
id: doc.id,
...rawData,
createdAt,
}
})
setOrders(nextOrders)
setLoading(false)
},
(firestoreError) => {
console.warn(
'[OrderHistory] Unable to load orders',
firestoreError?.message || firestoreError
)
setError(firestoreError?.message || "Impossible de récupérer l'historique des commandes.")
setLoading(false)
}
)
return () => {
if (typeof unsubscribe === 'function') {
unsubscribe()
}
}
}, [currentUID])
const renderOrder = useCallback(({ item }) => {
const amountValue =
typeof item.amount === 'number' && Number.isFinite(item.amount) ? item.amount : 0
const isPositive = amountValue > 0
const amountTextStyle = [
styles.orderAmount,
isPositive ? styles.amountPositive : styles.amountNegative,
]
const { dateLabel } = formatDateParts(item.createdAt)
let reasonLabel = mapOrderType(item.type)
if (item.type === 'GIFT') {
const reason = item.metadata?.reason
if (reason === 'WELCOME_BONUS') {
reasonLabel = 'Crédit de bienvenue'
} else if (typeof reason === 'string' && reason.trim()) {
reasonLabel = reason.trim()
}
} else if (item.type === 'SONG') {
reasonLabel = 'Génération de musique'
} else if (item.type === 'COINS') {
if (item.metadata?.coinPackKey) {
const shortenedPack = shortenIdentifier(item.metadata.coinPackKey, 10)
reasonLabel = `Pack ${shortenedPack || item.metadata.coinPackKey}`
} else if (typeof item.metadata?.source === 'string' && item.metadata.source.trim()) {
const source = item.metadata.source.trim()
reasonLabel = source === 'STRIPE_CHECKOUT' ? 'Recharge Stripe' : source
} else {
reasonLabel = 'Rechargement de coins'
}
} else if (item.type === 'SUBSCRIPTION') {
const rawPeriod =
typeof item.metadata?.billingPeriod === 'string'
? item.metadata.billingPeriod.toLowerCase()
: null
reasonLabel =
rawPeriod === 'annual'
? 'Abonnement annuel'
: rawPeriod === 'monthly'
? 'Abonnement mensuel'
: 'Abonnement'
}
return (
<View style={styles.orderCard}>
<CreditAmount value={amountValue} showPlus textStyle={amountTextStyle} iconSize={18} />
<Text style={styles.orderReason}>{reasonLabel}</Text>
<Text style={styles.orderTimestamp}>{dateLabel}</Text>
</View>
)
}, [])
const keyExtractor = useCallback((item) => item.id, [])
const listEmptyComponent = useMemo(() => {
if (loading) {
return null
}
if (!currentUID) {
return (
<Text style={styles.emptyText}>
Connecte-toi pour consulter l'historique de tes commandes.
</Text>
)
}
if (error) {
return <Text style={styles.errorText}>{error}</Text>
}
return <Text style={styles.emptyText}>Aucune opération de coins pour le moment.</Text>
}, [loading, currentUID, error])
return (
<Page
headerType="NAVIGATION"
title="Historique de commandes"
backgroundImg={background.profileBG}
contentContainerStyle={{ paddingBottom: gutters * 2 }}
containerStyle={{
backgroundColor: 'rgba(0,0,0,0.4)',
}}
>
<View style={styles.container}>
{loading ? (
<View style={styles.loaderContainer}>
<ActivityIndicator color={Palette.white} size="large" />
</View>
) : (
<FlatList
data={orders}
keyExtractor={keyExtractor}
renderItem={renderOrder}
contentContainerStyle={[
styles.listContent,
orders.length === 0 && styles.emptyListContent,
]}
ItemSeparatorComponent={() => <View style={{ height: 12 }} />}
ListEmptyComponent={listEmptyComponent}
/>
)}
</View>
</Page>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: gutters,
paddingTop: gutters,
},
loaderContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
listContent: {
paddingBottom: gutters * 2,
},
emptyListContent: {
flexGrow: 1,
justifyContent: 'center',
},
orderCard: {
padding: 16,
borderRadius: 16,
backgroundColor: Palette.ultraLightWhite,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.08)',
gap: 6,
},
orderAmount: {
fontSize: 16,
fontFamily: FONT_FAMILY.InterSemiBold,
},
amountPositive: {
color: Palette.green,
},
amountNegative: {
color: Palette.red,
},
orderReason: {
fontSize: 14,
fontFamily: FONT_FAMILY.InterRegular,
color: Palette.white,
},
orderTimestamp: {
fontSize: 12,
color: Palette.grayMid,
fontFamily: FONT_FAMILY.InterMedium,
},
emptyText: {
textAlign: 'center',
color: Palette.grayMid,
fontSize: 15,
fontFamily: FONT_FAMILY.InterRegular,
},
errorText: {
textAlign: 'center',
color: Palette.red,
fontSize: 15,
fontFamily: FONT_FAMILY.InterRegular,
},
})
export default OrderHistory