218 lines
5.3 KiB
TypeScript
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'a pas encore d'accès actif.</Text>
|
|
<Text style={styles.body}>
|
|
Pour activer l'abonnement sur mobile, demandez au responsable de facturation de
|
|
gérer l'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'accès s'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'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",
|
|
},
|
|
});
|