chore: fix UI bug

This commit is contained in:
2026-03-31 12:07:58 +02:00
parent 37103ff0ca
commit ff75c9811a
2 changed files with 121 additions and 165 deletions
+1 -162
View File
@@ -3,7 +3,6 @@ import {
View, View,
Text, Text,
FlatList, FlatList,
ScrollView,
Image, Image,
TouchableOpacity, TouchableOpacity,
StyleSheet, StyleSheet,
@@ -59,7 +58,6 @@ export default function CartScreen() {
const [loadingMedia, setLoadingMedia] = useState(false); const [loadingMedia, setLoadingMedia] = useState(false);
const [removeId, setRemoveId] = useState<number | null>(null); const [removeId, setRemoveId] = useState<number | null>(null);
const [showClearModal, setShowClearModal] = useState(false); const [showClearModal, setShowClearModal] = useState(false);
const [showRecapModal, setShowRecapModal] = useState(false);
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@@ -251,94 +249,6 @@ export default function CartScreen() {
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: colors.border, borderTopColor: colors.border,
}, },
recapScroll: {
maxHeight: 280,
},
recapItem: {
flexDirection: "row",
alignItems: "center",
paddingVertical: spacing.m,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
recapItemImage: {
width: 44,
height: 44,
borderRadius: borderRadius.sm,
},
recapItemImagePlaceholder: {
width: 44,
height: 44,
borderRadius: borderRadius.sm,
backgroundColor: colors.bgInput,
justifyContent: "center",
alignItems: "center",
},
recapItemInfo: {
flex: 1,
marginLeft: spacing.m,
},
recapItemName: {
color: colors.textWhite,
fontSize: fontSize.sm,
fontWeight: fontWeight.semibold,
},
recapItemQty: {
color: colors.textMuted,
fontSize: fontSize.xs,
marginTop: 2,
},
recapItemPrice: {
color: colors.success,
fontSize: fontSize.sm,
fontWeight: fontWeight.bold,
},
recapDivider: {
height: 1,
backgroundColor: colors.border,
marginVertical: spacing.l,
},
recapTotalRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: spacing.xs,
},
recapTotalLabel: {
color: colors.textSecondary,
fontSize: fontSize.md,
fontWeight: fontWeight.medium,
},
recapTotalValue: {
color: colors.success,
fontSize: fontSize.xxl,
fontWeight: fontWeight.bold,
},
recapArticlesCount: {
color: colors.textMuted,
fontSize: fontSize.xs,
},
recapNote: {
flexDirection: "row",
alignItems: "center",
gap: spacing.s,
backgroundColor: colors.accent + "18",
borderRadius: borderRadius.sm,
borderWidth: 1,
borderColor: colors.accent + "44",
padding: spacing.m,
marginBottom: spacing.l,
},
recapNoteText: {
color: colors.accentLight,
fontSize: fontSize.xs,
flex: 1,
lineHeight: 16,
},
recapActions: {
flexDirection: "row",
gap: spacing.m,
},
totalRow: { totalRow: {
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between", justifyContent: "space-between",
@@ -465,7 +375,7 @@ export default function CartScreen() {
</View> </View>
<Button <Button
title="Commander" title="Commander"
onPress={() => setShowRecapModal(true)} onPress={() => navigation.navigate("Checkout")}
variant="primary" variant="primary"
size="lg" size="lg"
fullWidth fullWidth
@@ -513,77 +423,6 @@ export default function CartScreen() {
</View> </View>
</Modal> </Modal>
<Modal
visible={showRecapModal}
onClose={() => setShowRecapModal(false)}
title="Récapitulatif"
icon="receipt-outline"
iconColor={colors.accent}
>
<ScrollView style={styles.recapScroll} showsVerticalScrollIndicator={false}>
{(enrichedItems.length > 0 ? enrichedItems : cartItems).map((item, idx) => (
<View key={item.id} style={[styles.recapItem, idx === 0 && { borderTopWidth: 1, borderTopColor: colors.borderLight }]}>
{(item as EnrichedItem).imageUri ? (
<Image
source={{ uri: (item as EnrichedItem).imageUri }}
style={styles.recapItemImage}
/>
) : (
<View style={styles.recapItemImagePlaceholder}>
<Ionicons name="leaf-outline" size={20} color={colors.textMuted} />
</View>
)}
<View style={styles.recapItemInfo}>
<Text style={styles.recapItemName} numberOfLines={1}>
{item.name_product}
</Text>
<Text style={styles.recapItemQty}>{item.quantity}g</Text>
</View>
<Text style={styles.recapItemPrice}>{item.price.toFixed(2)} </Text>
</View>
))}
</ScrollView>
<View style={styles.recapDivider} />
<View style={styles.recapTotalRow}>
<Text style={styles.recapTotalLabel}>Total</Text>
<Text style={styles.recapTotalValue}>{cartTotal.toFixed(2)} </Text>
</View>
<Text style={styles.recapArticlesCount}>
{cartCount} article{cartCount > 1 ? "s" : ""}
</Text>
<View style={[styles.recapDivider, { marginTop: spacing.l }]} />
<View style={styles.recapNote}>
<Ionicons name="information-circle-outline" size={16} color={colors.accentLight} />
<Text style={styles.recapNoteText}>
Vous renseignerez votre adresse et vos coordonnées à l'étape suivante.
</Text>
</View>
<View style={styles.recapActions}>
<Button
title="Modifier"
onPress={() => setShowRecapModal(false)}
variant="outline"
size="md"
style={{ flex: 1 }}
/>
<Button
title="Confirmer"
onPress={() => {
setShowRecapModal(false);
navigation.navigate("Checkout");
}}
variant="primary"
size="md"
style={{ flex: 1 }}
/>
</View>
</Modal>
<Modal <Modal
visible={showClearModal} visible={showClearModal}
onClose={() => setShowClearModal(false)} onClose={() => setShowClearModal(false)}
+120 -3
View File
@@ -63,6 +63,7 @@ export default function CheckoutScreen() {
const [cryptoData, setCryptoData] = useState<CryptoPaymentStatus | null>(null); const [cryptoData, setCryptoData] = useState<CryptoPaymentStatus | null>(null);
const [showCryptoModal, setShowCryptoModal] = useState(false); const [showCryptoModal, setShowCryptoModal] = useState(false);
const [cryptoPolling, setCryptoPolling] = useState(false); const [cryptoPolling, setCryptoPolling] = useState(false);
const [showRecapModal, setShowRecapModal] = useState(false);
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null); const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => { useEffect(() => {
@@ -124,7 +125,7 @@ export default function CheckoutScreen() {
} }
}; };
const handleCheckout = async () => { const handleValidateAndShowRecap = () => {
if (!nom.trim()) { setError("Veuillez saisir votre nom"); return; } if (!nom.trim()) { setError("Veuillez saisir votre nom"); return; }
if (!prenom.trim()) { setError("Veuillez saisir votre prenom"); return; } if (!prenom.trim()) { setError("Veuillez saisir votre prenom"); return; }
if (!telephone.trim()) { setError("Veuillez saisir votre numero de telephone"); return; } if (!telephone.trim()) { setError("Veuillez saisir votre numero de telephone"); return; }
@@ -134,6 +135,12 @@ export default function CheckoutScreen() {
setError("Veuillez sélectionner une cryptomonnaie"); setError("Veuillez sélectionner une cryptomonnaie");
return; return;
} }
setError(null);
setShowRecapModal(true);
};
const handleCheckout = async () => {
setShowRecapModal(false);
setError(null); setError(null);
setLoading(true); setLoading(true);
@@ -421,6 +428,37 @@ export default function CheckoutScreen() {
padding: spacing.m, marginBottom: spacing.s, padding: spacing.m, marginBottom: spacing.s,
}, },
cryptoOnlyText: { color: CRYPTO_COLOR, fontSize: fontSize.sm, fontWeight: fontWeight.medium, flex: 1 }, cryptoOnlyText: { color: CRYPTO_COLOR, fontSize: fontSize.sm, fontWeight: fontWeight.medium, flex: 1 },
// Modal récap
recapScroll: { maxHeight: 200 },
recapItem: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: spacing.s,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
recapItemLeft: { flex: 1, marginRight: spacing.m },
recapItemName: { color: colors.textWhite, fontSize: fontSize.sm, fontWeight: fontWeight.semibold },
recapItemQty: { color: colors.textMuted, fontSize: fontSize.xs, marginTop: 2 },
recapItemPrice: { color: colors.success, fontSize: fontSize.sm, fontWeight: fontWeight.bold },
recapDivider: { height: 1, backgroundColor: colors.border, marginVertical: spacing.m },
recapInfoRow: {
flexDirection: "row",
alignItems: "flex-start",
gap: spacing.s,
paddingVertical: spacing.xs,
},
recapInfoText: { color: colors.textPrimary, fontSize: fontSize.sm, flex: 1 },
recapTotalRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingTop: spacing.m,
},
recapTotalLabel: { color: colors.textSecondary, fontSize: fontSize.lg, fontWeight: fontWeight.semibold },
recapTotalValue: { color: colors.success, fontSize: fontSize.xxl, fontWeight: fontWeight.bold },
recapActions: { flexDirection: "row", gap: spacing.m, marginTop: spacing.l },
}), }),
[colors], [colors],
); );
@@ -632,16 +670,95 @@ export default function CheckoutScreen() {
<Button <Button
title="Valider la commande" title="Valider la commande"
onPress={handleCheckout} onPress={handleValidateAndShowRecap}
loading={loading} loading={loading}
disabled={loading || cartItems.length === 0} disabled={loading || cartItems.length === 0}
variant="success" variant="primary"
size="lg" size="lg"
fullWidth fullWidth
style={{ marginTop: spacing.l }} style={{ marginTop: spacing.l }}
/> />
</ScrollView> </ScrollView>
{/* Modal récapitulatif commande */}
<Modal
visible={showRecapModal}
onClose={() => setShowRecapModal(false)}
title="Récapitulatif"
icon="receipt-outline"
iconColor={colors.accent}
>
<ScrollView style={styles.recapScroll} showsVerticalScrollIndicator={false}>
{cartItems.map((item) => (
<View key={item.id} style={styles.recapItem}>
<View style={styles.recapItemLeft}>
<Text style={styles.recapItemName} numberOfLines={1}>{item.name_product}</Text>
<Text style={styles.recapItemQty}>{item.quantity}g</Text>
</View>
<Text style={styles.recapItemPrice}>{item.price.toFixed(2)} €</Text>
</View>
))}
</ScrollView>
<View style={styles.recapDivider} />
<View style={styles.recapInfoRow}>
<Ionicons name="person-outline" size={16} color={colors.textMuted} />
<Text style={styles.recapInfoText}>{prenom.trim()} {nom.trim()}</Text>
</View>
<View style={styles.recapInfoRow}>
<Ionicons name="call-outline" size={16} color={colors.textMuted} />
<Text style={styles.recapInfoText}>{telephone.trim()}</Text>
</View>
<View style={styles.recapInfoRow}>
<Ionicons name="location-outline" size={16} color={colors.textMuted} />
<Text style={styles.recapInfoText}>{address.trim()}</Text>
</View>
<View style={styles.recapInfoRow}>
<Ionicons
name={paymentMethod === "crypto" ? "logo-bitcoin" : "cash-outline"}
size={16}
color={paymentMethod === "crypto" ? CRYPTO_COLOR : colors.textMuted}
/>
<Text style={styles.recapInfoText}>
{paymentMethod === "crypto" ? `Crypto — ${payCurrency.toUpperCase()}` : "Espèces"}
</Text>
</View>
{useReferral && referralBalance > 0 && (
<View style={styles.recapInfoRow}>
<Ionicons name="gift-outline" size={16} color={colors.accent} />
<Text style={[styles.recapInfoText, { color: colors.accent }]}>
Crédit parrainage : -{referralBalance.toFixed(2)} €
</Text>
</View>
)}
<View style={styles.recapDivider} />
<View style={styles.recapTotalRow}>
<Text style={styles.recapTotalLabel}>Total</Text>
<Text style={styles.recapTotalValue}>{cartTotal.toFixed(2)} €</Text>
</View>
<View style={styles.recapActions}>
<Button
title="Modifier"
onPress={() => setShowRecapModal(false)}
variant="outline"
size="md"
style={{ flex: 1 }}
/>
<Button
title="Confirmer"
onPress={handleCheckout}
loading={loading}
variant="primary"
size="md"
style={{ flex: 1 }}
/>
</View>
</Modal>
{/* Modal adresse invalide */} {/* Modal adresse invalide */}
<Modal <Modal
visible={invalidAddressModal} visible={invalidAddressModal}