331 lines
9.5 KiB
JavaScript
331 lines
9.5 KiB
JavaScript
/* eslint-disable react/display-name */
|
|
import { Image, Pressable, Text, View } from "react-native";
|
|
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
import React from "reactn";
|
|
import { responsiveHeight } from "../actions/responsiveSizes.js";
|
|
import { icons, subBadges } from "../assets";
|
|
import BaseHeader from "../components/BaseHeader";
|
|
import CoinIcon from "../components/CoinIcon";
|
|
import ConnectBtn from "../components/ConnectBtn.js";
|
|
import CoinPackModal from "../components/modal/CoinPackModal";
|
|
import NavigateHeader from "../components/NavigateHeader";
|
|
import ShareBtn from "../components/ShareBtn/ShareBtn";
|
|
import { isWeb } from "../hooks/useLayoutType.js";
|
|
import { useUserData } from "../providers/UserDataProvider";
|
|
import { Routes } from "../navigation";
|
|
import { navigate } from "../navigation/NavigationService";
|
|
import { gutters } from "../styles";
|
|
import { FONT_FAMILY } from "../styles/Fonts";
|
|
import { subscribeCoinPackModal } from "../utils/coinPackModal";
|
|
|
|
export default ({
|
|
children,
|
|
|
|
topStickyContent = null,
|
|
bottomStickyContent = null,
|
|
|
|
title = "",
|
|
rightComponent = null,
|
|
|
|
containerType = "SAFE_AREA_VIEW",
|
|
headerType = "BASE",
|
|
|
|
containerStyle = {},
|
|
headerStyle = {},
|
|
contentContainerStyle = {},
|
|
|
|
scrollEnabled = false,
|
|
|
|
onBackPressed = null,
|
|
backgroundImg = null,
|
|
headerTitleStyle = {},
|
|
hideBackButton = false,
|
|
width = undefined,
|
|
maxWidth = 1200,
|
|
shareBtn = false,
|
|
connect = false,
|
|
blurIntensity = 0,
|
|
backgroundColor = "#000",
|
|
showCoin = true,
|
|
}) => {
|
|
const { currentUID = null, currentUserData = null } = useUserData?.() || {};
|
|
|
|
const coinBalance = React.useMemo(() => {
|
|
const value = currentUserData?.coins;
|
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
return value;
|
|
}
|
|
if (typeof value === "string") {
|
|
const parsed = Number(value);
|
|
if (Number.isFinite(parsed)) {
|
|
return parsed;
|
|
}
|
|
}
|
|
return 0;
|
|
}, [currentUserData?.coins]);
|
|
|
|
const formattedCoins = React.useMemo(() => {
|
|
try {
|
|
return new Intl.NumberFormat("fr-FR", {
|
|
maximumFractionDigits: 0,
|
|
}).format(coinBalance);
|
|
} catch (_error) {
|
|
return `${coinBalance}`;
|
|
}
|
|
}, [coinBalance]);
|
|
|
|
const showCoinBadge = isWeb && !!currentUID && showCoin;
|
|
const [isCoinModalVisible, setCoinModalVisible] = React.useState(false);
|
|
|
|
const subscriptionBadgeSource = React.useMemo(() => {
|
|
const allowedLevels = new Set(["starter", "pro", "premium"]);
|
|
const pickLevel = (value) => {
|
|
if (typeof value !== "string") {
|
|
return null;
|
|
}
|
|
const normalized = value.trim().toLowerCase();
|
|
return normalized && allowedLevels.has(normalized) ? normalized : null;
|
|
};
|
|
|
|
const nestedSubscription =
|
|
currentUserData?.subscription &&
|
|
typeof currentUserData.subscription === "object"
|
|
? currentUserData.subscription
|
|
: null;
|
|
|
|
const stripeMetadata =
|
|
currentUserData?.stripeSubscription &&
|
|
typeof currentUserData.stripeSubscription === "object" &&
|
|
typeof currentUserData.stripeSubscription.metadata === "object"
|
|
? currentUserData.stripeSubscription.metadata
|
|
: null;
|
|
|
|
const directCandidates = [
|
|
pickLevel(currentUserData?.premiumLevel),
|
|
pickLevel(currentUserData?.subscriptionLevel),
|
|
pickLevel(currentUserData?.subscriptionPlan),
|
|
pickLevel(currentUserData?.subscriptionPack),
|
|
];
|
|
|
|
const nestedCandidates = nestedSubscription
|
|
? ["level", "plan", "pack", "type"].map((key) =>
|
|
pickLevel(nestedSubscription?.[key]),
|
|
)
|
|
: [];
|
|
|
|
const metadataCandidates = stripeMetadata
|
|
? [
|
|
"level",
|
|
"subscriptionLevel",
|
|
"subscription_level",
|
|
"pack",
|
|
"Pack",
|
|
].map((key) => pickLevel(stripeMetadata?.[key]))
|
|
: [];
|
|
|
|
const resolvedLevel = [
|
|
...directCandidates,
|
|
...nestedCandidates,
|
|
...metadataCandidates,
|
|
].find(Boolean);
|
|
|
|
return resolvedLevel ? subBadges[resolvedLevel] || null : null;
|
|
}, [currentUserData]);
|
|
|
|
const handleCoinPress = React.useCallback(() => {
|
|
if (!showCoinBadge) return;
|
|
setCoinModalVisible(true);
|
|
}, [showCoinBadge]);
|
|
|
|
const handleCloseCoinModal = React.useCallback(() => {
|
|
setCoinModalVisible(false);
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
if (!showCoinBadge && isCoinModalVisible) {
|
|
setCoinModalVisible(false);
|
|
}
|
|
}, [showCoinBadge, isCoinModalVisible]);
|
|
|
|
React.useEffect(() => {
|
|
const unsubscribe = subscribeCoinPackModal(() => {
|
|
if (!showCoinBadge) {
|
|
return;
|
|
}
|
|
setCoinModalVisible(true);
|
|
});
|
|
return unsubscribe;
|
|
}, [showCoinBadge]);
|
|
|
|
const PageContainer =
|
|
containerType === "SAFE_AREA_VIEW" ? SafeAreaView : View;
|
|
|
|
const ContentContainer = scrollEnabled ? KeyboardAwareScrollView : View;
|
|
|
|
const computedWidth = width ?? (isWeb ? "60%" : "100%");
|
|
|
|
const shareButtonElement = (() => {
|
|
if (!shareBtn) return null;
|
|
if (typeof React?.isValidElement === "function") {
|
|
if (React.isValidElement(shareBtn)) {
|
|
return shareBtn;
|
|
}
|
|
}
|
|
if (typeof shareBtn === "object" && !Array.isArray(shareBtn)) {
|
|
return <ShareBtn {...shareBtn} />;
|
|
}
|
|
return <ShareBtn />;
|
|
})();
|
|
|
|
return (
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
minHeight: isWeb ? "100svh" : "100%",
|
|
position: "relative",
|
|
// paddingHorizontal: isWeb ? gutters : 0,
|
|
backgroundColor: backgroundColor,
|
|
}}
|
|
>
|
|
{backgroundImg ? (
|
|
<Image
|
|
source={backgroundImg}
|
|
resizeMode="cover"
|
|
// blurRadius={!isWeb ? blurIntensity : undefined}
|
|
blurIntensity={blurIntensity}
|
|
style={{
|
|
position: "absolute",
|
|
top: 0,
|
|
left: 0,
|
|
width: "100%",
|
|
height: "100%",
|
|
...(isWeb && blurIntensity
|
|
? { filter: `blur(${blurIntensity}px)` }
|
|
: {}),
|
|
}}
|
|
/>
|
|
) : null}
|
|
{showCoinBadge ? (
|
|
<View
|
|
style={{
|
|
position: "absolute",
|
|
top: gutters,
|
|
left: gutters,
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 6,
|
|
zIndex: 20,
|
|
}}
|
|
>
|
|
<Pressable
|
|
onPress={handleCoinPress}
|
|
accessibilityRole="button"
|
|
style={{
|
|
paddingHorizontal: 14,
|
|
paddingVertical: 8,
|
|
borderRadius: 20,
|
|
backgroundColor: "rgba(12, 14, 18, 0.72)",
|
|
borderWidth: 1,
|
|
borderColor: "rgba(255, 255, 255, 0.1)",
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 8,
|
|
}}
|
|
>
|
|
<CoinIcon size={22} />
|
|
<Text
|
|
style={{
|
|
color: "#ffffff",
|
|
fontFamily: FONT_FAMILY.InterSemiBold,
|
|
fontSize: 14,
|
|
}}
|
|
>
|
|
{formattedCoins}
|
|
</Text>
|
|
</Pressable>
|
|
{subscriptionBadgeSource ? (
|
|
<Pressable
|
|
onPress={() => navigate(Routes.ManageSubscription)}
|
|
accessibilityRole="button"
|
|
style={{ paddingVertical: 4 }}
|
|
>
|
|
<Image
|
|
source={subscriptionBadgeSource}
|
|
style={{ height: 40, width: 40, resizeMode: "contain" }}
|
|
/>
|
|
</Pressable>
|
|
) : null}
|
|
</View>
|
|
) : null}
|
|
<PageContainer
|
|
{...(containerType === "SAFE_AREA_VIEW" ? { edges: ["top"] } : {})}
|
|
style={{
|
|
height: "100%",
|
|
paddingTop: isWeb ? gutters / 2 : 0,
|
|
padding: gutters,
|
|
paddingBottom: 0,
|
|
...(isWeb ? { maxHeight: "100svh" } : {}),
|
|
width: computedWidth,
|
|
maxWidth: maxWidth ? maxWidth : null,
|
|
alignSelf: isWeb ? "center" : "auto",
|
|
...containerStyle,
|
|
}}
|
|
>
|
|
{headerType !== "NONE" ? (
|
|
headerType === "BASE" ? (
|
|
<BaseHeader containerStyle={{ ...headerStyle }} />
|
|
) : (
|
|
<NavigateHeader
|
|
containerStyle={{ ...headerStyle }}
|
|
title={title}
|
|
rightComponent={rightComponent}
|
|
onBackPressed={onBackPressed}
|
|
headerTitleStyle={headerTitleStyle}
|
|
hideBackButton={hideBackButton}
|
|
/>
|
|
)
|
|
) : null}
|
|
|
|
{topStickyContent?.()}
|
|
|
|
<ContentContainer
|
|
style={{ flex: 1, ...contentContainerStyle }}
|
|
{...(scrollEnabled
|
|
? {
|
|
scrollEventThrottle: 80,
|
|
showsVerticalScrollIndicator: false,
|
|
contentContainerStyle: { paddingBottom: responsiveHeight(55) },
|
|
keyboardDismissMode: "on-drag",
|
|
}
|
|
: {})}
|
|
>
|
|
{children}
|
|
</ContentContainer>
|
|
</PageContainer>
|
|
|
|
{bottomStickyContent?.()}
|
|
{(shareButtonElement || connect) && isWeb && (
|
|
<View
|
|
style={{
|
|
position: "absolute",
|
|
top: 20,
|
|
right: 20,
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 12, // ou marginLeft sur chaque bouton si gap non supporté
|
|
}}
|
|
>
|
|
{shareButtonElement}
|
|
{connect && <ConnectBtn />}
|
|
</View>
|
|
)}
|
|
|
|
<CoinPackModal
|
|
visible={isCoinModalVisible}
|
|
onClose={handleCloseCoinModal}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|