be-components 7.7.2 → 7.7.3

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.
Files changed (23) hide show
  1. package/lib/commonjs/Assets/fonts/Barlow-Bold.ttf +0 -0
  2. package/lib/commonjs/Assets/fonts/Barlow-Regular.ttf +0 -0
  3. package/lib/commonjs/Assets/fonts/Barlow-SemiBold.ttf +0 -0
  4. package/lib/commonjs/Assets/images/powered_by_be.webp +0 -0
  5. package/lib/commonjs/BetRouter/components/MyOpportunities.tsx.bak +440 -0
  6. package/lib/commonjs/BetRouter/types/ADMIN_PORTAL.md +863 -0
  7. package/lib/commonjs/BetRouter/types/ADMIN_PORTAL_SIMPLIFIED.md +1881 -0
  8. package/lib/commonjs/BetRouter/types/CREDENTIALS_EXAMPLE.md +350 -0
  9. package/lib/commonjs/BetRouter/types/LIQUIDITY_CLIENT_GUIDE.md +399 -0
  10. package/lib/commonjs/BetRouter/types/MARKET_LINKING_WORKFLOW.md +682 -0
  11. package/lib/commonjs/BetRouter/types/MARKET_LINKING_WORKFLOW_V2.md +627 -0
  12. package/lib/commonjs/BetRouter/types/README.md +249 -0
  13. package/lib/commonjs/Charts/README.md +310 -0
  14. package/lib/commonjs/NotificationManager/components/ScheduleNotification.js +1 -16
  15. package/lib/commonjs/NotificationManager/components/ScheduleNotification.js.map +1 -1
  16. package/lib/commonjs/NotificationManager/components/shared/GroupSelector.js +74 -4
  17. package/lib/commonjs/NotificationManager/components/shared/GroupSelector.js.map +1 -1
  18. package/lib/commonjs/NotificationManager/index.js +49 -5
  19. package/lib/commonjs/NotificationManager/index.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/NotificationManager/components/ScheduleNotification.tsx +1 -17
  22. package/src/NotificationManager/components/shared/GroupSelector.tsx +83 -6
  23. package/src/NotificationManager/index.tsx +52 -5
