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