Files
musicland/src/screens/Profile/OrderHistory.js
T
2025-11-10 14:24:16 +01:00

303 lines
7.7 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;