@@ -0,0 +1,440 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import type { OpportunityProps, OpportunityStatusLegProps, RouterOrderProps, RouterPartnerProps } from '../../types';
3
+ import { Text, View, Button } from '../../Components/Themed';
4
+ import { BetRouterApi } from '../api';
5
+ import { FlatList, TouchableOpacity } from 'react-native';
6
+ import { useColors } from '../../constants/useColors';
7
+ import { SearchBox, Icons } from '../../Components';
8
+ import Pagination from '../../Components/Pagination';
9
+ import OpportunityDetailModal from './OpportunityDetailModal';
10
+
11
+ type MyOpportunitiesProps = {
12
+ partners:RouterPartnerProps[];
13
+ compact?:boolean;
14
+ insets?: { top: number; bottom: number; left: number; right: number };
15
+ onBuyOpportunity?: (opportunity: any, legIndex?: number) => void;
16
+ }
17
+
18
+ const ITEMS_PER_PAGE = 5;
19
+
20
+ const MyOpportunities = ({ partners, compact = false, insets, onBuyOpportunity }:MyOpportunitiesProps) => {
21
+ const Colors = useColors();
22
+
23
+ const [ state, setState ] = useState<{
24
+ loading:boolean,
25
+ opportunities:OpportunityProps[],
26
+ opportunity_status_legs: OpportunityStatusLegProps[],
27
+ router_orders:RouterOrderProps[],
28
+ searchText: string,
29
+ currentPage: number,
30
+ selectedOpportunity: OpportunityProps | null,
31
+ showDetailModal: boolean
32
+ }>({
33
+ loading:false,
34
+ opportunities:[],
35
+ opportunity_status_legs:[],
36
+ router_orders:[],
37
+ searchText: '',
38
+ currentPage: 0,
39
+ selectedOpportunity: null,
40
+ showDetailModal: false
41
+ });
42
+
43
+ const { loading, opportunities, opportunity_status_legs, router_orders, searchText, currentPage, selectedOpportunity, showDetailModal } = state;
44
+
45
+ useEffect(() => {
46
+ BetRouterApi.setEnvironment();
47
+ getOpportunities();
48
+ },[]);
49
+
50
+ const getOpportunities = async() => {
51
+ setState({ ...state, loading: true });
52
+ const opps = await BetRouterApi.BetRouter.OpportunityApi.getMyOpenOpportunities();
53
+ setState({
54
+ ...state,
55
+ router_orders: opps.router_orders,
56
+ opportunities: opps.opportunities,
57
+ opportunity_status_legs: opps.opportunity_status_legs,
58
+ loading: false
59
+ })
60
+ }
61
+
62
+ // Filter and paginate opportunities
63
+ const filteredOpportunities = opportunities.filter(opp => {
64
+ if (!searchText) return true;
65
+
66
+ // Search in opportunity type
67
+ if (opp.opportunity_type.toLowerCase().includes(searchText.toLowerCase())) return true;
68
+
69
+ // Search in order titles
70
+ const oppOrders = router_orders.filter(order => order.opportunity_id === opp.opportunity_id);
71
+ return oppOrders.some(order => order.title?.toLowerCase().includes(searchText.toLowerCase()));
72
+ });
73
+
74
+ const totalPages = Math.ceil(filteredOpportunities.length / ITEMS_PER_PAGE);
75
+ const paginatedOpportunities = filteredOpportunities.slice(
76
+ currentPage * ITEMS_PER_PAGE,
77
+ (currentPage + 1) * ITEMS_PER_PAGE
78
+ );
79
+
80
+ const handleOpenDetail = (opp: OpportunityProps) => {
81
+ setState({ ...state, selectedOpportunity: opp, showDetailModal: true });
82
+ };
83
+
84
+ const handleCloseDetail = () => {
85
+ setState({ ...state, selectedOpportunity: null, showDetailModal: false });
86
+ };
87
+
88
+ const renderDetailModal = () => {
89
+ if (!selectedOpportunity) return null;
90
+
91
+ const opp = selectedOpportunity;
92
+ const isProfitable = (opp.expected_profit || 0) > 0;
93
+ const oppOrders = router_orders.filter(order => order.opportunity_id === opp.opportunity_id);
94
+
95
+ // Calculate available height respecting insets
96
+ const screenHeight = Dimensions.get('window').height;
97
+ const topInset = insets?.top || 0;
98
+ const bottomInset = insets?.bottom || 0;
99
+ const modalPadding = 32; // 16px top + 16px bottom
100
+ const maxModalHeight = screenHeight - topInset - bottomInset - modalPadding;
101
+
102
+ return (
103
+ <Modal
104
+ visible={showDetailModal}
105
+ transparent
106
+ animationType="fade"
107
+ onRequestClose={handleCloseDetail}
108
+ >
109
+ <View style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center', paddingTop: topInset, paddingBottom: bottomInset, paddingHorizontal: 16 }}>
110
+ <View type='body' style={{ width: '100%', maxWidth: 500, borderRadius: 12, height: maxModalHeight }}>
111
+ {/* Header */}
112
+ <View type='header' style={{ padding: 16, borderTopLeftRadius: 12, borderTopRightRadius: 12, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
113
+ <View transparent style={{ flex: 1 }}>
114
+ <Text theme='h1' size={18}>
115
+ {opp.opportunity_type.toUpperCase()}
116
+ </Text>
117
+ <Text
118
+ theme='h2'
119
+ size={20}
120
+ color={isProfitable ? Colors.text.success : Colors.text.warning}
121
+ style={{ marginTop: 4 }}
122
+ >
123
+ ${(opp.expected_profit || 0).toFixed(2)} profit
124
+ </Text>
125
+ </View>
126
+ <TouchableOpacity onPress={handleCloseDetail}>
127
+ <Icons.CloseIcon size={24} color={Colors.text.h1} />
128
+ </TouchableOpacity>
129
+ </View>
130
+
131
+ {/* Content */}
132
+ <View style={{ flex: 1 }}>
133
+ <FlatList
134
+ data={oppOrders}
135
+ keyExtractor={(item, idx) => `${item.router_order_id}-${idx}`}
136
+ style={{ flex: 1 }}
137
+ contentContainerStyle={{ padding: 16 }}
138
+ renderItem={({ item: order, index }) => {
139
+ const partner = partners.find(p => p.partner_id === order.partner_id);
140
+ const hasResult = order.result !== null && order.result !== undefined;
141
+ const displayWinnings = hasResult ? order.net_winnings : order.potential_payout;
142
+ const winningsLabel = hasResult
143
+ ? (order.result === 'win' ? '✓ Won'
144
+ : order.result === 'lose' ? '✗ Lost'
145
+ : order.result === 'push' ? '= Draw'
146
+ : order.result === 'cancelled' ? '✕ Cancelled'
147
+ : order.result)
148
+ : '⋯ Potential';
149
+
150
+ return (
151
+ <View transparent style={{
152
+ padding: 12,
153
+ backgroundColor: Colors.views.header,
154
+ borderRadius: 8,
155
+ marginBottom: 12,
156
+ borderWidth: 1,
157
+ borderColor: Colors.borders.light
158
+ }}>
159
+ <View transparent style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 8 }}>
160
+ <Text theme='h2' size={14}>
161
+ {partner?.name || 'Unknown'}
162
+ </Text>
163
+ <Text theme='h2' size={14}>
164
+ ${order.amount?.toFixed(2)}
165
+ </Text>
166
+ </View>
167
+
168
+ <Text theme='description' size={13} style={{ marginBottom: 8 }}>
169
+ {order.title}
170
+ </Text>
171
+
172
+ <View transparent style={{ flexDirection: 'row', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
173
+ <View transparent>
174
+ <Text theme='description' size={11}>Contest</Text>
175
+ <Text theme='h2' size={12} style={{ marginTop: 2 }}>
176
+ {order.contest_label}
177
+ </Text>
178
+ </View>
179
+
180
+ <View transparent>
181
+ <Text theme='description' size={11}>{winningsLabel}</Text>
182
+ <Text
183
+ theme='h2'
184
+ size={12}
185
+ style={{ marginTop: 2 }}
186
+ color={hasResult ? (order.result === 'win' ? Colors.text.success : order.result === 'loss' ? Colors.text.warning : undefined) : undefined}
187
+ >
188
+ ${displayWinnings?.toFixed(2)}
189
+ </Text>
190
+ </View>
191
+
192
+ <View transparent>
193
+ <Text theme='description' size={11}>Price</Text>
194
+ <Text theme='h2' size={12} style={{ marginTop: 2 }}>
195
+ {((order.price || 0) * 100).toFixed(1)}%
196
+ </Text>
197
+ </View>
198
+
199
+ <View transparent>
200
+ <Text theme='description' size={11}>Status</Text>
201
+ <Text theme='h2' size={12} style={{ marginTop: 2 }}>
202
+ {order.status}
203
+ </Text>
204
+ </View>
205
+ </View>
206
+ </View>
207
+ );
208
+ }}
209
+ ListFooterComponent={() => (
210
+ <View transparent style={{ marginTop: 8, paddingTop: 16, borderTopWidth: 1, borderTopColor: Colors.borders.light }}>
211
+ <View transparent style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 8 }}>
212
+ <Text theme='description' size={13}>Total Bet</Text>
213
+ <Text theme='h2' size={15}>
214
+ ${opp.total_bet_amount.toFixed(2)}
215
+ </Text>
216
+ </View>
217
+
218
+ <View transparent style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 8 }}>
219
+ <Text theme='description' size={13}>Expected ROI</Text>
220
+ <Text theme='h2' size={15}>
221
+ {opp.expected_roi.toFixed(1)}%
222
+ </Text>
223
+ </View>
224
+
225
+ <View transparent style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 8 }}>
226
+ <Text theme='description' size={13}>Total Winnings</Text>
227
+ <Text theme='h2' size={15}>
228
+ ${opp.total_winnings.toFixed(2)}
229
+ </Text>
230
+ </View>
231
+
232
+ <View transparent style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 8 }}>
233
+ <Text theme='description' size={13}>Total Fees</Text>
234
+ <Text theme='h2' size={15}>
235
+ ${opp.total_fees.toFixed(2)}
236
+ </Text>
237
+ </View>
238
+
239
+ <View transparent style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
240
+ <Text theme='description' size={13}>Fill Status</Text>
241
+ <Text theme='h2' size={15}>
242
+ {opp.fill_status === 'all_filled' ? '✓ All filled' :
243
+ opp.fill_status === 'partially_filled' ? '⚠ Partial' :
244
+ opp.fill_status === 'failed' ? '✗ Failed' : '⋯ Pending'}
245
+ </Text>
246
+ </View>
247
+ </View>
248
+ )}
249
+ />
250
+ </View>
251
+ </View>
252
+ </View>
253
+ </Modal>
254
+ );
255
+ };
256
+
257
+ const renderOpportunityCard = (data:{ item:OpportunityProps, index:number }) => {
258
+ const opp = data.item;
259
+ const isProfitable = (opp.expected_profit || 0) > 0;
260
+ const oppOrders = router_orders.filter(order => order.opportunity_id === opp.opportunity_id);
261
+ const firstOrder = oppOrders[0];
262
+ const partner = partners.find(p => p.partner_id === firstOrder?.partner_id);
263
+
264
+ return (
265
+ <TouchableOpacity onPress={() => handleOpenDetail(opp)}>
266
+ <View type='row' style={{ padding: 12, borderBottomWidth: 1, borderBottomColor: Colors.borders.light, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
267
+ <View transparent style={{ flex: 1, flexDirection: 'row', alignItems: 'center', gap: 12 }}>
268
+ {/* Type & Profit */}
269
+ <View transparent style={{ minWidth: 60 }}>
270
+ <Text theme='h2' size={11} numberOfLines={1}>
271
+ {opp.opportunity_type.toUpperCase()}
272
+ </Text>
273
+ <Text
274
+ theme='h2'
275
+ size={13}
276
+ color={isProfitable ? Colors.text.success : Colors.text.warning}
277
+ >
278
+ ${(opp.expected_profit || 0).toFixed(2)}
279
+ </Text>
280
+ </View>
281
+
282
+ {/* Partner/Contest */}
283
+ <View transparent style={{ flex: 1, minWidth: 0 }}>
284
+ {firstOrder && (
285
+ <Text theme='description' size={11} numberOfLines={1}>
286
+ {partner?.name || 'Unknown'}
287
+ </Text>
288
+ )}
289
+ <Text theme='description' size={10} numberOfLines={1}>
290
+ {oppOrders.length} leg{oppOrders.length !== 1 ? 's' : ''}
291
+ </Text>
292
+ </View>
293
+
294
+ {/* Bet */}
295
+ <View transparent style={{ minWidth: 40 }}>
296
+ <Text theme='h2' size={11}>
297
+ ${opp.total_bet_amount.toFixed(0)}
298
+ </Text>
299
+ </View>
300
+
301
+ {/* ROI */}
302
+ <View transparent style={{ minWidth: 45 }}>
303
+ <Text theme='h2' size={11}>
304
+ {opp.expected_roi.toFixed(1)}%
305
+ </Text>
306
+ </View>
307
+
308
+ {/* Status */}
309
+ <View transparent style={{ minWidth: 20 }}>
310
+ <Text theme='h2' size={13}>
311
+ {opp.fill_status === 'all_filled' ? '✓' :
312
+ opp.fill_status === 'partially_filled' ? '⚠' :
313
+ opp.fill_status === 'failed' ? '✗' : '⋯'}
314
+ </Text>
315
+ </View>
316
+ </View>
317
+
318
+ <Icons.ChevronIcon size={16} color={Colors.text.descriptionLight} direction="right" />
319
+ </View>
320
+ </TouchableOpacity>
321
+ );
322
+ };
323
+
324
+ const renderOpportunities = (data:{ item:OpportunityProps, index:number }) => {
325
+ const opp = data.item;
326
+ const isProfitable = (opp.expected_profit || 0) > 0;
327
+
328
+ // Get orders for this opportunity
329
+ const oppOrders = router_orders.filter(order => order.opportunity_id === opp.opportunity_id);
330
+
331
+ if (compact) {
332
+ // Compact mode for desktop sidebar - show minimal info, clickable
333
+ return (
334
+ <TouchableOpacity onPress={() => handleOpenDetail(opp)}>
335
+ <View type='row' style={{ padding:10, borderBottomWidth:1, borderBottomColor: Colors.borders.light }}>
336
+ <View transparent style={{ flex:1 }}>
337
+ <View transparent style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom:6 }}>
338
+ <Text theme='h1' size={14} numberOfLines={1}>
339
+ {opp.opportunity_type.toUpperCase()}
340
+ </Text>
341
+ <Text
342
+ theme='h2'
343
+ size={14}
344
+ color={isProfitable ? Colors.text.success : Colors.text.warning}
345
+ >
346
+ ${(opp.expected_profit || 0).toFixed(2)}
347
+ </Text>
348
+ </View>
349
+
350
+ <View transparent style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
351
+ <Text theme='description' size={11}>
352
+ {oppOrders.length} leg{oppOrders.length !== 1 ? 's' : ''} • ${opp.total_bet_amount.toFixed(2)}
353
+ </Text>
354
+ <Text theme='description' size={11}>
355
+ ROI: {opp.expected_roi.toFixed(1)}%
356
+ </Text>
357
+ </View>
358
+ </View>
359
+ </View>
360
+ </TouchableOpacity>
361
+ );
362
+ }
363
+
364
+ // Mobile-friendly card layout
365
+ return renderOpportunityCard(data);
366
+ }
367
+
368
+ const displayData = compact ? opportunities : paginatedOpportunities;
369
+
370
+ return (
371
+ <View style={{ flex:1 }}>
372
+ <View type='header' style={{ flexDirection:'row', alignItems:'center', padding:compact ? 8 : 10, borderTopRightRadius:8, borderTopLeftRadius:8 }}>
373
+ <View transparent style={{ flex:1 }}>
374
+ <Text theme='h1' style={{ fontSize: compact ? 14 : 16 }}>
375
+ {compact ? 'My Bets' : 'My Active Opportunities'}
376
+ </Text>
377
+ {compact && (
378
+ <Text theme='description' style={{ fontSize:11, marginTop:2 }}>
379
+ {opportunities.length} active
380
+ </Text>
381
+ )}
382
+ </View>
383
+ </View>
384
+
385
+ {/* Search - only show if not compact */}
386
+ {!compact && (
387
+ <View transparent style={{ padding: 12, paddingBottom: 0 }}>
388
+ <SearchBox
389
+ placeholder="Search by contest..."
390
+ onChange={(text) => setState({ ...state, searchText: text, currentPage: 0 })}
391
+ hide_search_button
392
+ />
393
+ </View>
394
+ )}
395
+
396
+ <View type='body' style={{ flex:1 }}>
397
+ {loading ? (
398
+ <View transparent style={{ padding:10 }}>
399
+ <Text theme='description' style={{ fontSize: compact ? 11 : 13 }}>
400
+ Loading...
401
+ </Text>
402
+ </View>
403
+ ) : filteredOpportunities.length === 0 ? (
404
+ <View transparent style={{ padding:10 }}>
405
+ <Text theme='description' style={{ fontSize: compact ? 11 : 13 }}>
406
+ {searchText ? 'No opportunities match your search' : 'No active opportunities'}
407
+ </Text>
408
+ </View>
409
+ ) : (
410
+ <>
411
+ <FlatList
412
+ key='my_opps_list'
413
+ data={displayData}
414
+ keyExtractor={(item) => item.opportunity_id}
415
+ renderItem={renderOpportunities}
416
+ />
417
+
418
+ {/* Pagination - only show if not compact */}
419
+ {!compact && totalPages > 1 && (
420
+ <View transparent style={{ padding: 12 }}>
421
+ <Pagination
422
+ page={currentPage}
423
+ totalPages={totalPages}
424
+ onPageChange={(page) => setState({ ...state, currentPage: page })}
425
+ />
426
+ <Text theme='description' size={11} style={{ marginTop: 8, textAlign: 'center' }}>
427
+ Showing {currentPage * ITEMS_PER_PAGE + 1}-{Math.min((currentPage + 1) * ITEMS_PER_PAGE, filteredOpportunities.length)} of {filteredOpportunities.length}
428
+ </Text>
429
+ </View>
430
+ )}
431
+ </>
432
+ )}
433
+ </View>
434
+
435
+ {renderDetailModal()}
436
+ </View>
437
+ )
438
+ }
439
+
440
+ export default MyOpportunities