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.
- package/lib/commonjs/Assets/fonts/Barlow-Bold.ttf +0 -0
- package/lib/commonjs/Assets/fonts/Barlow-Regular.ttf +0 -0
- package/lib/commonjs/Assets/fonts/Barlow-SemiBold.ttf +0 -0
- package/lib/commonjs/Assets/images/powered_by_be.webp +0 -0
- package/lib/commonjs/BetRouter/components/MyOpportunities.tsx.bak +440 -0
- package/lib/commonjs/BetRouter/types/ADMIN_PORTAL.md +863 -0
- package/lib/commonjs/BetRouter/types/ADMIN_PORTAL_SIMPLIFIED.md +1881 -0
- package/lib/commonjs/BetRouter/types/CREDENTIALS_EXAMPLE.md +350 -0
- package/lib/commonjs/BetRouter/types/LIQUIDITY_CLIENT_GUIDE.md +399 -0
- package/lib/commonjs/BetRouter/types/MARKET_LINKING_WORKFLOW.md +682 -0
- package/lib/commonjs/BetRouter/types/MARKET_LINKING_WORKFLOW_V2.md +627 -0
- package/lib/commonjs/BetRouter/types/README.md +249 -0
- package/lib/commonjs/Charts/README.md +310 -0
- package/lib/commonjs/NotificationManager/components/ScheduleNotification.js +1 -16
- package/lib/commonjs/NotificationManager/components/ScheduleNotification.js.map +1 -1
- package/lib/commonjs/NotificationManager/components/shared/GroupSelector.js +74 -4
- package/lib/commonjs/NotificationManager/components/shared/GroupSelector.js.map +1 -1
- package/lib/commonjs/NotificationManager/index.js +49 -5
- package/lib/commonjs/NotificationManager/index.js.map +1 -1
- package/package.json +1 -1
- package/src/NotificationManager/components/ScheduleNotification.tsx +1 -17
- package/src/NotificationManager/components/shared/GroupSelector.tsx +83 -6
- package/src/NotificationManager/index.tsx +52 -5
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|