more tickets
This commit is contained in:
@@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user