react-native-fpay 0.3.13 → 0.3.14
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/module/FountainPayProvider.js +2 -2
- package/lib/module/FountainPayProvider.js.map +1 -1
- package/lib/module/core/api/client.js +1 -1
- package/lib/module/core/api/index.js +3 -3
- package/lib/module/core/api/index.js.map +1 -1
- package/lib/module/ui/modals/FPPaymentRequestModal.js +0 -1
- package/lib/module/ui/modals/FPPaymentRequestModal.js.map +1 -1
- package/lib/module/ui/modals/FPShell.js +0 -1
- package/lib/module/ui/modals/FPShell.js.map +1 -1
- package/lib/module/ui/screens/ResultScreen.js +321 -0
- package/lib/module/ui/screens/ResultScreen.js.map +1 -0
- package/lib/module/ui/screens/SendScreen.js +30 -134
- package/lib/module/ui/screens/SendScreen.js.map +1 -1
- package/lib/module/ui/theme/index.js +1 -1
- package/lib/typescript/src/core/api/index.d.ts +6 -2
- package/lib/typescript/src/core/api/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/OtpInput/Styles.d.ts +21 -21
- package/lib/typescript/src/ui/modals/FPPaymentRequestModal.d.ts.map +1 -1
- package/lib/typescript/src/ui/modals/FPShell.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/ResultScreen.d.ts +8 -0
- package/lib/typescript/src/ui/screens/ResultScreen.d.ts.map +1 -0
- package/lib/typescript/src/ui/screens/SendScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/styles.d.ts +82 -82
- package/lib/typescript/src/ui/screens/sub/sendPayment/BluetoothSubScreen.d.ts +22 -22
- package/lib/typescript/src/ui/screens/sub/sendPayment/ProximitySubScreen.d.ts +22 -22
- package/package.json +1 -1
- package/src/FountainPayProvider.tsx +2 -2
- package/src/core/api/client.ts +1 -1
- package/src/core/api/index.ts +3 -3
- package/src/ui/modals/FPPaymentRequestModal.tsx +1 -5
- package/src/ui/modals/FPShell.tsx +0 -1
- package/src/ui/screens/ResultScreen.tsx +291 -0
- package/src/ui/screens/SendScreen.tsx +37 -171
- package/src/ui/theme/index.ts +1 -1
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Animated, Easing, TouchableOpacity } from 'react-native';
|
|
3
|
+
import Svg, { Path, Circle } from 'react-native-svg';
|
|
4
|
+
import styled from 'styled-components/native';
|
|
5
|
+
import { C, F, R, S } from '../theme';
|
|
6
|
+
import type { FPTransaction } from '../../core/types';
|
|
7
|
+
import { transferAPI } from '../../core/api';
|
|
8
|
+
|
|
9
|
+
// ── Icons ─────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
const SuccessIcon = () => (
|
|
12
|
+
<Svg width={64} height={64} viewBox="0 0 64 64" fill="none">
|
|
13
|
+
<Circle cx="32" cy="32" r="32" fill="#00875A" />
|
|
14
|
+
<Path d="M20 33L28 41L44 24" stroke="#fff" strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
15
|
+
</Svg>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const FailedIcon = () => (
|
|
19
|
+
<Svg width={64} height={64} viewBox="0 0 64 64" fill="none">
|
|
20
|
+
<Circle cx="32" cy="32" r="32" fill="#DE350B" />
|
|
21
|
+
<Path d="M22 22L42 42M42 22L22 42" stroke="#fff" strokeWidth="3.5" strokeLinecap="round" />
|
|
22
|
+
</Svg>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// ── Styled Components ─────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const Container = styled(Animated.View)`
|
|
28
|
+
flex: 1;
|
|
29
|
+
background-color: ${C.white};
|
|
30
|
+
padding: ${S.xxl}px ${S.lg}px ${S.xl}px;
|
|
31
|
+
align-items: center;
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const IconWrap = styled(Animated.View)`
|
|
35
|
+
margin-bottom: ${S.lg}px;
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const IconBg = styled.View<{ bg: string }>`
|
|
39
|
+
width: 112px;
|
|
40
|
+
height: 112px;
|
|
41
|
+
border-radius: 56px;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
align-items: center;
|
|
44
|
+
background-color: ${({ bg }) => bg};
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const StatusText = styled.Text<{ color: string }>`
|
|
48
|
+
font-size: ${F.xl}px;
|
|
49
|
+
font-weight: 800;
|
|
50
|
+
margin-bottom: ${S.sm}px;
|
|
51
|
+
text-align: center;
|
|
52
|
+
color: ${({ color }) => color};
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
const Amount = styled.Text`
|
|
56
|
+
font-size: ${F.hero}px;
|
|
57
|
+
font-weight: 900;
|
|
58
|
+
color: ${C.ink};
|
|
59
|
+
margin-bottom: ${S.xl}px;
|
|
60
|
+
letter-spacing: -1px;
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const Card = styled.View`
|
|
64
|
+
width: 100%;
|
|
65
|
+
background-color: ${C.surface};
|
|
66
|
+
border-radius: ${R.xl}px;
|
|
67
|
+
padding: ${S.md}px ${S.lg}px;
|
|
68
|
+
margin-bottom: ${S.xl}px;
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const RowWrap = styled.View`
|
|
72
|
+
flex-direction: row;
|
|
73
|
+
justify-content: space-between;
|
|
74
|
+
align-items: center;
|
|
75
|
+
padding: ${S.sm}px 0;
|
|
76
|
+
border-bottom-width: 1px;
|
|
77
|
+
border-bottom-color: ${C.border};
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
const RowLabel = styled.Text`
|
|
81
|
+
font-size: ${F.sm}px;
|
|
82
|
+
color: ${C.muted};
|
|
83
|
+
font-weight: 500;
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
const RowValue = styled.Text`
|
|
87
|
+
font-size: ${F.sm}px;
|
|
88
|
+
color: ${C.ink};
|
|
89
|
+
font-weight: 700;
|
|
90
|
+
max-width: 60%;
|
|
91
|
+
text-align: right;
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
const Footer = styled.View`
|
|
95
|
+
flex-direction: row;
|
|
96
|
+
align-items: center;
|
|
97
|
+
margin-top: auto;
|
|
98
|
+
`;
|
|
99
|
+
|
|
100
|
+
const CloseBtn = styled(TouchableOpacity)`
|
|
101
|
+
flex: 1;
|
|
102
|
+
background-color: ${C.brand};
|
|
103
|
+
border-radius: ${R.full}px;
|
|
104
|
+
padding: ${S.md}px 0;
|
|
105
|
+
align-items: center;
|
|
106
|
+
margin-left: ${S.md}px;
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
const CloseBtnText = styled.Text`
|
|
110
|
+
color: ${C.white};
|
|
111
|
+
font-weight: 800;
|
|
112
|
+
font-size: ${F.md}px;
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
const RingWrap = styled.View`
|
|
116
|
+
width: 48px;
|
|
117
|
+
height: 48px;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
align-items: center;
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
const RingLabel = styled.Text`
|
|
123
|
+
position: absolute;
|
|
124
|
+
font-size: 8px;
|
|
125
|
+
color: ${C.muted};
|
|
126
|
+
text-align: center;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
// ── Countdown ────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
const COUNTDOWN_SECONDS = 5;
|
|
133
|
+
const RING_SIZE = 48;
|
|
134
|
+
const STROKE_WIDTH = 3;
|
|
135
|
+
const RADIUS = (RING_SIZE - STROKE_WIDTH) / 2;
|
|
136
|
+
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
|
|
137
|
+
|
|
138
|
+
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
|
139
|
+
|
|
140
|
+
function CountdownRing({ duration }: { duration: number }) {
|
|
141
|
+
const progress = useRef(new Animated.Value(0)).current;
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
Animated.timing(progress, {
|
|
145
|
+
toValue: 1,
|
|
146
|
+
duration,
|
|
147
|
+
easing: Easing.linear,
|
|
148
|
+
useNativeDriver: false,
|
|
149
|
+
}).start();
|
|
150
|
+
}, [duration, progress]);
|
|
151
|
+
|
|
152
|
+
const strokeDashoffset = progress.interpolate({
|
|
153
|
+
inputRange: [0, 1],
|
|
154
|
+
outputRange: [0, CIRCUMFERENCE],
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<RingWrap>
|
|
159
|
+
<Svg width={RING_SIZE} height={RING_SIZE}>
|
|
160
|
+
<Circle
|
|
161
|
+
cx={RING_SIZE / 2}
|
|
162
|
+
cy={RING_SIZE / 2}
|
|
163
|
+
r={RADIUS}
|
|
164
|
+
stroke={C.border}
|
|
165
|
+
strokeWidth={STROKE_WIDTH}
|
|
166
|
+
fill="none"
|
|
167
|
+
/>
|
|
168
|
+
<AnimatedCircle
|
|
169
|
+
cx={RING_SIZE / 2}
|
|
170
|
+
cy={RING_SIZE / 2}
|
|
171
|
+
r={RADIUS}
|
|
172
|
+
stroke={C.brand}
|
|
173
|
+
strokeWidth={STROKE_WIDTH}
|
|
174
|
+
fill="none"
|
|
175
|
+
strokeDasharray={`${CIRCUMFERENCE} ${CIRCUMFERENCE}`}
|
|
176
|
+
strokeDashoffset={strokeDashoffset}
|
|
177
|
+
strokeLinecap="round"
|
|
178
|
+
rotation="-90"
|
|
179
|
+
origin={`${RING_SIZE / 2}, ${RING_SIZE / 2}`}
|
|
180
|
+
/>
|
|
181
|
+
</Svg>
|
|
182
|
+
<RingLabel>{`Auto\nclose`}</RingLabel>
|
|
183
|
+
</RingWrap>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── Row ──────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
function Row({ label, value }: { label: string; value: string }) {
|
|
190
|
+
return (
|
|
191
|
+
<RowWrap>
|
|
192
|
+
<RowLabel>{label}</RowLabel>
|
|
193
|
+
<RowValue>{value}</RowValue>
|
|
194
|
+
</RowWrap>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Main ─────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
interface Props {
|
|
201
|
+
transaction: FPTransaction | any;
|
|
202
|
+
onClose: () => void;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function ResultScreen({ transaction, onClose }: Props) {
|
|
206
|
+
const [transactionDetail, setTransactionDetail] = useState<any>(null)
|
|
207
|
+
const slideAnim = useRef(new Animated.Value(60)).current;
|
|
208
|
+
const opacAnim = useRef(new Animated.Value(0)).current;
|
|
209
|
+
const scaleAnim = useRef(new Animated.Value(0.5)).current;
|
|
210
|
+
|
|
211
|
+
const isSuccess = transactionDetail?.status.toLowerCase() === 'successful';
|
|
212
|
+
const accentColor = isSuccess ? C.green : C.red;
|
|
213
|
+
const bgColor = isSuccess ? '#E3FCEF' : '#FFEBE6';
|
|
214
|
+
|
|
215
|
+
const formatted = `${transactionDetail?.currency || 'NGN'} ${Number(transactionDetail?.amount).toLocaleString('en-NG', {
|
|
216
|
+
minimumFractionDigits: 2,
|
|
217
|
+
})}`;
|
|
218
|
+
|
|
219
|
+
const recipient = transaction?.recipient as any;
|
|
220
|
+
const recipientName = recipient?.accountName ?? recipient?.name ?? '—';
|
|
221
|
+
|
|
222
|
+
const channelLabel: Record<string, string> = {
|
|
223
|
+
transfer: 'Bank Transfer',
|
|
224
|
+
bluetooth: 'Bluetooth',
|
|
225
|
+
proximity: 'Nearby',
|
|
226
|
+
nqr: 'QR Code',
|
|
227
|
+
nfc: 'NFC Tap',
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const dateStr = transactionDetail?.createdAt
|
|
231
|
+
? new Date(transactionDetail?.createdAt).toLocaleString('en-NG')
|
|
232
|
+
: '—';
|
|
233
|
+
|
|
234
|
+
const loadTransaction = async()=>{
|
|
235
|
+
const response = await transferAPI.status(transaction.reference) as any;
|
|
236
|
+
console.log("Transaction payload: ", response);
|
|
237
|
+
if(response.status){
|
|
238
|
+
setTransactionDetail(response.payload);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
Animated.parallel([
|
|
244
|
+
Animated.spring(scaleAnim, { toValue: 1, useNativeDriver: true }),
|
|
245
|
+
Animated.timing(opacAnim, { toValue: 1, duration: 300, useNativeDriver: true }),
|
|
246
|
+
Animated.timing(slideAnim, { toValue: 0, duration: 350, useNativeDriver: true }),
|
|
247
|
+
]).start();
|
|
248
|
+
}, [scaleAnim, opacAnim, slideAnim]);
|
|
249
|
+
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
const timer = setTimeout(onClose, COUNTDOWN_SECONDS * 1000);
|
|
252
|
+
return () => clearTimeout(timer);
|
|
253
|
+
}, [onClose]);
|
|
254
|
+
|
|
255
|
+
useEffect(()=>{
|
|
256
|
+
if(transaction){
|
|
257
|
+
loadTransaction();
|
|
258
|
+
}
|
|
259
|
+
}, [transaction])
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<Container style={{ opacity: opacAnim, transform: [{ translateY: slideAnim }] }}>
|
|
263
|
+
<IconWrap style={{ transform: [{ scale: scaleAnim }] }}>
|
|
264
|
+
<IconBg bg={bgColor}>
|
|
265
|
+
{isSuccess ? <SuccessIcon /> : <FailedIcon />}
|
|
266
|
+
</IconBg>
|
|
267
|
+
</IconWrap>
|
|
268
|
+
|
|
269
|
+
<StatusText color={accentColor}>
|
|
270
|
+
{isSuccess ? 'Payment Successful' : 'Payment Failed'}
|
|
271
|
+
</StatusText>
|
|
272
|
+
|
|
273
|
+
<Amount>{formatted}</Amount>
|
|
274
|
+
|
|
275
|
+
<Card>
|
|
276
|
+
<Row label="To" value={recipientName} />
|
|
277
|
+
<Row label="Channel" value={channelLabel[transactionDetail?.channel] ?? transaction?.channel} />
|
|
278
|
+
<Row label="Reference" value={transactionDetail?.reference} />
|
|
279
|
+
<Row label="Date" value={dateStr} />
|
|
280
|
+
<Row label="Status" value={transactionDetail?.status?.toUpperCase()} />
|
|
281
|
+
</Card>
|
|
282
|
+
|
|
283
|
+
<Footer>
|
|
284
|
+
<CountdownRing duration={COUNTDOWN_SECONDS * 1000} />
|
|
285
|
+
<CloseBtn onPress={loadTransaction} activeOpacity={0.8}>
|
|
286
|
+
<CloseBtnText>Close</CloseBtnText>
|
|
287
|
+
</CloseBtn>
|
|
288
|
+
</Footer>
|
|
289
|
+
</Container>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
@@ -3,18 +3,15 @@
|
|
|
3
3
|
// Shows channel selection, then opens the
|
|
4
4
|
// relevant sub-screen in the same modal sheet
|
|
5
5
|
// ─────────────────────────────────────────────
|
|
6
|
-
import
|
|
6
|
+
import { useState } from 'react';
|
|
7
7
|
import {
|
|
8
8
|
View,
|
|
9
9
|
Text,
|
|
10
10
|
TouchableOpacity,
|
|
11
|
-
StyleSheet,
|
|
12
11
|
ScrollView,
|
|
13
12
|
Dimensions,
|
|
14
13
|
TextInput,
|
|
15
14
|
} from 'react-native';
|
|
16
|
-
import { C, R, S, F, shadow } from '../theme';
|
|
17
|
-
import { FPButton } from '../components/FPButton';
|
|
18
15
|
import { TransferSubScreen } from './sub/sendPayment/TransferSubScreen';
|
|
19
16
|
import { NQRSubScreen } from './sub/sendPayment/NQRSubScreen';
|
|
20
17
|
import { ProximitySubScreen } from './sub/sendPayment/ProximitySubScreen';
|
|
@@ -35,21 +32,12 @@ import styled from 'styled-components/native';
|
|
|
35
32
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
36
33
|
import Svg, { Path } from 'react-native-svg';
|
|
37
34
|
import { nfcAPI, nqrAPI, transferAPI } from '../../core/api';
|
|
38
|
-
import {
|
|
39
|
-
ButtonContainer,
|
|
40
|
-
ContentContainer,
|
|
41
|
-
CTAButton,
|
|
42
|
-
CTAText,
|
|
43
|
-
InputBox,
|
|
44
|
-
InputContainer,
|
|
45
|
-
StyledTextInput,
|
|
46
|
-
} from './styles';
|
|
47
|
-
import OTPInputs from '../components/OtpInput';
|
|
48
35
|
import ConfirmScreen from '../components/ConfirmScreen';
|
|
49
36
|
import LoadingAnimation from '../components/LoadingAnimation';
|
|
50
37
|
import { getFPStore } from '../../store/FPStore';
|
|
38
|
+
import { ResultScreen } from './ResultScreen';
|
|
51
39
|
|
|
52
|
-
const {
|
|
40
|
+
const { height } = Dimensions.get('window');
|
|
53
41
|
|
|
54
42
|
type Channel = 'TRANSFER' | 'QRCODE' | 'NFC' | 'BTH' | 'PXTR';
|
|
55
43
|
|
|
@@ -150,143 +138,6 @@ const ActionLabel = styled(Text)`
|
|
|
150
138
|
line-height: 14px;
|
|
151
139
|
`;
|
|
152
140
|
|
|
153
|
-
const SearchContainer = styled(View)`
|
|
154
|
-
flex-direction: row;
|
|
155
|
-
align-items: center;
|
|
156
|
-
background-color: #f9fafb;
|
|
157
|
-
border-radius: 12px;
|
|
158
|
-
padding: 14px 16px;
|
|
159
|
-
margin-bottom: 24px;
|
|
160
|
-
border: 1px solid #e5e7eb;
|
|
161
|
-
`;
|
|
162
|
-
|
|
163
|
-
const SearchInput = styled(TextInput)`
|
|
164
|
-
flex: 1;
|
|
165
|
-
margin-left: 10px;
|
|
166
|
-
font-size: 14px;
|
|
167
|
-
color: #1f2937;
|
|
168
|
-
padding: 0;
|
|
169
|
-
`;
|
|
170
|
-
|
|
171
|
-
const AddContactButton = styled.TouchableOpacity`
|
|
172
|
-
align-items: center;
|
|
173
|
-
margin-right: 20px;
|
|
174
|
-
|
|
175
|
-
flex-direction: row;
|
|
176
|
-
gap: 20px;
|
|
177
|
-
`;
|
|
178
|
-
|
|
179
|
-
const Icon = styled(Text)<{ size?: number; color?: string }>`
|
|
180
|
-
font-size: ${(props) => props.size || 24}px;
|
|
181
|
-
color: ${(props) => props.color || '#000000'};
|
|
182
|
-
`;
|
|
183
|
-
|
|
184
|
-
const AddCircle = styled.View`
|
|
185
|
-
width: 44px;
|
|
186
|
-
height: 44px;
|
|
187
|
-
border-radius: 32px;
|
|
188
|
-
background-color: #fff;
|
|
189
|
-
border-width: 2px;
|
|
190
|
-
border-color: #e0e0e0;
|
|
191
|
-
border-style: dashed;
|
|
192
|
-
justify-content: center;
|
|
193
|
-
align-items: center;
|
|
194
|
-
margin-bottom: 8px;
|
|
195
|
-
`;
|
|
196
|
-
|
|
197
|
-
const PrxyIconContainer = styled(View)<{ color: string }>`
|
|
198
|
-
width: 44px;
|
|
199
|
-
height: 44px;
|
|
200
|
-
border-radius: 100%;
|
|
201
|
-
background-color: ${(props) => props.color};
|
|
202
|
-
justify-content: center;
|
|
203
|
-
align-items: center;
|
|
204
|
-
elevation: 3;
|
|
205
|
-
shadow-color: #000;
|
|
206
|
-
shadow-offset: 0px 2px;
|
|
207
|
-
shadow-opacity: 0.12;
|
|
208
|
-
shadow-radius: 4px;
|
|
209
|
-
`;
|
|
210
|
-
|
|
211
|
-
const ContactName = styled.Text`
|
|
212
|
-
font-size: 12px;
|
|
213
|
-
color: #000;
|
|
214
|
-
`;
|
|
215
|
-
|
|
216
|
-
const ScanIcon = ({ color = '#111', size = 22 }) => (
|
|
217
|
-
<Svg
|
|
218
|
-
width={size}
|
|
219
|
-
height={size}
|
|
220
|
-
strokeWidth="0.9"
|
|
221
|
-
viewBox="0 0 24 24"
|
|
222
|
-
fill="none"
|
|
223
|
-
color={color}
|
|
224
|
-
>
|
|
225
|
-
<Path
|
|
226
|
-
d="M6 3H3V6"
|
|
227
|
-
stroke="#000000"
|
|
228
|
-
strokeWidth="0.9"
|
|
229
|
-
strokeLinecap="round"
|
|
230
|
-
strokeLinejoin="round"
|
|
231
|
-
/>
|
|
232
|
-
<Path
|
|
233
|
-
d="M2 12H12L22 12"
|
|
234
|
-
stroke="#000000"
|
|
235
|
-
strokeWidth="0.9"
|
|
236
|
-
strokeLinecap="round"
|
|
237
|
-
strokeLinejoin="round"
|
|
238
|
-
/>
|
|
239
|
-
<Path
|
|
240
|
-
d="M9 19V17V15"
|
|
241
|
-
stroke="#000000"
|
|
242
|
-
strokeWidth="0.9"
|
|
243
|
-
strokeLinecap="round"
|
|
244
|
-
strokeLinejoin="round"
|
|
245
|
-
/>
|
|
246
|
-
<Path
|
|
247
|
-
d="M12 16V15.5V15"
|
|
248
|
-
stroke="#000000"
|
|
249
|
-
strokeWidth="0.9"
|
|
250
|
-
strokeLinecap="round"
|
|
251
|
-
strokeLinejoin="round"
|
|
252
|
-
/>
|
|
253
|
-
<Path
|
|
254
|
-
d="M15 17V16V15"
|
|
255
|
-
stroke="#000000"
|
|
256
|
-
strokeWidth="0.9"
|
|
257
|
-
strokeLinecap="round"
|
|
258
|
-
strokeLinejoin="round"
|
|
259
|
-
/>
|
|
260
|
-
<Path
|
|
261
|
-
d="M12 21V19.5V18"
|
|
262
|
-
stroke="#000000"
|
|
263
|
-
strokeWidth="0.9"
|
|
264
|
-
strokeLinecap="round"
|
|
265
|
-
strokeLinejoin="round"
|
|
266
|
-
/>
|
|
267
|
-
<Path
|
|
268
|
-
d="M18 3H21V6"
|
|
269
|
-
stroke="#000000"
|
|
270
|
-
strokeWidth="0.9"
|
|
271
|
-
strokeLinecap="round"
|
|
272
|
-
strokeLinejoin="round"
|
|
273
|
-
/>
|
|
274
|
-
<Path
|
|
275
|
-
d="M6 21H3V18"
|
|
276
|
-
stroke="#000000"
|
|
277
|
-
strokeWidth="0.9"
|
|
278
|
-
strokeLinecap="round"
|
|
279
|
-
strokeLinejoin="round"
|
|
280
|
-
/>
|
|
281
|
-
<Path
|
|
282
|
-
d="M18 21H21V18"
|
|
283
|
-
stroke="#000000"
|
|
284
|
-
strokeWidth="0.9"
|
|
285
|
-
strokeLinecap="round"
|
|
286
|
-
strokeLinejoin="round"
|
|
287
|
-
/>
|
|
288
|
-
</Svg>
|
|
289
|
-
);
|
|
290
141
|
|
|
291
142
|
const BluetoothIcon = ({ color = '#111', size = 22 }) => (
|
|
292
143
|
<Svg
|
|
@@ -464,15 +315,11 @@ const SendScreen = ({
|
|
|
464
315
|
onError,
|
|
465
316
|
}: Props) => {
|
|
466
317
|
const [channel, setChannel] = useState<Channel>('TRANSFER');
|
|
467
|
-
const [transactionAmount, setTransactionAmount] = useState<string>('');
|
|
468
318
|
const [loading, setLoading] = useState<boolean>(false);
|
|
469
|
-
const [
|
|
470
|
-
const [
|
|
471
|
-
useState<boolean>(false);
|
|
319
|
+
const [showConfirmationModal, setShowConfirmationModal] = useState<boolean>(false);
|
|
320
|
+
const [completedTx, setCompletedTx] = useState<FPTransaction | null>(null);
|
|
472
321
|
|
|
473
|
-
const [transactionPayload, setTransactionPayload] = useState<
|
|
474
|
-
FPSendPaymentRequest | FPSendWalletPaymentRequest
|
|
475
|
-
>({} as FPSendPaymentRequest | FPSendWalletPaymentRequest);
|
|
322
|
+
const [transactionPayload, setTransactionPayload] = useState< FPSendPaymentRequest | FPSendWalletPaymentRequest>({} as FPSendPaymentRequest | FPSendWalletPaymentRequest);
|
|
476
323
|
|
|
477
324
|
const formatted = `${currency} ${amount.toLocaleString('en-NG', { minimumFractionDigits: 2 })}`;
|
|
478
325
|
|
|
@@ -488,26 +335,31 @@ const SendScreen = ({
|
|
|
488
335
|
setShowConfirmationModal(true);
|
|
489
336
|
};
|
|
490
337
|
|
|
338
|
+
const handleResultClose = () => {
|
|
339
|
+
setCompletedTx(null);
|
|
340
|
+
onClose(); // close the entire shell
|
|
341
|
+
};
|
|
342
|
+
|
|
491
343
|
const handleProcessingTransaction = async (temptId: string) => {
|
|
492
344
|
console.log('Got in here for processing');
|
|
493
345
|
try {
|
|
494
346
|
setLoading(true);
|
|
495
347
|
setShowConfirmationModal(false);
|
|
496
|
-
let response:
|
|
348
|
+
let response: any = null;
|
|
497
349
|
switch (transactionPayload?.channel) {
|
|
498
350
|
case 'transfer':
|
|
499
351
|
case 'bluetooth':
|
|
500
352
|
case 'proximity':
|
|
501
353
|
if ('isBank' in transactionPayload && transactionPayload.isBank) {
|
|
502
|
-
response = await transferAPI.sendToBank(
|
|
354
|
+
response = (await transferAPI.sendToBank(
|
|
503
355
|
transactionPayload as FPSendPaymentRequest,
|
|
504
356
|
temptId
|
|
505
|
-
);
|
|
357
|
+
)) || null;
|
|
506
358
|
} else {
|
|
507
|
-
response = await transferAPI.sendToWallet(
|
|
359
|
+
response = (await transferAPI.sendToWallet(
|
|
508
360
|
transactionPayload as FPSendWalletPaymentRequest,
|
|
509
361
|
temptId
|
|
510
|
-
);
|
|
362
|
+
)) || null;
|
|
511
363
|
}
|
|
512
364
|
break;
|
|
513
365
|
case 'nqr':
|
|
@@ -534,15 +386,19 @@ const SendScreen = ({
|
|
|
534
386
|
onError?.({ message: 'Invalid channel' } as FPError);
|
|
535
387
|
break;
|
|
536
388
|
}
|
|
537
|
-
|
|
538
|
-
if (response?.status) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
389
|
+
|
|
390
|
+
if (!response?.status) {
|
|
391
|
+
// Show failed result screen
|
|
392
|
+
setCompletedTx({
|
|
393
|
+
...(response?.payload ?? {}),
|
|
394
|
+
status: 'failed',
|
|
395
|
+
} as FPTransaction);
|
|
396
|
+
return; // ← don't call onError — let result screen handle it
|
|
543
397
|
}
|
|
544
398
|
|
|
545
|
-
|
|
399
|
+
// Success — show result screen, notify host app
|
|
400
|
+
setCompletedTx(response.payload as FPTransaction);
|
|
401
|
+
onPaymentSent?.(response.payload as FPTransaction);
|
|
546
402
|
} catch (error) {
|
|
547
403
|
onError?.(error as FPError);
|
|
548
404
|
} finally {
|
|
@@ -550,6 +406,16 @@ const SendScreen = ({
|
|
|
550
406
|
}
|
|
551
407
|
};
|
|
552
408
|
|
|
409
|
+
if (completedTx) {
|
|
410
|
+
console.log("Completed Tx: ", completedTx)
|
|
411
|
+
return (
|
|
412
|
+
<ResultScreen
|
|
413
|
+
transaction={{...transactionPayload, ...completedTx}}
|
|
414
|
+
onClose={handleResultClose}
|
|
415
|
+
/>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
553
419
|
return (
|
|
554
420
|
<Container>
|
|
555
421
|
{loading && <LoadingAnimation />}
|