xtablo-source/xtablo-expo/components/BillingPaywall.tsx
2026-05-03 09:28:46 +02:00

218 lines
5.3 KiB
TypeScript

import React from "react";
import {
ActivityIndicator,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
import { LinearGradient } from "expo-linear-gradient";
import type { RequiredOrganizationPlan } from "@/types/organization.types";
import type { BillingPackageOption } from "@/lib/purchases";
export type BillingPaywallProps = {
canManageBillingInApp: boolean;
errorMessage: string | null;
isLoadingPackages: boolean;
isRestoring: boolean;
isSyncing: boolean;
onPurchase: (pkg: BillingPackageOption) => void;
onRestore: () => void;
packages: BillingPackageOption[];
requiredPlan: RequiredOrganizationPlan;
};
export type { BillingPackageOption } from "@/lib/purchases";
export function BillingPaywall({
canManageBillingInApp,
errorMessage,
isLoadingPackages,
isRestoring,
isSyncing,
onPurchase,
onRestore,
packages,
requiredPlan,
}: BillingPaywallProps) {
if (!canManageBillingInApp) {
return (
<View style={styles.container}>
<Text style={styles.title}>Votre organisation n&apos;a pas encore d&apos;accès actif.</Text>
<Text style={styles.body}>
Pour activer l&apos;abonnement sur mobile, demandez au responsable de facturation de
gérer l&apos;offre depuis son compte.
</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>Débloquer XTablo sur iPhone</Text>
<Text style={styles.body}>
L&apos;accès s&apos;active quand la souscription est synchronisée dans votre organisation.
</Text>
{requiredPlan === "team" ? (
<Text style={styles.notice}>
Les forfaits équipe se gèrent sur le web. L&apos;offre annuelle reste disponible ici.
</Text>
) : null}
{isLoadingPackages ? (
<View style={styles.loadingRow}>
<ActivityIndicator />
<Text style={styles.loadingText}>Chargement des offres Apple</Text>
</View>
) : null}
{!isLoadingPackages
? packages.map((pkg) => (
<TouchableOpacity
activeOpacity={0.9}
key={pkg.id}
onPress={() => onPurchase(pkg)}
style={styles.cardShell}
>
<LinearGradient
colors={pkg.plan === "annual" ? ["#0f766e", "#115e59"] : ["#1d4ed8", "#1e40af"]}
end={{ x: 1, y: 1 }}
start={{ x: 0, y: 0 }}
style={styles.card}
>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}>{pkg.title}</Text>
<Text style={styles.cardPrice}>{pkg.price}</Text>
</View>
{pkg.description ? <Text style={styles.cardDescription}>{pkg.description}</Text> : null}
</LinearGradient>
</TouchableOpacity>
))
: null}
{isSyncing ? (
<View style={styles.syncBox}>
<Text style={styles.syncTitle}>Achat reçu</Text>
<Text style={styles.syncText}>
La synchronisation avec votre organisation est en cours. Cela peut prendre quelques
secondes.
</Text>
</View>
) : null}
{errorMessage ? <Text style={styles.errorText}>{errorMessage}</Text> : null}
<TouchableOpacity
activeOpacity={0.75}
disabled={isRestoring || isSyncing}
onPress={onRestore}
style={styles.restoreButton}
>
<Text style={styles.restoreText}>
{isRestoring ? "Restauration en cours…" : "Restaurer mes achats"}
</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
body: {
color: "#475569",
fontSize: 14,
lineHeight: 20,
},
card: {
borderRadius: 18,
gap: 10,
paddingHorizontal: 18,
paddingVertical: 18,
},
cardDescription: {
color: "rgba(255,255,255,0.85)",
fontSize: 13,
lineHeight: 18,
},
cardHeader: {
alignItems: "center",
flexDirection: "row",
justifyContent: "space-between",
},
cardPrice: {
color: "#ffffff",
fontSize: 20,
fontWeight: "700",
},
cardShell: {
marginTop: 12,
},
cardTitle: {
color: "#ffffff",
flex: 1,
fontSize: 16,
fontWeight: "700",
marginRight: 12,
},
container: {
gap: 10,
},
errorText: {
color: "#b91c1c",
fontSize: 13,
lineHeight: 18,
},
loadingRow: {
alignItems: "center",
flexDirection: "row",
gap: 10,
paddingVertical: 4,
},
loadingText: {
color: "#475569",
fontSize: 13,
},
notice: {
color: "#92400e",
fontSize: 13,
lineHeight: 18,
},
restoreButton: {
alignItems: "center",
borderColor: "#cbd5e1",
borderRadius: 14,
borderWidth: 1,
marginTop: 6,
paddingHorizontal: 16,
paddingVertical: 14,
},
restoreText: {
color: "#0f172a",
fontSize: 14,
fontWeight: "600",
},
syncBox: {
backgroundColor: "#eff6ff",
borderColor: "#bfdbfe",
borderRadius: 16,
borderWidth: 1,
paddingHorizontal: 14,
paddingVertical: 14,
},
syncText: {
color: "#1d4ed8",
fontSize: 13,
lineHeight: 18,
},
syncTitle: {
color: "#1e3a8a",
fontSize: 14,
fontWeight: "700",
marginBottom: 4,
},
title: {
color: "#0f172a",
fontSize: 18,
fontWeight: "700",
},
});