@bettoredge/calcutta 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/components/AuctionCompleteOverlay.tsx +306 -0
- package/src/components/AuctionCountdownOverlay.tsx +178 -0
- package/src/components/AuctionInfoChips.tsx +105 -0
- package/src/components/AuctionPausedOverlay.tsx +154 -0
- package/src/components/CalcuttaActionCard.tsx +291 -0
- package/src/components/CalcuttaAuction.tsx +682 -281
- package/src/components/CalcuttaAuctionItem.tsx +195 -106
- package/src/components/CalcuttaBidInput.tsx +130 -139
- package/src/components/CalcuttaCountdown.tsx +183 -0
- package/src/components/CalcuttaDetail.tsx +289 -143
- package/src/components/CalcuttaEscrow.tsx +49 -13
- package/src/components/CalcuttaLeaderboard.tsx +2 -2
- package/src/components/EscrowWidget.tsx +176 -0
- package/src/components/ItemSoldCelebration.tsx +288 -0
- package/src/components/SweepstakesReveal.tsx +1 -1
- package/src/components/sealed/SealedBidAuction.tsx +7 -9
- package/src/components/sealed/SealedBidHeader.tsx +3 -3
- package/src/components/sealed/SealedBidItemCard.tsx +9 -9
- package/src/components/sealed/SealedBidItemsTab.tsx +2 -1
- package/src/components/sealed/SealedBidMyBidsTab.tsx +3 -3
- package/src/components/sealed/SealedBidPlayersTab.tsx +2 -2
- package/src/components/sealed/SealedBidStatusBar.tsx +1 -1
- package/src/components/sealed/SealedBidTabBar.tsx +2 -0
- package/src/hooks/useCalcuttaAuction.ts +16 -2
- package/src/hooks/useCalcuttaEscrow.ts +5 -1
- package/src/hooks/useCalcuttaSocket.ts +80 -82
- package/src/index.ts +18 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { StyleSheet, Animated, TouchableOpacity } from 'react-native';
|
|
3
|
+
import { View, Text, useTheme } from '@bettoredge/styles';
|
|
4
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
5
|
+
|
|
6
|
+
export interface AuctionPausedOverlayProps {
|
|
7
|
+
visible: boolean;
|
|
8
|
+
isAdmin?: boolean;
|
|
9
|
+
onResume?: () => void;
|
|
10
|
+
resuming?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const AuctionPausedOverlay: React.FC<AuctionPausedOverlayProps> = ({
|
|
14
|
+
visible,
|
|
15
|
+
isAdmin,
|
|
16
|
+
onResume,
|
|
17
|
+
resuming,
|
|
18
|
+
}) => {
|
|
19
|
+
const { theme } = useTheme();
|
|
20
|
+
const opacityAnim = useRef(new Animated.Value(0)).current;
|
|
21
|
+
const pulseAnim = useRef(new Animated.Value(1)).current;
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (visible) {
|
|
25
|
+
Animated.timing(opacityAnim, { toValue: 1, duration: 300, useNativeDriver: true }).start();
|
|
26
|
+
const loop = Animated.loop(
|
|
27
|
+
Animated.sequence([
|
|
28
|
+
Animated.timing(pulseAnim, { toValue: 0.5, duration: 1200, useNativeDriver: true }),
|
|
29
|
+
Animated.timing(pulseAnim, { toValue: 1, duration: 1200, useNativeDriver: true }),
|
|
30
|
+
])
|
|
31
|
+
);
|
|
32
|
+
loop.start();
|
|
33
|
+
return () => loop.stop();
|
|
34
|
+
} else {
|
|
35
|
+
Animated.timing(opacityAnim, { toValue: 0, duration: 200, useNativeDriver: true }).start();
|
|
36
|
+
}
|
|
37
|
+
}, [visible]);
|
|
38
|
+
|
|
39
|
+
if (!visible) return null;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Animated.View style={[styles.overlay, { opacity: opacityAnim }]}>
|
|
43
|
+
<View variant="transparent" style={styles.backdrop} />
|
|
44
|
+
<View variant="transparent" style={styles.content}>
|
|
45
|
+
<Animated.View style={{ opacity: pulseAnim }}>
|
|
46
|
+
<View variant="transparent" style={styles.iconCircle}>
|
|
47
|
+
<Ionicons name="pause" size={48} color="#FFFFFF" />
|
|
48
|
+
</View>
|
|
49
|
+
</Animated.View>
|
|
50
|
+
|
|
51
|
+
<Text style={styles.title}>Auction Paused</Text>
|
|
52
|
+
<Text style={styles.subtitle}>
|
|
53
|
+
{isAdmin
|
|
54
|
+
? 'The auction is paused. Resume when ready.'
|
|
55
|
+
: 'The host has paused the auction.\nBidding will resume shortly.'}
|
|
56
|
+
</Text>
|
|
57
|
+
|
|
58
|
+
{isAdmin && onResume && (
|
|
59
|
+
<TouchableOpacity
|
|
60
|
+
style={[styles.resumeButton, resuming && { opacity: 0.6 }]}
|
|
61
|
+
onPress={onResume}
|
|
62
|
+
disabled={resuming}
|
|
63
|
+
activeOpacity={0.7}
|
|
64
|
+
>
|
|
65
|
+
<Ionicons name="play" size={20} color="#FFFFFF" />
|
|
66
|
+
<Text style={styles.resumeText}>
|
|
67
|
+
{resuming ? 'Resuming...' : 'Resume Auction'}
|
|
68
|
+
</Text>
|
|
69
|
+
</TouchableOpacity>
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
{!isAdmin && (
|
|
73
|
+
<View variant="transparent" style={styles.waitingRow}>
|
|
74
|
+
<Animated.View style={{ opacity: pulseAnim }}>
|
|
75
|
+
<View variant="transparent" style={styles.waitingDot} />
|
|
76
|
+
</Animated.View>
|
|
77
|
+
<Text style={styles.waitingText}>Waiting for host...</Text>
|
|
78
|
+
</View>
|
|
79
|
+
)}
|
|
80
|
+
</View>
|
|
81
|
+
</Animated.View>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const styles = StyleSheet.create({
|
|
86
|
+
overlay: {
|
|
87
|
+
...StyleSheet.absoluteFillObject,
|
|
88
|
+
zIndex: 999,
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
justifyContent: 'center',
|
|
91
|
+
},
|
|
92
|
+
backdrop: {
|
|
93
|
+
...StyleSheet.absoluteFillObject,
|
|
94
|
+
backgroundColor: 'rgba(0,0,0,0.80)',
|
|
95
|
+
},
|
|
96
|
+
content: {
|
|
97
|
+
alignItems: 'center',
|
|
98
|
+
padding: 40,
|
|
99
|
+
},
|
|
100
|
+
iconCircle: {
|
|
101
|
+
width: 100,
|
|
102
|
+
height: 100,
|
|
103
|
+
borderRadius: 50,
|
|
104
|
+
backgroundColor: '#EF4444',
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
justifyContent: 'center',
|
|
107
|
+
marginBottom: 24,
|
|
108
|
+
},
|
|
109
|
+
title: {
|
|
110
|
+
color: '#FFFFFF',
|
|
111
|
+
fontSize: 28,
|
|
112
|
+
lineHeight: 36,
|
|
113
|
+
fontWeight: '800',
|
|
114
|
+
marginBottom: 8,
|
|
115
|
+
},
|
|
116
|
+
subtitle: {
|
|
117
|
+
color: 'rgba(255,255,255,0.7)',
|
|
118
|
+
fontSize: 16,
|
|
119
|
+
textAlign: 'center',
|
|
120
|
+
lineHeight: 22,
|
|
121
|
+
marginBottom: 32,
|
|
122
|
+
},
|
|
123
|
+
resumeButton: {
|
|
124
|
+
flexDirection: 'row',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
backgroundColor: '#10B981',
|
|
127
|
+
paddingHorizontal: 28,
|
|
128
|
+
paddingVertical: 14,
|
|
129
|
+
borderRadius: 12,
|
|
130
|
+
},
|
|
131
|
+
resumeText: {
|
|
132
|
+
color: '#FFFFFF',
|
|
133
|
+
fontSize: 17,
|
|
134
|
+
lineHeight: 24,
|
|
135
|
+
fontWeight: '700',
|
|
136
|
+
marginLeft: 10,
|
|
137
|
+
},
|
|
138
|
+
waitingRow: {
|
|
139
|
+
flexDirection: 'row',
|
|
140
|
+
alignItems: 'center',
|
|
141
|
+
},
|
|
142
|
+
waitingDot: {
|
|
143
|
+
width: 8,
|
|
144
|
+
height: 8,
|
|
145
|
+
borderRadius: 4,
|
|
146
|
+
backgroundColor: '#F59E0B',
|
|
147
|
+
marginRight: 8,
|
|
148
|
+
},
|
|
149
|
+
waitingText: {
|
|
150
|
+
color: 'rgba(255,255,255,0.5)',
|
|
151
|
+
fontSize: 14,
|
|
152
|
+
lineHeight: 19,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native';
|
|
3
|
+
import { View, Text, useTheme } from '@bettoredge/styles';
|
|
4
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
5
|
+
import type { CalcuttaCompetitionProps } from '@bettoredge/types';
|
|
6
|
+
import type { CalcuttaLifecycleState } from '../helpers/lifecycleState';
|
|
7
|
+
import { formatCurrency } from '../helpers/formatting';
|
|
8
|
+
import { CalcuttaCountdown } from './CalcuttaCountdown';
|
|
9
|
+
|
|
10
|
+
export interface CalcuttaActionCardProps {
|
|
11
|
+
competition: CalcuttaCompetitionProps;
|
|
12
|
+
lifecycleState: CalcuttaLifecycleState;
|
|
13
|
+
hasJoined: boolean;
|
|
14
|
+
escrowBalance?: number;
|
|
15
|
+
activeBidCount?: number;
|
|
16
|
+
itemsWon?: number;
|
|
17
|
+
isAdmin?: boolean;
|
|
18
|
+
onJoin?: () => void;
|
|
19
|
+
onDepositEscrow?: () => void;
|
|
20
|
+
onManage?: () => void;
|
|
21
|
+
onStartAuction?: () => void;
|
|
22
|
+
joining?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CardConfig {
|
|
26
|
+
icon: string;
|
|
27
|
+
title: string;
|
|
28
|
+
description: string;
|
|
29
|
+
accentColor: string;
|
|
30
|
+
ctaLabel?: string;
|
|
31
|
+
ctaColor?: string;
|
|
32
|
+
ctaAction?: () => void;
|
|
33
|
+
showCountdown?: 'start' | 'end';
|
|
34
|
+
loading?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const CalcuttaActionCard: React.FC<CalcuttaActionCardProps> = ({
|
|
38
|
+
competition,
|
|
39
|
+
lifecycleState,
|
|
40
|
+
hasJoined,
|
|
41
|
+
escrowBalance = 0,
|
|
42
|
+
activeBidCount = 0,
|
|
43
|
+
itemsWon = 0,
|
|
44
|
+
isAdmin,
|
|
45
|
+
onJoin,
|
|
46
|
+
onDepositEscrow,
|
|
47
|
+
onManage,
|
|
48
|
+
onStartAuction,
|
|
49
|
+
joining,
|
|
50
|
+
}) => {
|
|
51
|
+
const { theme } = useTheme();
|
|
52
|
+
const isSweepstakes = competition.auction_type === 'sweepstakes';
|
|
53
|
+
const entryFee = Number(competition.entry_fee) || 0;
|
|
54
|
+
const isFree = entryFee === 0;
|
|
55
|
+
|
|
56
|
+
const getConfig = (): CardConfig => {
|
|
57
|
+
// Admin: can start auction
|
|
58
|
+
if (isAdmin && lifecycleState === 'scheduled' && onStartAuction) {
|
|
59
|
+
const participantCount = competition.participants?.length ?? 0;
|
|
60
|
+
return {
|
|
61
|
+
icon: 'play-circle-outline',
|
|
62
|
+
title: 'Ready to start?',
|
|
63
|
+
description: `${participantCount} player${participantCount !== 1 ? 's' : ''} have joined. Start the ${isSweepstakes ? 'competition' : 'auction'} when ready.`,
|
|
64
|
+
accentColor: theme.colors.primary.default,
|
|
65
|
+
ctaLabel: isSweepstakes ? 'Start Competition' : 'Start Auction',
|
|
66
|
+
ctaColor: theme.colors.primary.default,
|
|
67
|
+
ctaAction: onStartAuction,
|
|
68
|
+
showCountdown: 'start',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Not joined — prompt to join
|
|
73
|
+
if (!hasJoined && (lifecycleState === 'scheduled' || (lifecycleState === 'auctioning' && !isSweepstakes))) {
|
|
74
|
+
return {
|
|
75
|
+
icon: 'enter-outline',
|
|
76
|
+
title: isSweepstakes ? 'Join for a random team!' : 'Join this auction',
|
|
77
|
+
description: isFree
|
|
78
|
+
? 'Free to enter'
|
|
79
|
+
: `Entry fee: ${formatCurrency(entryFee, competition.market_type)}`,
|
|
80
|
+
accentColor: '#10B981',
|
|
81
|
+
ctaLabel: isFree ? 'Join Free' : `Join — ${formatCurrency(entryFee, competition.market_type)}`,
|
|
82
|
+
ctaColor: '#10B981',
|
|
83
|
+
ctaAction: onJoin,
|
|
84
|
+
showCountdown: 'start',
|
|
85
|
+
loading: joining,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Joined + scheduled
|
|
90
|
+
if (hasJoined && lifecycleState === 'scheduled') {
|
|
91
|
+
// Sweepstakes — just waiting
|
|
92
|
+
if (isSweepstakes) {
|
|
93
|
+
return {
|
|
94
|
+
icon: 'checkmark-circle',
|
|
95
|
+
title: "You're in!",
|
|
96
|
+
description: 'Your team will be assigned when the host starts the competition.',
|
|
97
|
+
accentColor: '#10B981',
|
|
98
|
+
showCountdown: 'start',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// No escrow — prompt to fund
|
|
102
|
+
if (escrowBalance <= 0) {
|
|
103
|
+
return {
|
|
104
|
+
icon: 'wallet-outline',
|
|
105
|
+
title: 'Fund your escrow',
|
|
106
|
+
description: 'You need funds in your escrow to place bids when the auction opens.',
|
|
107
|
+
accentColor: '#D97706',
|
|
108
|
+
ctaLabel: 'Add Funds',
|
|
109
|
+
ctaColor: '#D97706',
|
|
110
|
+
ctaAction: onDepositEscrow,
|
|
111
|
+
showCountdown: 'start',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// Has escrow — ready
|
|
115
|
+
return {
|
|
116
|
+
icon: 'checkmark-circle',
|
|
117
|
+
title: "You're ready!",
|
|
118
|
+
description: `Escrow: ${formatCurrency(escrowBalance, competition.market_type)} available`,
|
|
119
|
+
accentColor: '#10B981',
|
|
120
|
+
showCountdown: 'start',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Auction active
|
|
125
|
+
if (lifecycleState === 'auctioning' && hasJoined) {
|
|
126
|
+
if (activeBidCount > 0) {
|
|
127
|
+
return {
|
|
128
|
+
icon: 'trophy-outline',
|
|
129
|
+
title: `You have ${activeBidCount} active bid${activeBidCount !== 1 ? 's' : ''}`,
|
|
130
|
+
description: competition.auction_type === 'sealed_bid'
|
|
131
|
+
? 'Bids are sealed — results revealed when auction closes.'
|
|
132
|
+
: 'Keep watching for outbid notifications.',
|
|
133
|
+
accentColor: '#D97706',
|
|
134
|
+
showCountdown: competition.auction_type === 'sealed_bid' ? 'end' : undefined,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
icon: 'flash-outline',
|
|
139
|
+
title: 'Auction is live!',
|
|
140
|
+
description: 'Start placing your bids now.',
|
|
141
|
+
accentColor: '#D97706',
|
|
142
|
+
showCountdown: competition.auction_type === 'sealed_bid' ? 'end' : undefined,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Tournament in progress
|
|
147
|
+
if (lifecycleState === 'tournament') {
|
|
148
|
+
return {
|
|
149
|
+
icon: 'football-outline',
|
|
150
|
+
title: 'Tournament in progress',
|
|
151
|
+
description: itemsWon > 0
|
|
152
|
+
? `You own ${itemsWon} item${itemsWon !== 1 ? 's' : ''} — track results below.`
|
|
153
|
+
: 'Follow the action below.',
|
|
154
|
+
accentColor: '#8B5CF6',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Completed
|
|
159
|
+
if (lifecycleState === 'completed') {
|
|
160
|
+
return {
|
|
161
|
+
icon: 'ribbon-outline',
|
|
162
|
+
title: 'Competition complete',
|
|
163
|
+
description: itemsWon > 0
|
|
164
|
+
? `You owned ${itemsWon} item${itemsWon !== 1 ? 's' : ''}.`
|
|
165
|
+
: 'This competition has ended.',
|
|
166
|
+
accentColor: theme.colors.text.tertiary,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Admin manage fallback
|
|
171
|
+
if (isAdmin && onManage) {
|
|
172
|
+
return {
|
|
173
|
+
icon: 'settings-outline',
|
|
174
|
+
title: 'Manage Competition',
|
|
175
|
+
description: 'Configure settings, items, and payouts.',
|
|
176
|
+
accentColor: theme.colors.primary.default,
|
|
177
|
+
ctaLabel: 'Manage',
|
|
178
|
+
ctaColor: theme.colors.primary.default,
|
|
179
|
+
ctaAction: onManage,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Default
|
|
184
|
+
return {
|
|
185
|
+
icon: 'information-circle-outline',
|
|
186
|
+
title: competition.competition_name,
|
|
187
|
+
description: 'Competition details',
|
|
188
|
+
accentColor: theme.colors.text.tertiary,
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const config = getConfig();
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<View variant="transparent" style={[styles.card, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
196
|
+
{/* Left accent strip */}
|
|
197
|
+
<View variant="transparent" style={[styles.accent, { backgroundColor: config.accentColor }]} />
|
|
198
|
+
|
|
199
|
+
<View variant="transparent" style={styles.content}>
|
|
200
|
+
{/* Icon + Text */}
|
|
201
|
+
<View variant="transparent" style={styles.topRow}>
|
|
202
|
+
<View variant="transparent" style={[styles.iconCircle, { backgroundColor: config.accentColor + '18' }]}>
|
|
203
|
+
<Ionicons name={config.icon as any} size={22} color={config.accentColor} />
|
|
204
|
+
</View>
|
|
205
|
+
<View variant="transparent" style={styles.textBlock}>
|
|
206
|
+
<Text variant="body" bold>{config.title}</Text>
|
|
207
|
+
<Text variant="caption" color="secondary" style={{ marginTop: 2 }}>{config.description}</Text>
|
|
208
|
+
</View>
|
|
209
|
+
</View>
|
|
210
|
+
|
|
211
|
+
{/* Countdown */}
|
|
212
|
+
{config.showCountdown === 'start' && competition.scheduled_datetime && (
|
|
213
|
+
<View variant="transparent" style={styles.countdownRow}>
|
|
214
|
+
<CalcuttaCountdown
|
|
215
|
+
targetDate={competition.scheduled_datetime}
|
|
216
|
+
label="Auction starts"
|
|
217
|
+
size="compact"
|
|
218
|
+
/>
|
|
219
|
+
</View>
|
|
220
|
+
)}
|
|
221
|
+
{config.showCountdown === 'end' && competition.auction_end_datetime && (
|
|
222
|
+
<View variant="transparent" style={styles.countdownRow}>
|
|
223
|
+
<CalcuttaCountdown
|
|
224
|
+
targetDate={competition.auction_end_datetime}
|
|
225
|
+
label="Bidding closes"
|
|
226
|
+
size="compact"
|
|
227
|
+
/>
|
|
228
|
+
</View>
|
|
229
|
+
)}
|
|
230
|
+
|
|
231
|
+
{/* CTA Button */}
|
|
232
|
+
{config.ctaLabel && config.ctaAction && (
|
|
233
|
+
<TouchableOpacity
|
|
234
|
+
style={[styles.ctaButton, { backgroundColor: config.ctaColor || theme.colors.primary.default }]}
|
|
235
|
+
onPress={config.ctaAction}
|
|
236
|
+
activeOpacity={0.7}
|
|
237
|
+
disabled={config.loading}
|
|
238
|
+
>
|
|
239
|
+
{config.loading ? (
|
|
240
|
+
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
241
|
+
) : (
|
|
242
|
+
<Text variant="body" bold style={{ color: '#FFFFFF' }}>{config.ctaLabel}</Text>
|
|
243
|
+
)}
|
|
244
|
+
</TouchableOpacity>
|
|
245
|
+
)}
|
|
246
|
+
</View>
|
|
247
|
+
</View>
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const styles = StyleSheet.create({
|
|
252
|
+
card: {
|
|
253
|
+
borderRadius: 16,
|
|
254
|
+
borderWidth: 1,
|
|
255
|
+
flexDirection: 'row',
|
|
256
|
+
overflow: 'hidden',
|
|
257
|
+
},
|
|
258
|
+
accent: {
|
|
259
|
+
width: 4,
|
|
260
|
+
},
|
|
261
|
+
content: {
|
|
262
|
+
flex: 1,
|
|
263
|
+
padding: 16,
|
|
264
|
+
},
|
|
265
|
+
topRow: {
|
|
266
|
+
flexDirection: 'row',
|
|
267
|
+
alignItems: 'flex-start',
|
|
268
|
+
},
|
|
269
|
+
iconCircle: {
|
|
270
|
+
width: 42,
|
|
271
|
+
height: 42,
|
|
272
|
+
borderRadius: 21,
|
|
273
|
+
alignItems: 'center',
|
|
274
|
+
justifyContent: 'center',
|
|
275
|
+
},
|
|
276
|
+
textBlock: {
|
|
277
|
+
flex: 1,
|
|
278
|
+
marginLeft: 12,
|
|
279
|
+
justifyContent: 'center',
|
|
280
|
+
},
|
|
281
|
+
countdownRow: {
|
|
282
|
+
marginTop: 10,
|
|
283
|
+
},
|
|
284
|
+
ctaButton: {
|
|
285
|
+
marginTop: 14,
|
|
286
|
+
height: 44,
|
|
287
|
+
borderRadius: 10,
|
|
288
|
+
alignItems: 'center',
|
|
289
|
+
justifyContent: 'center',
|
|
290
|
+
},
|
|
291
|
+
});
|