chore: fix UI bug
This commit is contained in:
@@ -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)}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user