From 812cf00cefa6bf4f5b1071e6b55e4d49ac3fa648 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sun, 3 May 2026 17:12:38 +0200 Subject: [PATCH] feat: detect telegram notif is enabled or no --- mobile/src/api/api.ts | 4 + mobile/src/screens/client/CheckoutScreen.tsx | 852 +++++++++++++++---- 2 files changed, 684 insertions(+), 172 deletions(-) diff --git a/mobile/src/api/api.ts b/mobile/src/api/api.ts index 63fdbc27..8f279981 100644 --- a/mobile/src/api/api.ts +++ b/mobile/src/api/api.ts @@ -786,6 +786,7 @@ export interface PublicSettings { crypto_payment_enabled: boolean; crypto_only: boolean; nowpayments_currencies: string[]; + telegram_notifications_enabled: boolean; } export const getPublicSettings = async (): Promise => { @@ -799,6 +800,7 @@ export const getPublicSettings = async (): Promise => { crypto_payment_enabled: false, crypto_only: false, nowpayments_currencies: [], + telegram_notifications_enabled: false, }; try { const { data } = await apiClient.get(`${V1}/app-settings`); @@ -817,6 +819,8 @@ export const getPublicSettings = async (): Promise => { nowpayments_currencies: Array.isArray(data.nowpayments_currencies) ? data.nowpayments_currencies : [], + telegram_notifications_enabled: + data.telegram_notifications_enabled ?? false, }; } catch { return defaults; diff --git a/mobile/src/screens/client/CheckoutScreen.tsx b/mobile/src/screens/client/CheckoutScreen.tsx index c6832e4a..402676a6 100644 --- a/mobile/src/screens/client/CheckoutScreen.tsx +++ b/mobile/src/screens/client/CheckoutScreen.tsx @@ -54,13 +54,21 @@ export default function CheckoutScreen() { const [referralBalance, setReferralBalance] = useState(0); const [useReferral, setUseReferral] = useState(false); + // Notif telegram + const [telegramNotificationEnabled, setTelegramNotificationEnabled] = + useState(false); + // Crypto const [cryptoEnabled, setCryptoEnabled] = useState(false); const [cryptoOnly, setCryptoOnly] = useState(false); const [cryptoCurrencies, setCryptoCurrencies] = useState([]); - const [paymentMethod, setPaymentMethod] = useState<"especes" | "crypto">("especes"); + const [paymentMethod, setPaymentMethod] = useState<"especes" | "crypto">( + "especes", + ); const [payCurrency, setPayCurrency] = useState(""); - const [cryptoData, setCryptoData] = useState(null); + const [cryptoData, setCryptoData] = useState( + null, + ); const [showCryptoModal, setShowCryptoModal] = useState(false); const [cryptoPolling, setCryptoPolling] = useState(false); const [showRecapModal, setShowRecapModal] = useState(false); @@ -71,7 +79,13 @@ export default function CheckoutScreen() { if (res.success && res.balance > 0) setReferralBalance(res.balance); }); getPublicSettings().then((settings) => { - if (settings.crypto_payment_enabled && settings.nowpayments_currencies.length > 0) { + setTelegramNotificationEnabled( + settings.telegram_notifications_enabled, + ); + if ( + settings.crypto_payment_enabled && + settings.nowpayments_currencies.length > 0 + ) { setCryptoEnabled(true); setCryptoCurrencies(settings.nowpayments_currencies); setPayCurrency(settings.nowpayments_currencies[0]); @@ -116,7 +130,9 @@ export default function CheckoutScreen() { } }, 10000); }; - + const handleTelegramNotification = () => { + setTelegramNotificationEnabled((prev) => !prev); + }; const stopPolling = () => { setCryptoPolling(false); if (pollRef.current) { @@ -126,11 +142,26 @@ export default function CheckoutScreen() { }; const handleValidateAndShowRecap = () => { - if (!nom.trim()) { setError("Veuillez saisir votre nom"); return; } - if (!prenom.trim()) { setError("Veuillez saisir votre prenom"); return; } - if (!telephone.trim()) { setError("Veuillez saisir votre numero de telephone"); return; } - if (!address.trim()) { setError("Veuillez saisir une adresse de livraison"); return; } - if (cartItems.length === 0) { setError("Votre panier est vide"); return; } + if (!nom.trim()) { + setError("Veuillez saisir votre nom"); + return; + } + if (!prenom.trim()) { + setError("Veuillez saisir votre prenom"); + return; + } + if (!telephone.trim()) { + setError("Veuillez saisir votre numero de telephone"); + return; + } + if (!address.trim()) { + setError("Veuillez saisir une adresse de livraison"); + return; + } + if (cartItems.length === 0) { + setError("Votre panier est vide"); + return; + } if (paymentMethod === "crypto" && !payCurrency) { setError("Veuillez sélectionner une cryptomonnaie"); return; @@ -204,18 +235,26 @@ export default function CheckoutScreen() { const cryptoStatusLabel = (status: string) => { switch (status) { - case "waiting": return "⏳ En attente de paiement"; - case "confirming": return "🔄 Confirmation en cours..."; - case "confirmed": return "✅ Confirmé"; - case "finished": return "✅ Paiement reçu !"; - case "failed": return "❌ Paiement échoué"; - case "expired": return "⌛ Expiré"; - default: return status; + case "waiting": + return "⏳ En attente de paiement"; + case "confirming": + return "🔄 Confirmation en cours..."; + case "confirmed": + return "✅ Confirmé"; + case "finished": + return "✅ Paiement reçu !"; + case "failed": + return "❌ Paiement échoué"; + case "expired": + return "⌛ Expiré"; + default: + return status; } }; const cryptoStatusColor = (status: string) => { - if (status === "finished" || status === "confirmed") return colors.success; + if (status === "finished" || status === "confirmed") + return colors.success; if (status === "failed" || status === "expired") return colors.danger; if (status === "confirming") return colors.info; return colors.warning; @@ -254,8 +293,14 @@ export default function CheckoutScreen() { borderBottomColor: colors.borderLight, }, summaryItemLeft: { flex: 1, marginRight: spacing.m }, - summaryItemName: { color: colors.textPrimary, fontSize: fontSize.md }, - summaryItemQty: { color: colors.textMuted, fontSize: fontSize.sm }, + summaryItemName: { + color: colors.textPrimary, + fontSize: fontSize.md, + }, + summaryItemQty: { + color: colors.textMuted, + fontSize: fontSize.sm, + }, summaryItemPrice: { color: colors.textPrimary, fontSize: fontSize.md, @@ -296,20 +341,34 @@ export default function CheckoutScreen() { alignItems: "center", justifyContent: "space-between", }, - referralLeft: { flexDirection: "row", alignItems: "center", gap: spacing.s, flex: 1 }, + referralLeft: { + flexDirection: "row", + alignItems: "center", + gap: spacing.s, + flex: 1, + }, referralTitle: { color: colors.textPrimary, fontSize: fontSize.md, fontWeight: fontWeight.semibold, }, - referralAmount: { color: colors.accent, fontSize: fontSize.sm, marginTop: 2 }, + referralAmount: { + color: colors.accent, + fontSize: fontSize.sm, + marginTop: 2, + }, referralHint: { color: colors.textMuted, fontSize: fontSize.xs, marginTop: spacing.s, lineHeight: 16, }, - prefillHint: { color: "#6ee7b7", fontSize: fontSize.xs, marginTop: 4, opacity: 0.85 }, + prefillHint: { + color: "#6ee7b7", + fontSize: fontSize.xs, + marginTop: 4, + opacity: 0.85, + }, telegramNote: { flexDirection: "row", gap: spacing.s, @@ -341,93 +400,193 @@ export default function CheckoutScreen() { invalidAddrContent: { gap: spacing.m }, invalidAddrIconContainer: { alignItems: "center" }, invalidAddrIconCircle: { - width: 64, height: 64, borderRadius: 32, + width: 64, + height: 64, + borderRadius: 32, backgroundColor: "rgba(251,191,36,0.12)", - justifyContent: "center", alignItems: "center", + justifyContent: "center", + alignItems: "center", }, invalidAddrLabel: { - color: colors.textSecondary, fontSize: fontSize.sm, textAlign: "center", + color: colors.textSecondary, + fontSize: fontSize.sm, + textAlign: "center", }, invalidAddrSuggestion: { - backgroundColor: colors.bgInput, borderRadius: 10, - padding: spacing.m, borderWidth: 1, borderColor: colors.borderLight, + backgroundColor: colors.bgInput, + borderRadius: 10, + padding: spacing.m, + borderWidth: 1, + borderColor: colors.borderLight, }, invalidAddrSuggestionText: { - color: colors.textPrimary, fontSize: fontSize.md, - fontWeight: fontWeight.semibold, textAlign: "center", + color: colors.textPrimary, + fontSize: fontSize.md, + fontWeight: fontWeight.semibold, + textAlign: "center", + }, + invalidAddrActions: { + flexDirection: "row", + gap: spacing.m, + marginTop: spacing.s, }, - invalidAddrActions: { flexDirection: "row", gap: spacing.m, marginTop: spacing.s }, confirmContent: { gap: spacing.m }, confirmIconContainer: { alignItems: "center" }, confirmIconCircle: { - width: 72, height: 72, borderRadius: 36, + width: 72, + height: 72, + borderRadius: 36, backgroundColor: "rgba(74,222,128,0.1)", - justifyContent: "center", alignItems: "center", + justifyContent: "center", + alignItems: "center", }, confirmText: { - color: colors.textPrimary, fontSize: fontSize.md, - textAlign: "center", lineHeight: 22, + color: colors.textPrimary, + fontSize: fontSize.md, + textAlign: "center", + lineHeight: 22, }, confirmInfo: { - flexDirection: "row", alignItems: "center", gap: spacing.s, - backgroundColor: colors.bgInput, padding: spacing.m, - borderRadius: 12, borderWidth: 1, borderColor: colors.borderLight, + flexDirection: "row", + alignItems: "center", + gap: spacing.s, + backgroundColor: colors.bgInput, + padding: spacing.m, + borderRadius: 12, + borderWidth: 1, + borderColor: colors.borderLight, + }, + confirmInfoText: { + color: colors.textSecondary, + fontSize: fontSize.sm, + flex: 1, }, - confirmInfoText: { color: colors.textSecondary, fontSize: fontSize.sm, flex: 1 }, // Méthode de paiement - paymentMethodRow: { flexDirection: "row", gap: spacing.m, marginBottom: spacing.m }, + paymentMethodRow: { + flexDirection: "row", + gap: spacing.m, + marginBottom: spacing.m, + }, paymentMethodBtn: { - flex: 1, padding: spacing.m, borderRadius: borderRadius.md, - borderWidth: 2, borderColor: colors.borderLight, + flex: 1, + padding: spacing.m, + borderRadius: borderRadius.md, + borderWidth: 2, + borderColor: colors.borderLight, backgroundColor: colors.bgCard, - alignItems: "center", justifyContent: "center", gap: spacing.xs, + alignItems: "center", + justifyContent: "center", + gap: spacing.xs, }, paymentMethodBtnActive: { borderColor: CRYPTO_COLOR }, - paymentMethodBtnText: { color: colors.textMuted, fontSize: fontSize.sm, fontWeight: fontWeight.medium }, + paymentMethodBtnText: { + color: colors.textMuted, + fontSize: fontSize.sm, + fontWeight: fontWeight.medium, + }, paymentMethodBtnTextActive: { color: CRYPTO_COLOR }, // Sélection devise crypto - currencyGrid: { flexDirection: "row", flexWrap: "wrap", gap: spacing.s, marginTop: spacing.s }, + currencyGrid: { + flexDirection: "row", + flexWrap: "wrap", + gap: spacing.s, + marginTop: spacing.s, + }, currencyBtn: { - paddingHorizontal: spacing.m, paddingVertical: spacing.s, - borderRadius: borderRadius.sm, borderWidth: 2, - borderColor: colors.borderLight, backgroundColor: colors.bgCard, + paddingHorizontal: spacing.m, + paddingVertical: spacing.s, + borderRadius: borderRadius.sm, + borderWidth: 2, + borderColor: colors.borderLight, + backgroundColor: colors.bgCard, + }, + currencyBtnActive: { + borderColor: CRYPTO_COLOR, + backgroundColor: "rgba(247,147,26,0.08)", + }, + currencyBtnText: { + color: colors.textMuted, + fontSize: fontSize.sm, + fontWeight: fontWeight.semibold, }, - currencyBtnActive: { borderColor: CRYPTO_COLOR, backgroundColor: "rgba(247,147,26,0.08)" }, - currencyBtnText: { color: colors.textMuted, fontSize: fontSize.sm, fontWeight: fontWeight.semibold }, currencyBtnTextActive: { color: CRYPTO_COLOR }, - cryptoHint: { color: colors.textMuted, fontSize: fontSize.xs, marginTop: spacing.s }, + cryptoHint: { + color: colors.textMuted, + fontSize: fontSize.xs, + marginTop: spacing.s, + }, // Modal crypto cryptoContent: { gap: spacing.m }, - cryptoRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" }, - cryptoLabel: { color: colors.textSecondary, fontSize: fontSize.sm }, - cryptoValue: { color: colors.textPrimary, fontSize: fontSize.md, fontWeight: fontWeight.semibold }, + cryptoRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + }, + cryptoLabel: { + color: colors.textSecondary, + fontSize: fontSize.sm, + }, + cryptoValue: { + color: colors.textPrimary, + fontSize: fontSize.md, + fontWeight: fontWeight.semibold, + }, cryptoAddressBox: { - backgroundColor: colors.bgInput, borderRadius: borderRadius.md, - padding: spacing.m, borderWidth: 1, borderColor: colors.borderLight, + backgroundColor: colors.bgInput, + borderRadius: borderRadius.md, + padding: spacing.m, + borderWidth: 1, + borderColor: colors.borderLight, gap: spacing.s, }, cryptoAddressText: { - color: colors.textPrimary, fontSize: fontSize.xs, + color: colors.textPrimary, + fontSize: fontSize.xs, fontFamily: Platform.OS === "ios" ? "Courier" : "monospace", }, cryptoCopyBtn: { - flexDirection: "row", alignItems: "center", justifyContent: "center", - gap: spacing.xs, padding: spacing.s, borderRadius: borderRadius.sm, - backgroundColor: colors.bgCard, borderWidth: 1, borderColor: colors.borderLight, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: spacing.xs, + padding: spacing.s, + borderRadius: borderRadius.sm, + backgroundColor: colors.bgCard, + borderWidth: 1, + borderColor: colors.borderLight, + }, + cryptoCopyText: { + color: colors.textSecondary, + fontSize: fontSize.sm, }, - cryptoCopyText: { color: colors.textSecondary, fontSize: fontSize.sm }, cryptoPollingText: { - color: colors.textMuted, fontSize: fontSize.xs, textAlign: "center", + color: colors.textMuted, + fontSize: fontSize.xs, + textAlign: "center", + }, + cryptoSuccessText: { + color: colors.success, + fontSize: fontSize.sm, + textAlign: "center", }, - cryptoSuccessText: { color: colors.success, fontSize: fontSize.sm, textAlign: "center" }, // Crypto only badge cryptoOnlyBadge: { - flexDirection: "row", alignItems: "center", gap: spacing.s, - backgroundColor: "rgba(247,147,26,0.10)", borderRadius: borderRadius.md, - borderWidth: 1, borderColor: CRYPTO_COLOR + "55", - padding: spacing.m, marginBottom: spacing.s, + flexDirection: "row", + alignItems: "center", + gap: spacing.s, + backgroundColor: "rgba(247,147,26,0.10)", + borderRadius: borderRadius.md, + borderWidth: 1, + borderColor: CRYPTO_COLOR + "55", + 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: { @@ -439,26 +598,58 @@ export default function CheckoutScreen() { 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 }, + 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 }, + 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 }, + 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], ); @@ -471,77 +662,154 @@ export default function CheckoutScreen() { {/* Résumé commande */} - Resume de la commande + + Resume de la commande + {cartItems.map((item) => ( - + {item.name_product} - {item.quantity}g + + {item.quantity}g + - {item.price.toFixed(2)} € + + {item.price.toFixed(2)} € + ))} Total - {cartTotal.toFixed(2)} € + + {cartTotal.toFixed(2)} € + {/* Infos personnelles */} - Informations personnelles + + Informations personnelles + { setNom(t); setError(null); }} - icon={} - error={error && !nom.trim() ? error : undefined} + onChangeText={(t) => { + setNom(t); + setError(null); + }} + icon={ + + } + error={ + error && !nom.trim() ? error : undefined + } /> { setPrenom(t); setError(null); }} - icon={} - error={error && nom.trim() && !prenom.trim() ? error : undefined} + onChangeText={(t) => { + setPrenom(t); + setError(null); + }} + icon={ + + } + error={ + error && nom.trim() && !prenom.trim() + ? error + : undefined + } /> { setTelephone(t); setError(null); }} - icon={} + onChangeText={(t) => { + setTelephone(t); + setError(null); + }} + icon={ + + } keyboardType="phone-pad" - error={error && nom.trim() && prenom.trim() && !telephone.trim() ? error : undefined} + error={ + error && + nom.trim() && + prenom.trim() && + !telephone.trim() + ? error + : undefined + } /> - {!!telephone && Pre-rempli depuis votre profil} + {!!telephone && ( + + Pre-rempli depuis votre profil + + )} {/* Adresse */} - Adresse de livraison + + Adresse de livraison + { setAddress(t); setError(null); }} - icon={} - error={error && nom.trim() && prenom.trim() && telephone.trim() && !address.trim() ? error : undefined} + onChangeText={(t) => { + setAddress(t); + setError(null); + }} + icon={ + + } + error={ + error && + nom.trim() && + prenom.trim() && + telephone.trim() && + !address.trim() + ? error + : undefined + } multiline numberOfLines={3} /> {!!address && ( - Pre-rempli depuis votre profil — modifiez si vous etes ailleurs + Pre-rempli depuis votre profil — modifiez si vous + etes ailleurs )} @@ -551,9 +819,15 @@ export default function CheckoutScreen() { {cryptoOnly ? ( <> - Cryptomonnaie + + Cryptomonnaie + - + Paiement uniquement en cryptomonnaie @@ -562,60 +836,128 @@ export default function CheckoutScreen() { {cryptoCurrencies.map((c) => ( setPayCurrency(c)} > - + {c.toUpperCase()} ))} - Choisissez la devise avec laquelle vous souhaitez payer + Choisissez la devise avec laquelle vous + souhaitez payer ) : ( <> - Méthode de paiement + + Méthode de paiement + setPaymentMethod("especes")} + style={[ + styles.paymentMethodBtn, + paymentMethod === "especes" && + styles.paymentMethodBtnActive, + ]} + onPress={() => + setPaymentMethod("especes") + } > - - + + Espèces setPaymentMethod("crypto")} + style={[ + styles.paymentMethodBtn, + paymentMethod === "crypto" && + styles.paymentMethodBtnActive, + ]} + onPress={() => + setPaymentMethod("crypto") + } > - - + + Crypto {paymentMethod === "crypto" && ( <> - Cryptomonnaie + + Cryptomonnaie + {cryptoCurrencies.map((c) => ( setPayCurrency(c)} + style={[ + styles.currencyBtn, + payCurrency === c && + styles.currencyBtnActive, + ]} + onPress={() => + setPayCurrency(c) + } > - + {c.toUpperCase()} ))} - Vous recevrez l'adresse wallet après validation + Vous recevrez l'adresse wallet après + validation )} @@ -629,45 +971,86 @@ export default function CheckoutScreen() { - + - Credit parrainage - {referralBalance.toFixed(2)} € disponibles + + Credit parrainage + + + {referralBalance.toFixed(2)} € + disponibles + {useReferral && ( - Le credit sera deduit de ta commande. Tu dois quand meme atteindre le minimum de ta zone + le credit utilise. + Le credit sera deduit de ta commande. Tu dois + quand meme atteindre le minimum de ta zone + le + credit utilise. )} )} {/* Note Telegram */} - - - - Compte Telegram requis - - Votre commande ne pourra être validée que si votre compte Telegram est lié.{" "} - - navigation.navigate("ClientTabs")}> - Lier depuis votre profil → - - - + {telegramNotificationEnabled && ( + + - {error && nom.trim() && prenom.trim() && telephone.trim() && address.trim() && ( - {error} + + + Compte Telegram requis + + + + Votre commande ne pourra être validée que si + votre compte Telegram est lié. + + + + navigation.navigate("ClientTabs") + } + > + + Lier depuis votre profil → + + + + )} + {error && + nom.trim() && + prenom.trim() && + telephone.trim() && + address.trim() && ( + {error} + )} +