feat: modal & payments
This commit is contained in:
+32
-11
@@ -79,6 +79,18 @@ function SubscriptionCard({ plan, selected, onSelect }) {
|
||||
Number.isFinite(plan.coinsPerMonth)
|
||||
? Math.round(plan.coinsPerMonth)
|
||||
: null;
|
||||
const songsPerMonth =
|
||||
coinsPerMonth !== null
|
||||
? Math.floor(coinsPerMonth / CREDITS_PER_MUSIC)
|
||||
: null;
|
||||
const songsPerMonthLabel =
|
||||
songsPerMonth === null
|
||||
? null
|
||||
: songsPerMonth >= 1
|
||||
? `≈ ${songsPerMonth} ${
|
||||
songsPerMonth > 1 ? "musiques" : "musique"
|
||||
} / mois`
|
||||
: "Moins d'une musique / mois";
|
||||
const planBadgeKey = getPlanKeyForBadge(plan);
|
||||
const planBadgeSource =
|
||||
planBadgeKey && subBadges[planBadgeKey] ? subBadges[planBadgeKey] : null;
|
||||
@@ -145,6 +157,9 @@ function SubscriptionCard({ plan, selected, onSelect }) {
|
||||
<Text style={styles.coinsPerMonthSuffix}>/ mois</Text>
|
||||
</View>
|
||||
) : null}
|
||||
{songsPerMonthLabel ? (
|
||||
<Text style={styles.songsPerMonth}>{songsPerMonthLabel}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
@@ -225,7 +240,7 @@ const getPlanKeyForBadge = (plan) => {
|
||||
}
|
||||
|
||||
const match = Object.entries(PLAN_SYNONYMS).find(([, variants]) =>
|
||||
variants.some((variant) => label.includes(variant)),
|
||||
variants.some((variant) => label.includes(variant))
|
||||
);
|
||||
|
||||
return match ? match[0] : null;
|
||||
@@ -270,7 +285,7 @@ const normalizePlans = (plans = [], periodKey) => {
|
||||
}))
|
||||
.sort(
|
||||
(planA, planB) =>
|
||||
getPlanPriority(planA, periodKey) - getPlanPriority(planB, periodKey),
|
||||
getPlanPriority(planA, periodKey) - getPlanPriority(planB, periodKey)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -285,6 +300,7 @@ const PAGE_BACKGROUND_COLOR = "#303438";
|
||||
const SUBSCRIPTION_DISCLAIMER =
|
||||
"Résiliable en un clic à tout moment. Les prix sont indiqués TTC. Contacte-nous pour des besoins spécifiques (facturation annuelle, volume, offres éducation).";
|
||||
const CARD_MIN_HEIGHT = isWeb ? 320 : 240;
|
||||
const CREDITS_PER_MUSIC = 8;
|
||||
|
||||
export default function Payments() {
|
||||
const route = useRoute();
|
||||
@@ -319,7 +335,7 @@ export default function Payments() {
|
||||
monthly: normalizePlans(subscriptions?.monthly, "monthly"),
|
||||
annual: normalizePlans(subscriptions?.annual, "annual"),
|
||||
}),
|
||||
[subscriptions?.monthly, subscriptions?.annual],
|
||||
[subscriptions?.monthly, subscriptions?.annual]
|
||||
);
|
||||
const hasAnyPlan =
|
||||
(normalizedPlans?.monthly?.length || 0) > 0 ||
|
||||
@@ -341,12 +357,12 @@ export default function Payments() {
|
||||
if (shouldApplyPack && normalizedPlans) {
|
||||
const desired = initialSubscriptionPack.trim().toLowerCase();
|
||||
for (const [periodKey, mapping] of Object.entries(
|
||||
PACK_PRICE_ID_BY_PERIOD,
|
||||
PACK_PRICE_ID_BY_PERIOD
|
||||
)) {
|
||||
const candidateId = mapping[desired];
|
||||
if (!candidateId) continue;
|
||||
const exists = (normalizedPlans[periodKey] || []).some(
|
||||
(plan) => plan?.priceId === candidateId,
|
||||
(plan) => plan?.priceId === candidateId
|
||||
);
|
||||
if (exists) {
|
||||
matchedPeriod = periodKey;
|
||||
@@ -367,7 +383,7 @@ export default function Payments() {
|
||||
}
|
||||
|
||||
const alreadySelected = periodPlans.some(
|
||||
(plan) => plan.priceId === next[key],
|
||||
(plan) => plan.priceId === next[key]
|
||||
);
|
||||
|
||||
if (shouldApplyPack && matchedPeriod === null) {
|
||||
@@ -413,7 +429,7 @@ export default function Payments() {
|
||||
}));
|
||||
setProcessingPriceId(null);
|
||||
},
|
||||
[billingPeriod],
|
||||
[billingPeriod]
|
||||
);
|
||||
|
||||
const handleCheckout = React.useCallback(async () => {
|
||||
@@ -429,7 +445,7 @@ export default function Payments() {
|
||||
console.error("[Payments] checkout error", error);
|
||||
setErrorMessage(
|
||||
error?.message ||
|
||||
"Une erreur est survenue lors de la création de la session Stripe.",
|
||||
"Une erreur est survenue lors de la création de la session Stripe."
|
||||
);
|
||||
} finally {
|
||||
setProcessingPriceId(null);
|
||||
@@ -464,14 +480,14 @@ export default function Payments() {
|
||||
const currentPlanCount = currentPlans.length;
|
||||
const mobileActionSafePadding = React.useMemo(
|
||||
() => Math.max(insets.bottom, gutters),
|
||||
[insets.bottom],
|
||||
[insets.bottom]
|
||||
);
|
||||
|
||||
const mobileListContentInset = React.useMemo(
|
||||
() => ({
|
||||
paddingBottom: mobileActionSafePadding + gutters * 3,
|
||||
}),
|
||||
[mobileActionSafePadding],
|
||||
[mobileActionSafePadding]
|
||||
);
|
||||
|
||||
const renderHeaderSection = React.useCallback(() => {
|
||||
@@ -535,7 +551,7 @@ export default function Payments() {
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
[handleSelect, selectedPriceId],
|
||||
[handleSelect, selectedPriceId]
|
||||
);
|
||||
|
||||
const renderMobileEmptyComponent = React.useCallback(() => {
|
||||
@@ -909,6 +925,11 @@ const styles = StyleSheet.create({
|
||||
fontSize: 13,
|
||||
color: Palette.primary,
|
||||
},
|
||||
songsPerMonth: {
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 12,
|
||||
color: "rgba(255, 255, 255, 0.75)",
|
||||
},
|
||||
priceValue: {
|
||||
fontFamily: FONT_FAMILY.InterBold,
|
||||
fontSize: isWeb ? 22 : 20,
|
||||
|
||||
@@ -31,7 +31,6 @@ import { gutters, size } from "../../styles/Style";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
|
||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
import CreditAmount from "../../components/CreditAmount";
|
||||
|
||||
const MUSIC_GENERATION_COIN_COST = 8;
|
||||
const SONG_OPTIONS_PER_GENERATION = 2;
|
||||
@@ -528,7 +527,10 @@ const RegenerateModal = ({ visible, onClose, onConfirm }) => {
|
||||
const { width, height } = useWindowDimensions();
|
||||
const isCompactWidth = width < 420;
|
||||
const horizontalPadding = Math.max(isCompactWidth ? 16 : gutters, 12);
|
||||
const verticalPadding = Math.max(isCompactWidth ? gutters : gutters * 1.5, 12);
|
||||
const verticalPadding = Math.max(
|
||||
isCompactWidth ? gutters : gutters * 1.5,
|
||||
12
|
||||
);
|
||||
const availableWidth = Math.max(width - horizontalPadding * 2, 0);
|
||||
const contentWidth =
|
||||
availableWidth > 0
|
||||
|
||||
Reference in New Issue
Block a user