@bettoredge/calcutta 0.4.1 → 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettoredge/calcutta",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Calcutta auction competition components for BettorEdge applications",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -15,6 +15,7 @@ export interface CalcuttaActionCardProps {
15
15
  activeBidCount?: number;
16
16
  itemsWon?: number;
17
17
  isAdmin?: boolean;
18
+ participantCount?: number;
18
19
  onJoin?: () => void;
19
20
  onDepositEscrow?: () => void;
20
21
  onManage?: () => void;
@@ -30,8 +31,12 @@ interface CardConfig {
30
31
  ctaLabel?: string;
31
32
  ctaColor?: string;
32
33
  ctaAction?: () => void;
34
+ secondaryCtaLabel?: string;
35
+ secondaryCtaColor?: string;
36
+ secondaryCtaAction?: () => void;
33
37
  showCountdown?: 'start' | 'end';
34
38
  loading?: boolean;
39
+ secondaryLoading?: boolean;
35
40
  }
36
41
 
37
42
  export const CalcuttaActionCard: React.FC<CalcuttaActionCardProps> = ({
@@ -42,6 +47,7 @@ export const CalcuttaActionCard: React.FC<CalcuttaActionCardProps> = ({
42
47
  activeBidCount = 0,
43
48
  itemsWon = 0,
44
49
  isAdmin,
50
+ participantCount = 0,
45
51
  onJoin,
46
52
  onDepositEscrow,
47
53
  onManage,
@@ -56,8 +62,7 @@ export const CalcuttaActionCard: React.FC<CalcuttaActionCardProps> = ({
56
62
  const getConfig = (): CardConfig => {
57
63
  // Admin: can start auction
58
64
  if (isAdmin && lifecycleState === 'scheduled' && onStartAuction) {
59
- const participantCount = competition.participants?.length ?? 0;
60
- return {
65
+ const config: CardConfig = {
61
66
  icon: 'play-circle-outline',
62
67
  title: 'Ready to start?',
63
68
  description: `${participantCount} player${participantCount !== 1 ? 's' : ''} have joined. Start the ${isSweepstakes ? 'competition' : 'auction'} when ready.`,
@@ -67,6 +72,14 @@ export const CalcuttaActionCard: React.FC<CalcuttaActionCardProps> = ({
67
72
  ctaAction: onStartAuction,
68
73
  showCountdown: 'start',
69
74
  };
75
+ // Admin hasn't joined yet — show join as secondary action
76
+ if (!hasJoined && onJoin) {
77
+ config.secondaryCtaLabel = isFree ? 'Join Free' : `Join — ${formatCurrency(entryFee, competition.market_type)}`;
78
+ config.secondaryCtaColor = '#10B981';
79
+ config.secondaryCtaAction = onJoin;
80
+ config.secondaryLoading = joining;
81
+ }
82
+ return config;
70
83
  }
71
84
 
72
85
  // Not joined — prompt to join
@@ -228,6 +241,22 @@ export const CalcuttaActionCard: React.FC<CalcuttaActionCardProps> = ({
228
241
  </View>
229
242
  )}
230
243
 
244
+ {/* Secondary CTA Button */}
245
+ {config.secondaryCtaLabel && config.secondaryCtaAction && (
246
+ <TouchableOpacity
247
+ style={[styles.ctaButton, { backgroundColor: config.secondaryCtaColor || '#10B981' }]}
248
+ onPress={config.secondaryCtaAction}
249
+ activeOpacity={0.7}
250
+ disabled={config.secondaryLoading}
251
+ >
252
+ {config.secondaryLoading ? (
253
+ <ActivityIndicator size="small" color="#FFFFFF" />
254
+ ) : (
255
+ <Text variant="body" bold style={{ color: '#FFFFFF' }}>{config.secondaryCtaLabel}</Text>
256
+ )}
257
+ </TouchableOpacity>
258
+ )}
259
+
231
260
  {/* CTA Button */}
232
261
  {config.ctaLabel && config.ctaAction && (
233
262
  <TouchableOpacity
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
2
- import { StyleSheet, TouchableOpacity, ActivityIndicator, FlatList, ScrollView, Image, TextInput, Platform, useWindowDimensions } from 'react-native';
2
+ import { StyleSheet, TouchableOpacity, ActivityIndicator, FlatList, ScrollView, Image, TextInput, Platform, useWindowDimensions, RefreshControl } from 'react-native';
3
3
  import { View, Text, useTheme } from '@bettoredge/styles';
4
4
  import { Ionicons } from '@expo/vector-icons';
5
5
  import type { CalcuttaEscrowProps } from '@bettoredge/types';
@@ -39,6 +39,8 @@ export interface CalcuttaAuctionProps {
39
39
  onManage?: () => void;
40
40
  onJoin?: () => void | Promise<void>;
41
41
  onLeave?: () => void | Promise<void>;
42
+ onRefresh?: () => void | Promise<void>;
43
+ onAuctionClosed?: () => void;
42
44
  }
43
45
 
44
46
  // Inner component for live auction (original layout)
@@ -54,12 +56,15 @@ const LiveAuction: React.FC<CalcuttaAuctionProps> = ({
54
56
  onNextItem,
55
57
  player_balance,
56
58
  onDepositFunds,
59
+ onRefresh,
60
+ onAuctionClosed: onAuctionClosedProp,
57
61
  }) => {
58
62
  const { theme } = useTheme();
59
63
  const { width: windowWidth } = useWindowDimensions();
60
64
  const [containerWidth, setContainerWidth] = useState(0);
61
65
  const measuredWidth = containerWidth || windowWidth;
62
66
  const isDesktop = Platform.OS === 'web' && measuredWidth >= 700;
67
+ const [refreshing, setRefreshing] = useState(false);
63
68
  const {
64
69
  loading,
65
70
  competition,
@@ -87,6 +92,13 @@ const LiveAuction: React.FC<CalcuttaAuctionProps> = ({
87
92
 
88
93
  const isAdmin = player_id != null && competition?.admin_id === player_id;
89
94
 
95
+ const handleRefresh = async () => {
96
+ setRefreshing(true);
97
+ try {
98
+ await Promise.all([refreshAuction(), fetchEscrow(), onRefresh?.()]);
99
+ } catch {} finally { setRefreshing(false); }
100
+ };
101
+
90
102
  // Auction complete overlay
91
103
  const [showAuctionComplete, setShowAuctionComplete] = useState(false);
92
104
 
@@ -94,7 +106,8 @@ const LiveAuction: React.FC<CalcuttaAuctionProps> = ({
94
106
  handleAuctionClosed(data);
95
107
  // Show complete overlay after a short delay (let last item celebration finish)
96
108
  setTimeout(() => setShowAuctionComplete(true), 1000);
97
- }, [handleAuctionClosed]);
109
+ onAuctionClosedProp?.();
110
+ }, [handleAuctionClosed, onAuctionClosedProp]);
98
111
 
99
112
  // Celebration modal state
100
113
  const [celebrationData, setCelebrationData] = useState<{
@@ -535,7 +548,7 @@ const LiveAuction: React.FC<CalcuttaAuctionProps> = ({
535
548
  Full width: Active item
536
549
  Below: [Items (paginated)] [Players] [Info]
537
550
  ═══════════════════════════════════════ */
538
- <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 12, paddingBottom: 40 }} keyboardShouldPersistTaps="handled">
551
+ <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 12, paddingBottom: 40 }} keyboardShouldPersistTaps="handled" refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}>
539
552
  {/* Top bar: escrow + admin */}
540
553
  <View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 12, zIndex: 200 }}>
541
554
  <View variant="transparent" style={{ position: 'relative', zIndex: 200 }}>
@@ -666,6 +679,7 @@ const LiveAuction: React.FC<CalcuttaAuctionProps> = ({
666
679
  keyboardShouldPersistTaps="handled"
667
680
  contentContainerStyle={{ paddingBottom: 40 }}
668
681
  style={{ backgroundColor: theme.colors.surface.base }}
682
+ refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}
669
683
  extraData={`${infoTab}-${items.map(i => `${i.calcutta_auction_item_id}:${i.status}:${i.current_bid}`).join(',')}-${my_bids.length}-${paused}-${escrow?.escrow_balance}`}
670
684
  ListHeaderComponent={
671
685
  <View variant="transparent">
@@ -745,12 +759,19 @@ const SweepstakesWaiting: React.FC<CalcuttaAuctionProps & { competition: any }>
745
759
  player_id,
746
760
  onClose,
747
761
  onManage,
762
+ onRefresh,
748
763
  competition,
749
764
  }) => {
750
765
  const { theme } = useTheme();
751
- const { items, rounds, participants, item_results } = useCalcuttaCompetition(calcutta_competition_id);
766
+ const { items, rounds, participants, item_results, refresh } = useCalcuttaCompetition(calcutta_competition_id);
752
767
  const { images: itemImages } = useCalcuttaItemImages(items);
753
768
  const isAdmin = player_id != null && competition?.admin_id == player_id;
769
+ const [refreshing, setRefreshing] = useState(false);
770
+ const handleRefresh = async () => {
771
+ setRefreshing(true);
772
+ try { await Promise.all([refresh(), onRefresh?.()]); }
773
+ catch {} finally { setRefreshing(false); }
774
+ };
754
775
 
755
776
  // Build player lookup for owner usernames
756
777
  const ownerIds = items.filter(i => i.winning_player_id).map(i => i.winning_player_id!);
@@ -787,7 +808,7 @@ const SweepstakesWaiting: React.FC<CalcuttaAuctionProps & { competition: any }>
787
808
 
788
809
  return (
789
810
  <View variant="transparent" style={[styles.container, { backgroundColor: theme.colors.surface.base }]}>
790
- <ScrollView style={{ flex: 1, backgroundColor: theme.colors.surface.base }} contentContainerStyle={{ gap: 16, paddingBottom: Platform.OS === 'web' ? 300 : 40, backgroundColor: theme.colors.surface.base }} keyboardShouldPersistTaps="handled">
811
+ <ScrollView style={{ flex: 1, backgroundColor: theme.colors.surface.base }} contentContainerStyle={{ gap: 16, paddingBottom: Platform.OS === 'web' ? 300 : 40, backgroundColor: theme.colors.surface.base }} keyboardShouldPersistTaps="handled" refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}>
791
812
  {/* Hero with embedded title */}
792
813
  <View style={{ position: 'relative' }}>
793
814
  {competition.image?.url ? (
@@ -1078,13 +1099,6 @@ export const CalcuttaAuction: React.FC<CalcuttaAuctionProps> = (props) => {
1078
1099
  );
1079
1100
  }
1080
1101
 
1081
- // Post-auction: show results view for all types
1082
- const isPostAuction = ['auction_closed', 'inprogress', 'closed'].includes(competition.status);
1083
-
1084
- if (competition.auction_type === 'sweepstakes' || isPostAuction) {
1085
- return <SweepstakesWaiting {...props} competition={competition} />;
1086
- }
1087
-
1088
1102
  if (competition.auction_type === 'sealed_bid') {
1089
1103
  return <SealedBidAuction {...props} />;
1090
1104
  }