more tickets

This commit is contained in:
Thomas Demirdjian
2025-11-13 18:31:38 +01:00
parent dd4cf9b4d4
commit 5457030d4a
32 changed files with 913 additions and 558 deletions
@@ -15,7 +15,7 @@ import {
useWindowDimensions,
} from "react-native";
import { responsiveHeight } from "../../actions/responsiveSizes";
import { ai } from "../../assets";
import { ai, cardsImg } from "../../assets";
import { gutters } from "../../styles";
import { getCreationStageStates } from "../../utils/projectStages";
import AnimatedPaginationDot from "../AnimatedPaginationDot/AnimatedPaginationDot";
@@ -36,15 +36,15 @@ const STAGE_CARD_CONTENT = [
},
{
key: "director",
title: "John",
description: "Come back when your audio is ready!",
title: "Theo",
description: "Theo t'accompagne pour créer ton playback.",
image: ai.rightIcon,
},
{
key: "publisher",
title: "Bena",
title: "Publication",
description: "Ta vidéo est prête ? Direction YouTube !",
image: ai.bena,
image: cardsImg.production,
},
];
@@ -14,7 +14,7 @@ import {
View,
useWindowDimensions,
} from "react-native";
import { ai } from "../../assets";
import { ai, cardsImg } from "../../assets";
import { getCreationStageStates } from "../../utils/projectStages";
import AnimatedPaginationDot from "../AnimatedPaginationDot/AnimatedPaginationDot";
import PersonaCard from "../cards/PersonaCard/PersonaCard";
@@ -29,21 +29,21 @@ const STAGE_CARD_CONTENT = [
},
{
key: "beatmaker",
title: "Malik",
title: "Theo",
description: "Come back, when you'll have lyrics!",
image: ai.rightIcon,
},
{
key: "director",
title: "John",
description: "Come back when your audio is ready!",
title: "Theo",
description: "Theo t'accompagne pour créer ton playback.",
image: ai.rightIcon,
},
{
key: "publisher",
title: "Bena",
title: "Publication",
description: "Ta vidéo est prête ? Direction YouTube !",
image: ai.bena,
image: cardsImg.production,
},
];
+56 -76
View File
@@ -1,7 +1,6 @@
import React from "react";
import {
ActivityIndicator,
Linking,
Modal,
Pressable,
ScrollView,
@@ -15,16 +14,11 @@ import GradientButton from "../GradientButton";
import CreateLyricsHeader from "../../screens/Writing/components/CreateLyricsHeader";
import { Palette, gutters } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { getFunctionsClient } from "../../config/firebase";
import { isWeb } from "../../hooks/useLayoutType";
import CreditAmount from "../CreditAmount";
import { useStripe } from "../../providers/StripeProvider";
const WEB_MODAL_MAX_WIDTH = 1000;
const FUNCTIONS_REGION = "europe-west1";
const STRIPE_SUCCESS_URL =
"https://dashboard.stripe.com/test/billing/starter-guide/checkout-success";
const STRIPE_CANCEL_URL = STRIPE_SUCCESS_URL;
const formatCurrency = (amount, currency = "eur") => {
if (typeof amount !== "number") {
return null;
@@ -92,49 +86,60 @@ function CoinPackCard({ pack, selected, onSelect }) {
}
const CoinPackModal = ({ visible, onClose }) => {
const [coinPacks, setCoinPacks] = React.useState([]);
const [selectedPackId, setSelectedPackId] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
const [isProcessing, setIsProcessing] = React.useState(false);
const [errorMessage, setErrorMessage] = React.useState(null);
const modalMaxWidth = isWeb ? WEB_MODAL_MAX_WIDTH : undefined;
const fetchCoinPacks = React.useCallback(async () => {
setIsLoading(true);
setErrorMessage(null);
try {
const callable = getFunctionsClient(FUNCTIONS_REGION).httpsCallable(
"subscription-listCoinPacks",
);
const { data } = await callable();
const packs = Array.isArray(data?.packs) ? data.packs : [];
setCoinPacks(packs);
setSelectedPackId((current) => {
if (
current &&
packs.some((pack) => pack?.productId && pack.productId === current)
) {
return current;
}
return packs[0]?.productId || null;
});
} catch (error) {
console.error("[CoinPackModal] fetchCoinPacks error", error);
setErrorMessage(
error?.message ||
"Impossible de charger les packs de pièces. Réessaie plus tard.",
);
} finally {
setIsLoading(false);
const {
coinPacks,
isCatalogLoading,
catalogError,
refreshCatalog,
createCoinPackCheckout,
} = useStripe();
React.useEffect(() => {
if (!coinPacks.length) {
setSelectedPackId(null);
return;
}
}, []);
setSelectedPackId((current) => {
if (
current &&
coinPacks.some((pack) => pack?.productId && pack.productId === current)
) {
return current;
}
return coinPacks[0]?.productId || null;
});
}, [coinPacks]);
React.useEffect(() => {
if (!visible) {
return;
}
fetchCoinPacks();
}, [visible, fetchCoinPacks]);
if (!coinPacks.length && !isCatalogLoading && !catalogError) {
refreshCatalog();
}
}, [
visible,
coinPacks.length,
isCatalogLoading,
catalogError,
refreshCatalog,
]);
const closeModal = React.useCallback(
({ force = false } = {}) => {
if (isProcessing && !force) {
return;
}
setIsProcessing(false);
onClose?.();
},
[isProcessing, onClose],
);
const handleCheckout = React.useCallback(async () => {
if (!selectedPackId) {
@@ -145,33 +150,8 @@ const CoinPackModal = ({ visible, onClose }) => {
setErrorMessage(null);
try {
const callable = getFunctionsClient(FUNCTIONS_REGION).httpsCallable(
"subscription-createCoinPackCheckoutSession",
);
const { data } = await callable({
productId: selectedPackId,
returnUrls: {
successUrl: STRIPE_SUCCESS_URL,
cancelUrl: STRIPE_CANCEL_URL,
},
});
const checkoutUrl = data?.url;
if (!checkoutUrl) {
throw new Error("Session Stripe introuvable.");
}
if (isWeb) {
if (typeof window !== "undefined") {
window.location.assign(checkoutUrl);
}
} else {
const canOpen = await Linking.canOpenURL(checkoutUrl);
if (!canOpen) {
throw new Error("Impossible d'ouvrir l'URL de paiement.");
}
await Linking.openURL(checkoutUrl);
}
await createCoinPackCheckout(selectedPackId);
closeModal({ force: true });
} catch (error) {
console.error("[CoinPackModal] checkout error", error);
setErrorMessage(
@@ -181,17 +161,17 @@ const CoinPackModal = ({ visible, onClose }) => {
} finally {
setIsProcessing(false);
}
}, [selectedPackId, isWeb]);
}, [selectedPackId, createCoinPackCheckout, closeModal]);
const handleClose = React.useCallback(() => {
if (isProcessing) {
return;
}
onClose?.();
}, [isProcessing, onClose]);
closeModal();
}, [closeModal]);
const isLoadingCoinPacks = isCatalogLoading && !coinPacks.length;
const combinedErrorMessage = errorMessage || catalogError;
const renderContent = () => {
if (isLoading) {
if (isLoadingCoinPacks) {
return (
<View style={styles.loaderContainer}>
<ActivityIndicator color={Palette.white} />
@@ -250,8 +230,8 @@ const CoinPackModal = ({ visible, onClose }) => {
</Text>
</View>
{errorMessage ? (
<Text style={styles.errorText}>{errorMessage}</Text>
{combinedErrorMessage ? (
<Text style={styles.errorText}>{combinedErrorMessage}</Text>
) : null}
<View style={styles.content}>{renderContent()}</View>
@@ -263,7 +243,7 @@ const CoinPackModal = ({ visible, onClose }) => {
disabled={
!selectedPackId ||
isProcessing ||
isLoading ||
isLoadingCoinPacks ||
!coinPacks.length
}
/>