Created
December 8, 2025 19:46
-
-
Save WisaniShilumani/e676506de705551547712971b3ac3c0d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Text, View, Platform, StyleSheet, Animated } from 'react-native' | |
| import Colors from '@colors/index' | |
| import { useAppData } from '@context/app-context' | |
| import { Subscription } from '@common/enums' | |
| import { Ionicons, MaterialIcons } from '@expo/vector-icons' | |
| import { useMemo, useEffect, useRef } from 'react' | |
| import { useInAppPurchaseContext } from '@context/purchase-context' | |
| import format from 'date-fns/format' | |
| import { HeaderLogoWhite } from '@components/header-logo' | |
| import { LinearGradient } from 'expo-linear-gradient' | |
| import { MAXIMUM_USER_ACTIVE_CHATS_EXECUTIVE, MAXIMUM_USER_ACTIVE_CHATS_GOLD } from '@common/constants' | |
| import Accordion from '@components/accordion' | |
| import ZoomInView from '@components/animators/zoom-in' | |
| import addDays from 'date-fns/addDays' | |
| const FeatureLikes = { | |
| title: 'Priority likes', | |
| description: 'Get seen by matches sooner', | |
| icon: 'heart-outline', | |
| } | |
| const FeatureFilters = { | |
| title: 'Advanced filters', | |
| description: "Find exactly who you're looking for", | |
| icon: 'options', | |
| } | |
| const FeatureReadReceipts = { | |
| title: 'Read receipts', | |
| description: 'See if matches read your messages', | |
| icon: 'checkmark-done', | |
| } | |
| const FeatureUnlimitedLikes = { | |
| title: 'Unlimited likes', | |
| description: 'Like as many profiles as possible', | |
| icon: 'infinite', | |
| } | |
| const AndMore = { | |
| title: 'And more...', | |
| description: 'See who likes you (unlimited), 7 active chats, five free roses and a free spotlight a week.', | |
| icon: 'ellipsis-horizontal', | |
| } | |
| const FeatureChatsGold = { | |
| title: `${MAXIMUM_USER_ACTIVE_CHATS_GOLD} chats`, | |
| description: 'Chat with as many people as you want', | |
| icon: 'chatbubble-outline', | |
| } | |
| const FeatureChatsExecutive = { | |
| title: `${MAXIMUM_USER_ACTIVE_CHATS_EXECUTIVE} chats`, | |
| description: 'Chat with as many people as you want', | |
| icon: 'chatbubble-outline', | |
| } | |
| const featuresGold = [FeatureLikes, FeatureChatsGold, FeatureFilters, FeatureReadReceipts, AndMore] | |
| const featuresExecutive = [FeatureLikes, FeatureChatsExecutive, FeatureFilters, FeatureReadReceipts, FeatureUnlimitedLikes, AndMore] | |
| const SubscriptionCard = ({ isExpanded, animate = true }: { isExpanded?: boolean; animate?: boolean }) => { | |
| const { userProfile } = useAppData() | |
| const { entitlements } = useInAppPurchaseContext() | |
| const rotationValue = useRef(new Animated.Value(animate ? 0 : 1)).current | |
| const expirationDate = useMemo(() => { | |
| const firstEntitlement = Object.values(entitlements?.active || {})[0] | |
| if (!firstEntitlement) return format(addDays(new Date(), 7), 'dd/MM/yyyy') | |
| return format(new Date(firstEntitlement.expirationDateMillis || 0), 'dd/MM/yyyy') | |
| }, [entitlements?.active]) | |
| useEffect(() => { | |
| if (animate) { | |
| Animated.timing(rotationValue, { | |
| toValue: 1, | |
| duration: 500, | |
| useNativeDriver: true, | |
| }).start() | |
| } | |
| }, [rotationValue]) | |
| const subscriptionName = useMemo(() => { | |
| if (userProfile.subscription === Subscription.Executive) return 'Executive Plan' | |
| if (userProfile.subscription === Subscription.Gold) return 'Gold Plan' | |
| return 'Premium Plan' | |
| }, [userProfile?.subscription]) | |
| if (!userProfile?.subscriptionActive) return null | |
| const colors = userProfile.subscription === Subscription.Executive ? [Colors.grey.c900, Colors.grey.c700] : [Colors.primary.c500, Colors.primary.c300] | |
| const features = userProfile.subscription === Subscription.Executive ? featuresExecutive : featuresGold | |
| const rotation = rotationValue.interpolate({ | |
| inputRange: [0, 1], | |
| outputRange: ['30deg', '0deg'], | |
| }) | |
| const opacity = rotationValue.interpolate({ | |
| inputRange: [0, 1], | |
| outputRange: [0.4, 1], | |
| }) | |
| const translateY = rotationValue.interpolate({ | |
| inputRange: [0, 1], | |
| outputRange: [-130, 0], | |
| }) | |
| return ( | |
| <> | |
| <Animated.View | |
| style={[ | |
| styles.animatedContainer, | |
| { | |
| transform: [{ perspective: 1000 }, { rotateX: rotation }, { translateY }], | |
| opacity, | |
| }, | |
| ]} | |
| > | |
| <LinearGradient colors={colors as [string, string]} style={styles.subscriptionCard} start={{ x: 0.1, y: 0.7 }} end={{ x: 1, y: 0.2 }}> | |
| <View style={styles.cardHeader}> | |
| <HeaderLogoWhite size={36} /> | |
| <Text style={styles.planName}>{subscriptionName}</Text> | |
| </View> | |
| <View style={styles.cardBody}> | |
| <Text style={styles.expirationTitle}>EXP</Text> | |
| <Text style={styles.expirationText}>{expirationDate}</Text> | |
| </View> | |
| <View style={styles.cardFooter}> | |
| <Text style={styles.userName}>{userProfile.firstName?.toUpperCase()}</Text> | |
| <View style={styles.expirationContainer}> | |
| <MaterialIcons name={Platform.OS === 'ios' ? 'apple' : 'android'} size={32} color={Colors.grey.c0} /> | |
| </View> | |
| </View> | |
| </LinearGradient> | |
| </Animated.View> | |
| <Accordion | |
| isExpanded={isExpanded} | |
| button={(isExpanded: boolean, chevronRotation: any) => ( | |
| <View style={styles.viewFeaturesButton}> | |
| <Text style={styles.viewFeaturesText}>View benefits</Text> | |
| <Animated.View style={{ transform: [{ rotate: chevronRotation }] }}> | |
| <Ionicons name="chevron-down" color={Colors.grey.c600} size={20} /> | |
| </Animated.View> | |
| </View> | |
| )} | |
| content={ | |
| <ZoomInView style={styles.featuresWrapper}> | |
| {features.map((feature, index) => ( | |
| <View key={index} style={styles.featureRow}> | |
| <View style={styles.featureIconWrapper}> | |
| <Ionicons name={feature.icon as any} color={Colors.grey.c900} size={24} /> | |
| </View> | |
| <View style={styles.featureTextWrapper}> | |
| <Text style={styles.featureTitleText}>{feature.title}</Text> | |
| <Text style={styles.featureDescriptionText}>{feature.description}</Text> | |
| </View> | |
| </View> | |
| ))} | |
| </ZoomInView> | |
| } | |
| style={{ width: '100%', marginBottom: 16 }} | |
| /> | |
| </> | |
| ) | |
| } | |
| const styles = StyleSheet.create({ | |
| animatedContainer: { | |
| marginTop: 24, | |
| marginBottom: 12, | |
| position: 'relative', | |
| zIndex: 1000, | |
| width: '100%', | |
| }, | |
| subscriptionCard: { | |
| borderRadius: 12, | |
| padding: 20, | |
| minHeight: 210, | |
| justifyContent: 'space-between', | |
| }, | |
| cardHeader: { | |
| flexDirection: 'row', | |
| justifyContent: 'space-between', | |
| alignItems: 'center', | |
| }, | |
| planName: { | |
| color: Colors.grey.c0, | |
| fontSize: 16, | |
| fontWeight: '400', | |
| }, | |
| cardBody: { | |
| justifyContent: 'space-between', | |
| alignItems: 'flex-end', | |
| paddingTop: 32, | |
| }, | |
| cardFooter: { | |
| flexDirection: 'row', | |
| justifyContent: 'space-between', | |
| alignItems: 'flex-end', | |
| }, | |
| userName: { | |
| color: Colors.grey.c0, | |
| fontSize: 14, | |
| fontWeight: '600', | |
| }, | |
| expirationContainer: { | |
| flexDirection: 'row', | |
| alignItems: 'center', | |
| }, | |
| expirationTitle: { | |
| color: Colors.grey.c0, | |
| fontSize: 12, | |
| fontWeight: '600', | |
| }, | |
| expirationText: { | |
| color: Colors.grey.c0, | |
| fontSize: 18, | |
| fontWeight: '300', | |
| }, | |
| viewFeaturesButton: { | |
| flexDirection: 'row', | |
| justifyContent: 'flex-end', | |
| gap: 8, | |
| alignItems: 'center', | |
| borderRadius: 12, | |
| width: '100%', | |
| }, | |
| viewFeaturesText: { | |
| color: Colors.grey.c700, | |
| fontSize: 14, | |
| fontWeight: '600', | |
| }, | |
| featuresWrapper: { | |
| width: '100%', | |
| paddingBottom: 20, | |
| marginTop: 12, | |
| marginBottom: 24, | |
| borderRadius: 12, | |
| overflow: 'hidden', | |
| backgroundColor: Colors.grey.c100, | |
| paddingTop: 8, | |
| paddingHorizontal: 16, | |
| }, | |
| featureRow: { | |
| flexDirection: 'row', | |
| alignItems: 'center', | |
| gap: 8, | |
| marginVertical: 12, | |
| }, | |
| featureIconWrapper: { | |
| padding: 4, | |
| borderRadius: 24, | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| }, | |
| featureTextWrapper: { | |
| flex: 1, | |
| }, | |
| featureTitleText: { | |
| color: Colors.grey.c900, | |
| fontSize: 14, | |
| fontWeight: '600', | |
| }, | |
| featureDescriptionText: { | |
| color: Colors.grey.c600, | |
| fontSize: 12, | |
| fontWeight: '400', | |
| }, | |
| }) | |
| export default SubscriptionCard |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment