@bettoredge/calcutta 0.3.1 → 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.
@@ -17,6 +17,8 @@ export interface CalcuttaAuctionItemComponentProps {
17
17
  is_active_item?: boolean;
18
18
  is_paused?: boolean;
19
19
  itemImage?: { url: string };
20
+ highest_bidder?: { username?: string; profile_pic?: string; is_me?: boolean };
21
+ winner?: { username?: string; profile_pic?: string; is_me?: boolean };
20
22
  }
21
23
 
22
24
  const getTimerColor = (secondsLeft: number, theme: any) => {
@@ -36,9 +38,16 @@ export const CalcuttaAuctionItem: React.FC<CalcuttaAuctionItemComponentProps> =
36
38
  is_active_item,
37
39
  is_paused,
38
40
  itemImage,
41
+ highest_bidder,
42
+ winner,
39
43
  }) => {
40
44
  const { theme } = useTheme();
41
- const [expanded, setExpanded] = useState(false);
45
+ const [expanded, setExpanded] = useState(!!is_active_item);
46
+
47
+ // Auto-expand when this item becomes active
48
+ useEffect(() => {
49
+ if (is_active_item) setExpanded(true);
50
+ }, [is_active_item]);
42
51
 
43
52
  // Per-item countdown for live auction
44
53
  const [secondsLeft, setSecondsLeft] = useState<number | null>(null);
@@ -66,7 +75,9 @@ export const CalcuttaAuctionItem: React.FC<CalcuttaAuctionItemComponentProps> =
66
75
  };
67
76
  }, [item.item_deadline, item.status, is_paused]);
68
77
 
69
- const canBid = (item.status === 'active' || item.status === 'pending') && !is_paused;
78
+ const canBid = auction_type === 'live'
79
+ ? item.status === 'active' && is_active_item && !is_paused
80
+ : (item.status === 'active' || item.status === 'pending') && !is_paused;
70
81
  const statusColor = getItemStatusColor(item.status);
71
82
  const isSold = item.status === 'sold';
72
83
  const isUpcoming = auction_type === 'live' && item.status === 'pending';
@@ -82,30 +93,123 @@ export const CalcuttaAuctionItem: React.FC<CalcuttaAuctionItemComponentProps> =
82
93
  return s.toFixed(1);
83
94
  };
84
95
 
96
+ // ══════════════════════════════════════
97
+ // ACTIVE ITEM — hero card, always expanded
98
+ // ══════════════════════════════════════
99
+ if (is_active_item && auction_type === 'live') {
100
+ const timerColor = secondsLeft != null && secondsLeft > 0 ? getTimerColor(secondsLeft, theme) : theme.colors.text.tertiary;
101
+ const isUrgent = secondsLeft != null && secondsLeft <= 10 && secondsLeft > 0;
102
+
103
+ return (
104
+ <View variant="transparent" style={[styles.activeCard, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.primary.default + '40' }]}>
105
+ {/* Top bar: item info + timer */}
106
+ <View variant="transparent" style={styles.activeHeader}>
107
+ {(itemImage?.url || item.item_image?.url) ? (
108
+ <Image
109
+ source={{ uri: itemImage?.url || item.item_image?.url }}
110
+ style={[styles.activeImage, { backgroundColor: theme.colors.surface.base }]}
111
+ resizeMode="contain"
112
+ />
113
+ ) : (
114
+ <View variant="transparent" style={[styles.activeImage, { backgroundColor: theme.colors.surface.base }]}>
115
+ <Ionicons
116
+ name={item.item_type === 'team' ? 'people-outline' : 'person-outline'}
117
+ size={24}
118
+ color={theme.colors.text.tertiary}
119
+ />
120
+ </View>
121
+ )}
122
+ <View variant="transparent" style={{ flex: 1, marginLeft: 14 }}>
123
+ <Text variant="h3" bold numberOfLines={1}>{item.item_name}</Text>
124
+ {item.seed != null && <Text variant="caption" color="tertiary">Seed #{item.seed}</Text>}
125
+ {highest_bidder && item.current_bid > 0 && (
126
+ <View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', marginTop: 4 }}>
127
+ {highest_bidder.profile_pic ? (
128
+ <Image source={{ uri: highest_bidder.profile_pic }} style={{ width: 16, height: 16, borderRadius: 8, marginRight: 5 }} />
129
+ ) : (
130
+ <View variant="transparent" style={{ width: 16, height: 16, borderRadius: 8, backgroundColor: highest_bidder.is_me ? '#10B98130' : theme.colors.primary.subtle, alignItems: 'center', justifyContent: 'center', marginRight: 5 }}>
131
+ <Text variant="caption" style={{ fontSize: 8, lineHeight: 11, color: highest_bidder.is_me ? '#10B981' : theme.colors.primary.default }}>
132
+ {(highest_bidder.username || '?').charAt(0).toUpperCase()}
133
+ </Text>
134
+ </View>
135
+ )}
136
+ <Text variant="caption" style={{ color: highest_bidder.is_me ? '#10B981' : theme.colors.text.secondary, fontSize: 12, lineHeight: 16 }}>
137
+ {highest_bidder.is_me ? 'You' : highest_bidder.username || 'Unknown'}
138
+ </Text>
139
+ {highest_bidder.is_me && (
140
+ <Ionicons name="checkmark-circle" size={12} color="#10B981" style={{ marginLeft: 3 }} />
141
+ )}
142
+ </View>
143
+ )}
144
+ </View>
145
+ </View>
146
+
147
+ {/* Current bid + highest bidder + timer row */}
148
+ <View variant="transparent" style={styles.activeBidRow}>
149
+ <View variant="transparent" style={{ flex: 1 }}>
150
+ <Text variant="caption" color="tertiary">Current Bid</Text>
151
+ <Text style={[styles.activeBidAmount, { color: theme.colors.text.primary }]}>
152
+ {item.current_bid > 0 ? formatCurrency(item.current_bid) : 'No bids yet'}
153
+ </Text>
154
+ </View>
155
+ {secondsLeft != null && secondsLeft > 0 && !is_paused && (
156
+ <View variant="transparent" style={[styles.activeTimerBox, { backgroundColor: timerColor + '15', borderColor: timerColor + '30' }]}>
157
+ <Ionicons name="timer-outline" size={16} color={timerColor} />
158
+ <Text style={[styles.activeTimerText, { color: timerColor }]}>
159
+ {formatCountdown(secondsLeft)}
160
+ </Text>
161
+ {isUrgent && item.current_bid > 0 && (
162
+ <Text variant="caption" bold style={{ color: theme.colors.status.error, marginLeft: 6, fontSize: 11, lineHeight: 15 }}>
163
+ Going!
164
+ </Text>
165
+ )}
166
+ </View>
167
+ )}
168
+ </View>
169
+
170
+ {/* Bid input — always visible */}
171
+ {canBid && (
172
+ <CalcuttaBidInput
173
+ current_bid={item.current_bid}
174
+ min_bid={min_bid}
175
+ bid_increment={bid_increment}
176
+ auction_type={auction_type}
177
+ escrow_balance={escrow_balance}
178
+ existing_bid_amount={my_bid?.bid_amount}
179
+ onSubmit={onPlaceBid}
180
+ loading={false}
181
+ disabled={is_paused}
182
+ />
183
+ )}
184
+ </View>
185
+ );
186
+ }
187
+
188
+ // ══════════════════════════════════════
189
+ // INACTIVE ITEMS — compact rows
190
+ // ══════════════════════════════════════
85
191
  return (
86
192
  <View
87
193
  variant="transparent"
88
194
  style={[
89
195
  styles.container,
90
- { borderColor: theme.colors.border.subtle, opacity: itemOpacity },
91
- is_active_item && { borderLeftWidth: 3, borderLeftColor: theme.colors.primary.default },
196
+ { borderColor: theme.colors.border.subtle, opacity: itemOpacity, backgroundColor: theme.colors.surface.base },
92
197
  ]}
93
198
  >
94
199
  <TouchableOpacity
95
200
  style={styles.row}
96
- activeOpacity={0.7}
201
+ activeOpacity={canBid ? 0.7 : 1}
97
202
  onPress={() => canBid && setExpanded(!expanded)}
98
203
  disabled={!canBid}
99
204
  >
100
- {/* Item image / fallback */}
101
205
  {(itemImage?.url || item.item_image?.url) ? (
102
206
  <Image
103
207
  source={{ uri: itemImage?.url || item.item_image?.url }}
104
- style={[styles.imageContainer, { backgroundColor: theme.colors.surface.base }]}
208
+ style={[styles.imageContainer, { backgroundColor: theme.colors.surface.elevated }]}
105
209
  resizeMode="contain"
106
210
  />
107
211
  ) : (
108
- <View variant="transparent" style={[styles.imageContainer, { backgroundColor: theme.colors.surface.base }]}>
212
+ <View variant="transparent" style={[styles.imageContainer, { backgroundColor: theme.colors.surface.elevated }]}>
109
213
  <Ionicons
110
214
  name={item.item_type === 'team' ? 'people-outline' : 'person-outline'}
111
215
  size={16}
@@ -115,94 +219,50 @@ export const CalcuttaAuctionItem: React.FC<CalcuttaAuctionItemComponentProps> =
115
219
  )}
116
220
 
117
221
  <View variant="transparent" style={styles.info}>
118
- <View variant="transparent" style={styles.nameRow}>
119
- <Text variant="body" bold numberOfLines={1} style={styles.name}>
120
- {item.item_name}
121
- </Text>
122
- {item.seed != null && (
123
- <Text variant="caption" color="tertiary" style={styles.seed}>
124
- #{item.seed}
125
- </Text>
126
- )}
127
- </View>
222
+ <Text variant="body" bold numberOfLines={1}>{item.item_name}{item.seed != null ? ` #${item.seed}` : ''}</Text>
128
223
  <View variant="transparent" style={styles.statusRow}>
129
224
  <View variant="transparent" style={[styles.statusBadge, { backgroundColor: statusColor + '20' }]}>
130
- <Text variant="caption" style={{ color: statusColor, fontSize: 10 }}>
131
- {getStatusLabel(item.status)}
132
- </Text>
225
+ <Text variant="caption" style={{ color: statusColor, fontSize: 10, lineHeight: 13 }}>{getStatusLabel(item.status)}</Text>
133
226
  </View>
134
227
  {my_bid && (
135
228
  <View variant="transparent" style={styles.bidStatus}>
136
- <Ionicons
137
- name={my_bid.bid_status === 'won' ? 'checkmark-circle' : 'ellipse'}
138
- size={10}
139
- color={getBidStatusColor(my_bid.bid_status)}
140
- />
141
- <Text variant="caption" style={{ color: getBidStatusColor(my_bid.bid_status), marginLeft: 4, fontSize: 10 }}>
142
- {getStatusLabel(my_bid.bid_status)}
143
- </Text>
229
+ <Ionicons name={my_bid.bid_status === 'won' ? 'checkmark-circle' : 'ellipse'} size={10} color={getBidStatusColor(my_bid.bid_status)} />
230
+ <Text variant="caption" style={{ color: getBidStatusColor(my_bid.bid_status), marginLeft: 3, fontSize: 10, lineHeight: 13 }}>{getStatusLabel(my_bid.bid_status)}</Text>
144
231
  </View>
145
232
  )}
146
- {/* Winner info for sold items */}
147
233
  {isSold && item.winning_bid > 0 && (
148
- <Text variant="caption" color="tertiary" style={{ marginLeft: 8, fontSize: 10 }}>
149
- Sold: {formatCurrency(item.winning_bid)}
150
- </Text>
234
+ <Text variant="caption" color="tertiary" style={{ marginLeft: 6, fontSize: 10, lineHeight: 13 }}>Sold: {formatCurrency(item.winning_bid)}</Text>
235
+ )}
236
+ {isSold && winner && (
237
+ <View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', marginLeft: 6 }}>
238
+ {winner.profile_pic ? (
239
+ <Image source={{ uri: winner.profile_pic }} style={{ width: 14, height: 14, borderRadius: 7, marginRight: 3 }} />
240
+ ) : (
241
+ <View variant="transparent" style={{ width: 14, height: 14, borderRadius: 7, backgroundColor: winner.is_me ? '#10B98130' : theme.colors.primary.subtle, alignItems: 'center', justifyContent: 'center', marginRight: 3 }}>
242
+ <Text variant="caption" style={{ fontSize: 7, lineHeight: 10, color: winner.is_me ? '#10B981' : theme.colors.primary.default }}>
243
+ {(winner.username || '?').charAt(0).toUpperCase()}
244
+ </Text>
245
+ </View>
246
+ )}
247
+ <Text variant="caption" style={{ fontSize: 10, lineHeight: 13, color: winner.is_me ? '#10B981' : theme.colors.text.tertiary }}>
248
+ {winner.is_me ? 'You' : winner.username || 'Unknown'}
249
+ </Text>
250
+ </View>
151
251
  )}
152
252
  </View>
153
253
  </View>
154
254
 
155
- {/* Per-item countdown timer badge (live active item) */}
156
- {is_active_item && secondsLeft != null && secondsLeft > 0 && !is_paused && (
157
- <View
158
- variant="transparent"
159
- style={[
160
- styles.timerBadge,
161
- { backgroundColor: getTimerColor(secondsLeft, theme) + '20' },
162
- secondsLeft < 5 && { opacity: Math.abs(Math.sin(Date.now() / 300)) * 0.5 + 0.5 },
163
- ]}
164
- >
165
- <Ionicons name="timer-outline" size={12} color={getTimerColor(secondsLeft, theme)} />
166
- <Text
167
- variant="caption"
168
- bold
169
- style={{ color: getTimerColor(secondsLeft, theme), marginLeft: 3, fontSize: 11 }}
170
- >
171
- {formatCountdown(secondsLeft)}
172
- </Text>
173
- </View>
174
- )}
175
-
176
- {/* Going once indicator */}
177
- {is_active_item && secondsLeft != null && secondsLeft <= 10 && secondsLeft > 0 && item.current_bid > 0 && !is_paused && (
178
- <Text variant="caption" bold style={{ color: theme.colors.status.error, fontSize: 10, marginLeft: 4 }}>
179
- Going...
180
- </Text>
181
- )}
182
-
183
- <View variant="transparent" style={styles.bidInfo}>
184
- <Text variant="caption" color="tertiary">
185
- {auction_type === 'sealed_bid' ? 'Your Bid' : 'Current Bid'}
255
+ <View variant="transparent" style={styles.rightColumn}>
256
+ <Text variant="caption" color="tertiary" style={{ fontSize: 10, lineHeight: 13 }}>
257
+ {auction_type === 'sealed_bid' ? 'Your Bid' : isSold ? 'Winning Bid' : 'Current Bid'}
186
258
  </Text>
187
259
  <Text variant="body" bold>
188
260
  {auction_type === 'sealed_bid'
189
- ? my_bid
190
- ? formatCurrency(my_bid.bid_amount)
191
- : '-'
192
- : item.current_bid > 0
193
- ? formatCurrency(item.current_bid)
194
- : '-'}
261
+ ? my_bid ? formatCurrency(my_bid.bid_amount) : '-'
262
+ : (isSold ? item.winning_bid : item.current_bid) > 0 ? formatCurrency(isSold ? item.winning_bid : item.current_bid) : '-'}
195
263
  </Text>
264
+ {canBid && <Ionicons name={expanded ? 'chevron-up' : 'chevron-down'} size={14} color={theme.colors.text.tertiary} style={{ marginTop: 2 }} />}
196
265
  </View>
197
-
198
- {canBid && (
199
- <Ionicons
200
- name={expanded ? 'chevron-up' : 'chevron-down'}
201
- size={16}
202
- color={theme.colors.text.tertiary}
203
- style={styles.chevron}
204
- />
205
- )}
206
266
  </TouchableOpacity>
207
267
 
208
268
  {expanded && canBid && (
@@ -225,6 +285,54 @@ export const CalcuttaAuctionItem: React.FC<CalcuttaAuctionItemComponentProps> =
225
285
  };
226
286
 
227
287
  const styles = StyleSheet.create({
288
+ // ── Active item hero card ──
289
+ activeCard: {
290
+ marginHorizontal: 12,
291
+ marginVertical: 8,
292
+ borderRadius: 16,
293
+ borderWidth: 1.5,
294
+ padding: 16,
295
+ },
296
+ activeHeader: {
297
+ flexDirection: 'row',
298
+ alignItems: 'center',
299
+ },
300
+ activeImage: {
301
+ width: 52,
302
+ height: 52,
303
+ borderRadius: 12,
304
+ alignItems: 'center',
305
+ justifyContent: 'center',
306
+ },
307
+ activeBidRow: {
308
+ flexDirection: 'row',
309
+ alignItems: 'center',
310
+ marginTop: 14,
311
+ paddingTop: 14,
312
+ borderTopWidth: 1,
313
+ borderTopColor: 'rgba(128,128,128,0.15)',
314
+ },
315
+ activeBidAmount: {
316
+ fontSize: 28,
317
+ lineHeight: 37,
318
+ fontWeight: '800',
319
+ marginTop: 2,
320
+ },
321
+ activeTimerBox: {
322
+ flexDirection: 'row',
323
+ alignItems: 'center',
324
+ paddingHorizontal: 12,
325
+ paddingVertical: 8,
326
+ borderRadius: 10,
327
+ borderWidth: 1,
328
+ },
329
+ activeTimerText: {
330
+ fontSize: 20,
331
+ lineHeight: 26,
332
+ fontWeight: '800',
333
+ marginLeft: 6,
334
+ },
335
+ // ── Inactive item rows ──
228
336
  container: {
229
337
  borderBottomWidth: 1,
230
338
  },
@@ -234,30 +342,22 @@ const styles = StyleSheet.create({
234
342
  padding: 12,
235
343
  },
236
344
  imageContainer: {
237
- height: 32,
238
- width: 32,
239
- borderRadius: 16,
345
+ height: 36,
346
+ width: 36,
347
+ borderRadius: 8,
240
348
  alignItems: 'center',
241
349
  justifyContent: 'center',
242
350
  },
243
351
  info: {
244
352
  flex: 1,
245
353
  marginLeft: 10,
246
- },
247
- nameRow: {
248
- flexDirection: 'row',
249
- alignItems: 'center',
250
- },
251
- name: {
252
- flex: 1,
253
- },
254
- seed: {
255
- marginLeft: 6,
354
+ marginRight: 10,
256
355
  },
257
356
  statusRow: {
258
357
  flexDirection: 'row',
259
358
  alignItems: 'center',
260
- marginTop: 4,
359
+ flexWrap: 'wrap',
360
+ marginTop: 3,
261
361
  },
262
362
  statusBadge: {
263
363
  paddingHorizontal: 6,
@@ -267,22 +367,11 @@ const styles = StyleSheet.create({
267
367
  bidStatus: {
268
368
  flexDirection: 'row',
269
369
  alignItems: 'center',
270
- marginLeft: 8,
271
- },
272
- timerBadge: {
273
- flexDirection: 'row',
274
- alignItems: 'center',
275
- paddingHorizontal: 6,
276
- paddingVertical: 3,
277
- borderRadius: 6,
278
370
  marginLeft: 6,
279
371
  },
280
- bidInfo: {
372
+ rightColumn: {
281
373
  alignItems: 'flex-end',
282
- marginLeft: 10,
283
- },
284
- chevron: {
285
- marginLeft: 8,
374
+ minWidth: 70,
286
375
  },
287
376
  bidInputContainer: {
288
377
  paddingHorizontal: 12,