fix: 付费订阅bug
This commit is contained in:
549
app/exchange.tsx
549
app/exchange.tsx
@@ -2,14 +2,18 @@ import Ionicons from '@expo/vector-icons/Ionicons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useBalance } from '@/hooks/use-balance';
|
||||
import { usePricing } from '@/hooks/use-pricing';
|
||||
|
||||
type PurchaseTab = 'subscription' | 'pack';
|
||||
|
||||
@@ -33,8 +37,6 @@ const screenPalette = {
|
||||
buttonText: '#101010',
|
||||
};
|
||||
|
||||
const CURRENT_POINTS = 60;
|
||||
|
||||
const SUBSCRIPTION_TAGLINE = 'No active subscription plans';
|
||||
|
||||
const TABS: { key: PurchaseTab; label: string }[] = [
|
||||
@@ -52,8 +54,29 @@ const POINT_BUNDLES: PointsBundle[] = [
|
||||
export default function PointsExchangeScreen() {
|
||||
const router = useRouter();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [activeTab, setActiveTab] = useState<PurchaseTab>('pack');
|
||||
const [activeTab, setActiveTab] = useState<PurchaseTab>('subscription');
|
||||
const [selectedBundleId, setSelectedBundleId] = useState<string | null>(null);
|
||||
const [selectedSubscriptionIndex, setSelectedSubscriptionIndex] = useState<number | null>(null);
|
||||
const [customAmount, setCustomAmount] = useState<string>('500');
|
||||
|
||||
const { balance, isLoading: isBalanceLoading, refresh } = useBalance();
|
||||
const {
|
||||
stripePricingData,
|
||||
isStripePricingLoading,
|
||||
stripePricingError,
|
||||
creditPlans,
|
||||
hasActiveSubscription,
|
||||
hasCanceledButActiveSubscription,
|
||||
currentSubscriptionCredits,
|
||||
activeAuthSubscription,
|
||||
handleSubscriptionAction,
|
||||
formatCredits,
|
||||
createSubscriptionPending,
|
||||
upgradeSubscriptionPending,
|
||||
restoreSubscriptionPending,
|
||||
rechargeToken,
|
||||
rechargeTokenPending,
|
||||
} = usePricing();
|
||||
|
||||
const visibleBundles = useMemo(
|
||||
() => (activeTab === 'pack' ? POINT_BUNDLES : []),
|
||||
@@ -79,19 +102,73 @@ export default function PointsExchangeScreen() {
|
||||
setSelectedBundleId(bundleId);
|
||||
}, []);
|
||||
|
||||
const handleSubscriptionSelect = useCallback((index: number) => {
|
||||
setSelectedSubscriptionIndex(index);
|
||||
}, []);
|
||||
|
||||
const handlePurchase = useCallback(() => {
|
||||
const bundle = POINT_BUNDLES.find(item => item.id === selectedBundleId);
|
||||
if (activeTab === 'subscription') {
|
||||
// 订阅购买
|
||||
if (selectedSubscriptionIndex !== null) {
|
||||
handleSubscriptionAction(selectedSubscriptionIndex);
|
||||
} else {
|
||||
Alert.alert('Select a plan', 'Please select a subscription plan first.');
|
||||
}
|
||||
} else {
|
||||
// 积分包购买
|
||||
if (selectedBundleId) {
|
||||
const bundle = POINT_BUNDLES.find(item => item.id === selectedBundleId);
|
||||
|
||||
if (!bundle) {
|
||||
Alert.alert('Select a bundle', 'Please pick the bundle you wish to purchase.');
|
||||
return;
|
||||
if (!bundle) {
|
||||
Alert.alert('Select a bundle', 'Please pick the bundle you wish to purchase.');
|
||||
return;
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'Confirm purchase',
|
||||
`You are purchasing ${bundle.points} points for ¥ ${bundle.price}.`,
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Confirm',
|
||||
onPress: () => {
|
||||
// TODO: 调用充值API
|
||||
rechargeToken(bundle.points);
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// 自定义数量
|
||||
const amount = parseInt(customAmount, 10);
|
||||
if (isNaN(amount) || amount < 500) {
|
||||
Alert.alert('Invalid Amount', 'Please enter a valid amount (minimum 500 points).');
|
||||
return;
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'Confirm purchase',
|
||||
`You are purchasing ${amount} points.`,
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Confirm',
|
||||
onPress: () => {
|
||||
rechargeToken(amount);
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'Confirm purchase',
|
||||
`You are purchasing ${bundle.points} points for ¥ ${bundle.price}.`,
|
||||
);
|
||||
}, [selectedBundleId]);
|
||||
}, [
|
||||
activeTab,
|
||||
selectedBundleId,
|
||||
selectedSubscriptionIndex,
|
||||
customAmount,
|
||||
handleSubscriptionAction,
|
||||
rechargeToken,
|
||||
]);
|
||||
|
||||
return (
|
||||
<View style={[styles.screen, { paddingTop: insets.top + 12 }]}>
|
||||
@@ -124,83 +201,216 @@ export default function PointsExchangeScreen() {
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={styles.balanceCluster}>
|
||||
<View style={styles.energyOrb}>
|
||||
<Ionicons name="flash" size={22} color={screenPalette.accent} />
|
||||
</View>
|
||||
<Text style={styles.balanceValue}>{CURRENT_POINTS}</Text>
|
||||
<Text style={styles.balanceSubtitle}>{SUBSCRIPTION_TAGLINE}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.tabBar}>
|
||||
{TABS.map(tab => {
|
||||
const isActive = tab.key === activeTab;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={tab.key}
|
||||
style={styles.tabButton}
|
||||
onPress={() => changeTab(tab.key)}
|
||||
accessibilityRole="tab"
|
||||
accessibilityState={{ selected: isActive }}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text style={[styles.tabLabel, isActive && styles.tabLabelActive]}>
|
||||
{tab.label}
|
||||
</Text>
|
||||
<View
|
||||
style={[styles.tabIndicator, isActive && styles.tabIndicatorActive]}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
<View style={styles.tabDivider} />
|
||||
|
||||
{activeTab === 'pack' ? (
|
||||
<View style={styles.packGrid}>
|
||||
{visibleBundles.map(bundle => {
|
||||
const isSelected = bundle.id === selectedBundleId;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={bundle.id}
|
||||
style={[
|
||||
styles.packCard,
|
||||
isSelected && styles.packCardSelected,
|
||||
]}
|
||||
onPress={() => handleBundleSelect(bundle.id)}
|
||||
accessibilityRole="button"
|
||||
accessibilityState={{ selected: isSelected }}
|
||||
activeOpacity={0.88}
|
||||
>
|
||||
<View style={styles.packHeader}>
|
||||
<Ionicons name="flash" size={18} color={screenPalette.accent} />
|
||||
<Text style={styles.packPoints}>{bundle.points}</Text>
|
||||
</View>
|
||||
<Text style={styles.packPrice}>¥ {bundle.price}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
{isBalanceLoading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={screenPalette.accent} />
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.subscriptionEmpty}>
|
||||
<Text style={styles.emptyTitle}>Subscription</Text>
|
||||
<Text style={styles.emptySubtitle}>
|
||||
No subscription tiers are available at the moment.
|
||||
</Text>
|
||||
</View>
|
||||
<>
|
||||
<View style={styles.balanceCluster}>
|
||||
<View style={styles.energyOrb}>
|
||||
<Ionicons name="flash" size={22} color={screenPalette.accent} />
|
||||
</View>
|
||||
<Text style={styles.balanceValue}>{balance.remainingTokenBalance}</Text>
|
||||
<Text style={styles.balanceSubtitle}>{SUBSCRIPTION_TAGLINE}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.tabBar}>
|
||||
{TABS.map(tab => {
|
||||
const isActive = tab.key === activeTab;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={tab.key}
|
||||
style={styles.tabButton}
|
||||
onPress={() => changeTab(tab.key)}
|
||||
accessibilityRole="tab"
|
||||
accessibilityState={{ selected: isActive }}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text style={[styles.tabLabel, isActive && styles.tabLabelActive]}>
|
||||
{tab.label}
|
||||
</Text>
|
||||
<View
|
||||
style={[styles.tabIndicator, isActive && styles.tabIndicatorActive]}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
<View style={styles.tabDivider} />
|
||||
|
||||
{activeTab === 'subscription' ? (
|
||||
<>
|
||||
{/* 订阅套餐列表 */}
|
||||
{isStripePricingLoading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={screenPalette.accent} />
|
||||
<Text style={styles.loadingText}>Loading plans...</Text>
|
||||
</View>
|
||||
) : stripePricingError ? (
|
||||
<View style={styles.subscriptionEmpty}>
|
||||
<Text style={styles.emptyTitle}>Error</Text>
|
||||
<Text style={styles.emptySubtitle}>{stripePricingError}</Text>
|
||||
</View>
|
||||
) : stripePricingData?.pricing_table_items && stripePricingData.pricing_table_items.length > 0 ? (
|
||||
<View style={styles.subscriptionGrid}>
|
||||
{stripePricingData.pricing_table_items
|
||||
.filter(item => item.recurring?.interval === 'month')
|
||||
.map((item, index) => {
|
||||
const plan = creditPlans[index];
|
||||
const isSelected = selectedSubscriptionIndex === index;
|
||||
const priceInDollars = parseInt(item.amount) / 100;
|
||||
const grantToken = item.metadata?.grant_token || '0';
|
||||
const isCurrentSubscription =
|
||||
hasActiveSubscription &&
|
||||
activeAuthSubscription?.plan?.toLowerCase() === item.name?.toLowerCase();
|
||||
const isCanceledSubscription =
|
||||
hasCanceledButActiveSubscription &&
|
||||
activeAuthSubscription?.plan?.toLowerCase() === item.name?.toLowerCase();
|
||||
const isHighlight = item.is_highlight || item.highlight_text === 'most_popular';
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={item.price_id}
|
||||
style={[
|
||||
styles.subscriptionCard,
|
||||
isSelected && styles.subscriptionCardSelected,
|
||||
isHighlight && styles.subscriptionCardHighlight,
|
||||
]}
|
||||
onPress={() => handleSubscriptionSelect(index)}
|
||||
accessibilityRole="button"
|
||||
accessibilityState={{ selected: isSelected }}
|
||||
activeOpacity={0.88}
|
||||
>
|
||||
{/* 套餐头部 */}
|
||||
<View style={styles.subscriptionHeader}>
|
||||
<Text style={styles.subscriptionName}>{item.name}</Text>
|
||||
{isHighlight && (
|
||||
<View style={styles.popularBadge}>
|
||||
<Text style={styles.popularBadgeText}>Popular</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 价格 */}
|
||||
<View style={styles.subscriptionPriceContainer}>
|
||||
<Text style={styles.subscriptionPrice}>${priceInDollars}</Text>
|
||||
<Text style={styles.subscriptionInterval}>/mo</Text>
|
||||
</View>
|
||||
|
||||
{/* 积分信息 */}
|
||||
<View style={styles.subscriptionCreditsBox}>
|
||||
<View style={styles.creditsRow}>
|
||||
<Ionicons name="flash" size={20} color={screenPalette.accent} />
|
||||
<Text style={styles.subscriptionCreditsValue}>
|
||||
{formatCredits(Number(grantToken))}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.creditsLabel}>credits per month</Text>
|
||||
</View>
|
||||
|
||||
{/* 状态徽章 */}
|
||||
{isCurrentSubscription && (
|
||||
<View style={styles.currentBadge}>
|
||||
<Ionicons name="checkmark-circle" size={14} color="#22c55e" />
|
||||
<Text style={styles.currentBadgeText}>Current Plan</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{isCanceledSubscription && (
|
||||
<View style={styles.canceledBadge}>
|
||||
<Ionicons name="warning" size={14} color="#fb923c" />
|
||||
<Text style={styles.canceledBadgeText}>Canceled</Text>
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.subscriptionEmpty}>
|
||||
<Text style={styles.emptyTitle}>No Plans Available</Text>
|
||||
<Text style={styles.emptySubtitle}>
|
||||
No subscription tiers are available at the moment.
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* 预设套餐 */}
|
||||
<View style={styles.packGrid}>
|
||||
{visibleBundles.map(bundle => {
|
||||
const isSelected = bundle.id === selectedBundleId;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={bundle.id}
|
||||
style={[
|
||||
styles.packCard,
|
||||
isSelected && styles.packCardSelected,
|
||||
]}
|
||||
onPress={() => handleBundleSelect(bundle.id)}
|
||||
accessibilityRole="button"
|
||||
accessibilityState={{ selected: isSelected }}
|
||||
activeOpacity={0.88}
|
||||
>
|
||||
<View style={styles.packHeader}>
|
||||
<Ionicons name="flash" size={18} color={screenPalette.accent} />
|
||||
<Text style={styles.packPoints}>{bundle.points}</Text>
|
||||
</View>
|
||||
<Text style={styles.packPrice}>¥ {bundle.price}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{/* 自定义金额输入 */}
|
||||
<View style={styles.customAmountContainer}>
|
||||
<Text style={styles.customAmountLabel}>Or enter custom amount:</Text>
|
||||
<TextInput
|
||||
style={styles.customAmountInput}
|
||||
value={customAmount}
|
||||
onChangeText={setCustomAmount}
|
||||
keyboardType="numeric"
|
||||
placeholder="Min. 500 points"
|
||||
placeholderTextColor={screenPalette.mutedText}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
<View style={[styles.bottomBar, { paddingBottom: Math.max(insets.bottom, 16) }]}>
|
||||
<TouchableOpacity
|
||||
style={styles.primaryButton}
|
||||
style={[
|
||||
styles.primaryButton,
|
||||
(createSubscriptionPending || upgradeSubscriptionPending || restoreSubscriptionPending || rechargeTokenPending) &&
|
||||
styles.primaryButtonDisabled,
|
||||
]}
|
||||
onPress={handlePurchase}
|
||||
disabled={
|
||||
createSubscriptionPending ||
|
||||
upgradeSubscriptionPending ||
|
||||
restoreSubscriptionPending ||
|
||||
rechargeTokenPending
|
||||
}
|
||||
activeOpacity={0.85}
|
||||
>
|
||||
<Text style={styles.primaryButtonLabel}>Purchase points</Text>
|
||||
{createSubscriptionPending ||
|
||||
upgradeSubscriptionPending ||
|
||||
restoreSubscriptionPending ||
|
||||
rechargeTokenPending ? (
|
||||
<ActivityIndicator color={screenPalette.buttonText} />
|
||||
) : (
|
||||
<Text style={styles.primaryButtonLabel}>
|
||||
{activeTab === 'subscription' ? 'Subscribe' : 'Purchase points'}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -368,6 +578,182 @@ const styles = StyleSheet.create({
|
||||
textAlign: 'center',
|
||||
lineHeight: 20,
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: 300,
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 12,
|
||||
fontSize: 14,
|
||||
color: screenPalette.secondaryText,
|
||||
},
|
||||
subscriptionGrid: {
|
||||
marginTop: 28,
|
||||
gap: 16,
|
||||
},
|
||||
subscriptionCard: {
|
||||
backgroundColor: screenPalette.surfaceRaised,
|
||||
borderRadius: 20,
|
||||
padding: 24,
|
||||
borderWidth: 2,
|
||||
borderColor: 'transparent',
|
||||
position: 'relative',
|
||||
},
|
||||
subscriptionCardSelected: {
|
||||
borderColor: screenPalette.accent,
|
||||
backgroundColor: 'rgba(254, 184, 64, 0.05)',
|
||||
},
|
||||
subscriptionCardHighlight: {
|
||||
borderColor: screenPalette.button,
|
||||
},
|
||||
subscriptionHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
subscriptionName: {
|
||||
fontSize: 24,
|
||||
fontWeight: '700',
|
||||
color: screenPalette.primaryText,
|
||||
},
|
||||
subscriptionPriceContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'baseline',
|
||||
marginBottom: 20,
|
||||
},
|
||||
subscriptionPrice: {
|
||||
fontSize: 40,
|
||||
fontWeight: '700',
|
||||
color: screenPalette.accent,
|
||||
fontVariant: ['tabular-nums'],
|
||||
},
|
||||
subscriptionInterval: {
|
||||
fontSize: 16,
|
||||
color: screenPalette.secondaryText,
|
||||
marginLeft: 4,
|
||||
},
|
||||
subscriptionCreditsBox: {
|
||||
backgroundColor: 'rgba(254, 184, 64, 0.1)',
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
creditsRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
marginBottom: 4,
|
||||
},
|
||||
subscriptionCreditsValue: {
|
||||
fontSize: 28,
|
||||
fontWeight: '700',
|
||||
color: screenPalette.accent,
|
||||
fontVariant: ['tabular-nums'],
|
||||
},
|
||||
creditsLabel: {
|
||||
fontSize: 13,
|
||||
color: screenPalette.secondaryText,
|
||||
fontWeight: '500',
|
||||
},
|
||||
subscriptionCreditsContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
marginBottom: 16,
|
||||
},
|
||||
subscriptionCredits: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: screenPalette.primaryText,
|
||||
},
|
||||
subscriptionFeatures: {
|
||||
gap: 8,
|
||||
},
|
||||
featureItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
gap: 8,
|
||||
},
|
||||
featureBullet: {
|
||||
fontSize: 16,
|
||||
color: screenPalette.accent,
|
||||
lineHeight: 20,
|
||||
},
|
||||
featureText: {
|
||||
flex: 1,
|
||||
fontSize: 13,
|
||||
color: screenPalette.secondaryText,
|
||||
lineHeight: 20,
|
||||
},
|
||||
popularBadge: {
|
||||
backgroundColor: screenPalette.button,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 12,
|
||||
},
|
||||
popularBadgeText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
color: screenPalette.buttonText,
|
||||
},
|
||||
currentBadge: {
|
||||
marginTop: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
backgroundColor: 'rgba(34, 197, 94, 0.15)',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 10,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
currentBadgeText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
color: '#22c55e',
|
||||
},
|
||||
canceledBadge: {
|
||||
marginTop: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
backgroundColor: 'rgba(251, 146, 60, 0.15)',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 10,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
canceledBadgeText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
color: '#fb923c',
|
||||
},
|
||||
customAmountContainer: {
|
||||
marginTop: 24,
|
||||
backgroundColor: screenPalette.surfaceRaised,
|
||||
borderRadius: 22,
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
customAmountLabel: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: screenPalette.primaryText,
|
||||
marginBottom: 12,
|
||||
},
|
||||
customAmountInput: {
|
||||
backgroundColor: screenPalette.surface,
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 14,
|
||||
fontSize: 16,
|
||||
color: screenPalette.primaryText,
|
||||
borderWidth: 1,
|
||||
borderColor: screenPalette.divider,
|
||||
},
|
||||
bottomBar: {
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
@@ -378,6 +764,9 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
primaryButtonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
primaryButtonLabel: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
|
||||
Reference in New Issue
Block a user