Skip to content

Instantly share code, notes, and snippets.

@WisaniShilumani
Created December 8, 2025 19:46
Show Gist options
  • Select an option

  • Save WisaniShilumani/e676506de705551547712971b3ac3c0d to your computer and use it in GitHub Desktop.

Select an option

Save WisaniShilumani/e676506de705551547712971b3ac3c0d to your computer and use it in GitHub Desktop.
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