react-native-fpay 0.4.20 → 0.4.21

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 (62) hide show
  1. package/lib/module/FountainPayProvider.js +50 -126
  2. package/lib/module/FountainPayProvider.js.map +1 -1
  3. package/lib/module/core/api/client.js +1 -1
  4. package/lib/module/core/api/index.js +16 -13
  5. package/lib/module/core/api/index.js.map +1 -1
  6. package/lib/module/core/types/index.js +1 -1
  7. package/lib/module/core/types/index.js.map +1 -1
  8. package/lib/module/engine/FPEngine.js +27 -17
  9. package/lib/module/engine/FPEngine.js.map +1 -1
  10. package/lib/module/store/FPStore.js +11 -4
  11. package/lib/module/store/FPStore.js.map +1 -1
  12. package/lib/module/ui/components/ConfirmScreen.js +2 -2
  13. package/lib/module/ui/components/ConfirmScreen.js.map +1 -1
  14. package/lib/module/ui/modals/FPCreatePin.js +234 -0
  15. package/lib/module/ui/modals/FPCreatePin.js.map +1 -0
  16. package/lib/module/ui/modals/FPShell.js +7 -3
  17. package/lib/module/ui/modals/FPShell.js.map +1 -1
  18. package/lib/module/ui/modals/FPValidateBvn.js +258 -0
  19. package/lib/module/ui/modals/FPValidateBvn.js.map +1 -0
  20. package/lib/module/ui/screens/ResultScreen.js +4 -17
  21. package/lib/module/ui/screens/ResultScreen.js.map +1 -1
  22. package/lib/module/ui/screens/SendScreen.js +24 -9
  23. package/lib/module/ui/screens/SendScreen.js.map +1 -1
  24. package/lib/module/ui/screens/sub/sendPayment/ProximitySubScreen.js +3 -3
  25. package/lib/module/ui/screens/sub/sendPayment/ProximitySubScreen.js.map +1 -1
  26. package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js +8 -1
  27. package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js.map +1 -1
  28. package/lib/typescript/src/FountainPayProvider.d.ts +3 -4
  29. package/lib/typescript/src/FountainPayProvider.d.ts.map +1 -1
  30. package/lib/typescript/src/core/api/index.d.ts +20 -21
  31. package/lib/typescript/src/core/api/index.d.ts.map +1 -1
  32. package/lib/typescript/src/core/types/index.d.ts +16 -6
  33. package/lib/typescript/src/core/types/index.d.ts.map +1 -1
  34. package/lib/typescript/src/engine/FPEngine.d.ts +3 -0
  35. package/lib/typescript/src/engine/FPEngine.d.ts.map +1 -1
  36. package/lib/typescript/src/store/FPStore.d.ts +8 -5
  37. package/lib/typescript/src/store/FPStore.d.ts.map +1 -1
  38. package/lib/typescript/src/ui/components/ConfirmScreen.d.ts +1 -2
  39. package/lib/typescript/src/ui/components/ConfirmScreen.d.ts.map +1 -1
  40. package/lib/typescript/src/ui/modals/FPCreatePin.d.ts +3 -0
  41. package/lib/typescript/src/ui/modals/FPCreatePin.d.ts.map +1 -0
  42. package/lib/typescript/src/ui/modals/FPShell.d.ts.map +1 -1
  43. package/lib/typescript/src/ui/modals/FPValidateBvn.d.ts +3 -0
  44. package/lib/typescript/src/ui/modals/FPValidateBvn.d.ts.map +1 -0
  45. package/lib/typescript/src/ui/screens/ResultScreen.d.ts.map +1 -1
  46. package/lib/typescript/src/ui/screens/SendScreen.d.ts.map +1 -1
  47. package/lib/typescript/src/ui/screens/sub/sendPayment/TransferSubScreen.d.ts.map +1 -1
  48. package/package.json +1 -1
  49. package/src/FountainPayProvider.tsx +59 -149
  50. package/src/core/api/client.ts +1 -1
  51. package/src/core/api/index.ts +54 -54
  52. package/src/core/types/index.ts +18 -5
  53. package/src/engine/FPEngine.ts +33 -20
  54. package/src/store/FPStore.ts +15 -8
  55. package/src/ui/components/ConfirmScreen.tsx +4 -3
  56. package/src/ui/modals/FPCreatePin.tsx +206 -0
  57. package/src/ui/modals/FPShell.tsx +16 -2
  58. package/src/ui/modals/FPValidateBvn.tsx +229 -0
  59. package/src/ui/screens/ResultScreen.tsx +6 -10
  60. package/src/ui/screens/SendScreen.tsx +60 -36
  61. package/src/ui/screens/sub/sendPayment/ProximitySubScreen.tsx +3 -3
  62. package/src/ui/screens/sub/sendPayment/TransferSubScreen.tsx +7 -1
@@ -30,7 +30,8 @@ export interface FPStoreState {
30
30
  tokenExpiresAt: number | null; // unix ms — null means no expiry info
31
31
 
32
32
  // User set via initializeSDK()
33
- user: FPUserInfo | null;
33
+ user: any | null;
34
+ tempId: string;
34
35
 
35
36
  // Account details set via setAccountDetails()
36
37
  accountDetails: FPTransferRecipient | null;
@@ -50,7 +51,7 @@ export interface FPStoreState {
50
51
 
51
52
  lastBroadcastPosition: { latitude: number; longitude: number } | null;
52
53
 
53
- apiKey: string | null;
54
+ appId: string | null;
54
55
  }
55
56
 
56
57
  // ── Actions shape ─────────────────────────────────────────────────────────────
@@ -59,8 +60,11 @@ export interface FPStoreActions {
59
60
  setAccessToken: (token: string, expiresInSeconds?: number) => void;
60
61
  clearAccessToken: () => void;
61
62
 
62
- setUser: (user: FPUserInfo) => void;
63
+ setUser: (user: any) => void;
63
64
  clearUser: () => void;
65
+
66
+ setTempId: (temp: string) => void;
67
+ clearTempId: () => void;
64
68
 
65
69
  setAccountDetails: (account: FPTransferRecipient) => void;
66
70
  setBalance: (payload: FPBalance) => void;
@@ -80,7 +84,7 @@ export interface FPStoreActions {
80
84
  // Full reset — call on logout
81
85
  reset: () => void;
82
86
 
83
- setApiKey: (key: string) => void;
87
+ setAppId: (id: string) => void;
84
88
  }
85
89
 
86
90
  export type FPStore = FPStoreState & FPStoreActions;
@@ -101,7 +105,8 @@ const initialState: FPStoreState = {
101
105
  psspId: null,
102
106
  account: null,
103
107
  lastBroadcastPosition: null,
104
- apiKey: null,
108
+ appId: null,
109
+ tempId: ''
105
110
  };
106
111
 
107
112
  // ── Store ─────────────────────────────────────────────────────────────────────
@@ -126,9 +131,11 @@ export const useFPStore = create<FPStore>()(
126
131
  }),
127
132
 
128
133
  setUser: (user) => set({ user }),
129
-
130
134
  clearUser: () => set({ user: null }),
131
135
 
136
+ setTempId: (temp) => set({ tempId: temp }),
137
+ clearTempId: () => set({ tempId: "" }),
138
+
132
139
  setAccountDetails: (accountDetails) => set({ accountDetails }),
133
140
  setBalance: (payload) => set({ balance: payload, balanceFetchedAt: Date.now() }),
134
141
  clearBalance: () => set({ balance: null, balanceFetchedAt: null }),
@@ -162,7 +169,7 @@ export const useFPStore = create<FPStore>()(
162
169
 
163
170
  reset: () => set(initialState),
164
171
 
165
- setApiKey: (key: string) => set({ apiKey: key }),
172
+ setAppId: (id: string) => set({ appId: id }),
166
173
  }),
167
174
  {
168
175
  name: '@fp_store', // AsyncStorage key
@@ -180,7 +187,7 @@ export const useFPStore = create<FPStore>()(
180
187
  psspId: state.psspId,
181
188
  account: state.account,
182
189
  balance: state.balance,
183
- apiKey: state.apiKey,
190
+ appId: state.appId,
184
191
  balanceFetchedAt: state.balanceFetchedAt,
185
192
  lastBroadcastPosition: state.lastBroadcastPosition,
186
193
  // isAuthenticated and authError are NOT persisted —
@@ -15,6 +15,7 @@ import styled from 'styled-components/native';
15
15
  import OTPInputs from './OtpInput';
16
16
  import InLoading from './LoadingAnimation/InLoading';
17
17
  import type {
18
+ FPInternalTransferRecipient,
18
19
  FPSendPaymentRequest,
19
20
  FPSendWalletPaymentRequest,
20
21
  FPTransferRecipient,
@@ -181,7 +182,7 @@ const ProcessingRow = styled.View`
181
182
 
182
183
  interface TransferReminderScreenProps {
183
184
  userId: string;
184
- transaction: FPSendPaymentRequest | FPSendWalletPaymentRequest | null;
185
+ transaction: any;
185
186
  onContinue: (tempId: string) => void;
186
187
  }
187
188
 
@@ -266,13 +267,13 @@ const ConfirmScreen: React.FC<TransferReminderScreenProps> = ({
266
267
  const recipientName = transaction?.recipient.accountName ?? '';
267
268
  const accountNumber = transaction?.recipient.accountNumber ?? '';
268
269
  const bankName = !isWalletTransfer
269
- ? (transaction?.recipient as FPTransferRecipient).bankName
270
+ ? (transaction?.recipient as any)?.bankName
270
271
  : undefined;
271
272
 
272
273
  const handleContinue = async () => {
273
274
  setProcessing(true);
274
275
  try {
275
- const response: any = await transferAPI.validateTransfer(otp, userId);
276
+ const response: any = await transferAPI.validateTransfer(otp, userId, transaction?.recipient.id);
276
277
  console.log('Validate: ', response);
277
278
  if (!response.status) {
278
279
  Vibration.vibrate(100);
@@ -0,0 +1,206 @@
1
+ import React from "react";
2
+ import { useState } from "react";
3
+ import { Animated, Modal, StyleSheet, Text, TouchableOpacity, View } from "react-native"
4
+
5
+ import { C, R, S, F, shadow } from '../theme';
6
+ import OTPInputs from "../components/OtpInput";
7
+ import styled from "styled-components/native";
8
+ import InLoading from "../components/LoadingAnimation/InLoading";
9
+ import { FPEngine } from "../../engine/FPEngine";
10
+
11
+
12
+
13
+
14
+ const ProcessingRow = styled.View`
15
+ width: 100%;
16
+ display: flex;
17
+ justify-content: center;
18
+ align-items: center;
19
+ `;
20
+
21
+ const ButtonRow = styled(View)`
22
+ flex-direction: row;
23
+ gap: 12px;
24
+ margin-top: 32px;
25
+ `;
26
+
27
+ const ContinueButton = styled(TouchableOpacity)`
28
+ flex: 1;
29
+ background-color: #003333;
30
+ border-radius: 50px;
31
+ padding-vertical: 17px;
32
+ align-items: center;
33
+ justify-content: center;
34
+ shadow-color: #fff;
35
+ shadow-offset: 0px 4px;
36
+ shadow-opacity: 0.3;
37
+ shadow-radius: 10px;
38
+ elevation: 6;
39
+ `;
40
+
41
+ const ContinueButtonText = styled(Text)`
42
+ color: #ffffff;
43
+ font-size: 16px;
44
+ font-weight: 700;
45
+ letter-spacing: -0.2px;
46
+ `;
47
+
48
+ const SubText = styled(Text)`
49
+ width: 90%;
50
+ font-size: 11px;
51
+ color: #666;
52
+ `;
53
+
54
+ const Container = styled(View)`
55
+ width: 100%;
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ gap: 10px;
60
+ `
61
+
62
+
63
+ const FPCreatePin = () => {
64
+ const [processing, setProcessing] = useState(false);
65
+ const [otp, setOtp] = useState<string>('');
66
+ const scale = React.useRef(new Animated.Value(0.85)).current;
67
+ const buttonAnim = React.useRef(new Animated.Value(0)).current;
68
+ const [error, setError] = useState("")
69
+
70
+
71
+ const handleCreatePin = async() => {
72
+ setError("");
73
+ setProcessing(true);
74
+ try{
75
+ await FPEngine.createUserTransactionPin({pin: otp});
76
+ } catch (error: any) {
77
+ console.error('Error creating PIN:', error);
78
+ setError(error.message || "Error creating PIN")
79
+ } finally {
80
+ setProcessing(false);
81
+ }
82
+ }
83
+
84
+
85
+ React.useEffect(() => {
86
+ Animated.sequence([
87
+ Animated.timing(buttonAnim, {
88
+ toValue: 1,
89
+ duration: 300,
90
+ useNativeDriver: true,
91
+ }),
92
+ ]).start();
93
+ }, []);
94
+
95
+ return (
96
+ <Modal
97
+ visible
98
+ transparent
99
+ animationType="slide"
100
+ statusBarTranslucent
101
+ >
102
+ <View
103
+ style={{
104
+ flex: 1,
105
+ backgroundColor: 'rgba(0,0,0,0.5)',
106
+ justifyContent: 'center',
107
+ alignItems: 'center',
108
+ }}
109
+ >
110
+ <Animated.View style={{ transform: [{ scale }] }}>
111
+ <View
112
+ style={[st.card, {
113
+ borderRadius: 20,
114
+ padding: 30,
115
+ }]}
116
+ >
117
+ <Container>
118
+ <Text style={{ color: C.brand, fontSize: 16, fontWeight: '600', marginBottom: 16 }}>
119
+ Create PIN
120
+ </Text>
121
+ <OTPInputs count={4} callBack={setOtp} />
122
+ <SubText>Create your preferred pin to authorize transactions</SubText>
123
+ {error && <Text style={{ color: 'red', fontSize: 12, marginTop: 6 }}>{error}</Text>}
124
+ </Container>
125
+
126
+ {processing ? (
127
+ <ProcessingRow>
128
+ <ButtonRow>
129
+ <InLoading />
130
+ </ButtonRow>
131
+ </ProcessingRow>
132
+ ) : (
133
+ <ButtonRow>
134
+ <ContinueButton
135
+ disabled={!otp}
136
+ onPress={handleCreatePin}
137
+ activeOpacity={0.85}
138
+ >
139
+ <ContinueButtonText>Create Pin</ContinueButtonText>
140
+ </ContinueButton>
141
+ </ButtonRow>
142
+ )}
143
+ </View>
144
+ </Animated.View>
145
+ </View>
146
+ </Modal>
147
+ );
148
+ };
149
+
150
+ const st = StyleSheet.create({
151
+ scrim: {
152
+ flex: 1,
153
+ backgroundColor: 'rgba(11,29,53,0.6)',
154
+ justifyContent: 'flex-end',
155
+ padding: S.lg,
156
+ paddingBottom: 48,
157
+ },
158
+ card: {
159
+ backgroundColor: C.white,
160
+ borderRadius: 28,
161
+ padding: S.xl,
162
+ alignItems: 'center',
163
+ ...shadow.lg,
164
+ },
165
+ avatar: {
166
+ width: 72,
167
+ height: 72,
168
+ borderRadius: 36,
169
+ backgroundColor: C.brandLight,
170
+ justifyContent: 'center',
171
+ alignItems: 'center',
172
+ marginBottom: S.md,
173
+ },
174
+ avatarText: { fontSize: 28, fontWeight: '800', color: C.brand },
175
+ headline: {
176
+ fontSize: F.sm,
177
+ fontWeight: '700',
178
+ color: C.muted,
179
+ letterSpacing: 0.8,
180
+ textTransform: 'uppercase',
181
+ marginBottom: 4,
182
+ },
183
+ sender: { fontSize: F.xl, fontWeight: '800', color: C.ink },
184
+ sub: { fontSize: F.md, color: C.muted, marginBottom: S.sm },
185
+ amount: { fontSize: 40, fontWeight: '800', color: C.ink, marginBottom: S.md },
186
+ noteBox: {
187
+ backgroundColor: C.surface,
188
+ borderRadius: R.md,
189
+ padding: S.md,
190
+ width: '100%',
191
+ marginBottom: S.md,
192
+ },
193
+ noteLabel: { fontSize: F.xs, color: C.muted, marginBottom: 2 },
194
+ note: { fontSize: F.md, color: C.ink },
195
+ disclaimer: {
196
+ fontSize: F.sm,
197
+ color: C.muted,
198
+ textAlign: 'center',
199
+ lineHeight: 20,
200
+ marginBottom: S.lg,
201
+ },
202
+ btnRow: { flexDirection: 'row', gap: S.sm, width: '100%' },
203
+ declineBtn: { flex: 1 },
204
+ acceptBtn: { flex: 1 },
205
+ });
206
+ export default FPCreatePin;
@@ -16,10 +16,13 @@ import {
16
16
  import { _onEvent } from '../../engine/FPEngine';
17
17
  import SendScreen from '../screens/SendScreen';
18
18
  import ReceiveScreen from '../screens/ReceiveScreen';
19
+ import FPValidateBvn from '../modals/FPValidateBvn';
20
+ import FPCreatePin from '../modals/FPCreatePin';
19
21
  import { FPPaymentRequestModal } from './FPPaymentRequestModal';
20
22
  import { FPEngine } from '../../engine/FPEngine';
21
23
  import { C, S, F } from '../theme';
22
24
  import type { FPCurrency, FPUserInfo } from '../../core/types';
25
+ import { useFPStore } from '../../store/FPStore';
23
26
 
24
27
  const { width: SCREEN_W, height: SCREEN_H } = Dimensions.get('window');
25
28
 
@@ -37,6 +40,8 @@ export function FPShell() {
37
40
  const [visible, setVisible] = useState(false);
38
41
  const slide = useRef(new Animated.Value(SCREEN_H)).current;
39
42
 
43
+ const user = useFPStore(s => s.user);
44
+
40
45
  const open = (state: SheetState) => {
41
46
  setSheet(state);
42
47
  setVisible(true);
@@ -89,6 +94,15 @@ export function FPShell() {
89
94
  return (
90
95
  <>
91
96
  {/* Always-on BT payment request modal */}
97
+ {user && user?.verifyBvn === false && (
98
+ <FPValidateBvn />
99
+ )}
100
+
101
+ {/* PIN creation — shown after BVN verified but no PIN yet */}
102
+ {user && user.verifyBvn === true && user.hasPin === false && (
103
+ <FPCreatePin />
104
+ )}
105
+
92
106
  <FPPaymentRequestModal />
93
107
 
94
108
  {visible && (
@@ -98,7 +112,7 @@ export function FPShell() {
98
112
  <View style={st.content}>
99
113
  {sheet.mode === 'send' && (
100
114
  <SendScreen
101
- user={FPEngine.getUser() ?? null}
115
+ user={user ?? null}
102
116
  account={FPEngine.getAccount() ?? null}
103
117
  amount={sheet.amount ?? 0}
104
118
  currency={sheet.currency ?? 'NGN'}
@@ -113,7 +127,7 @@ export function FPShell() {
113
127
  <ReceiveScreen
114
128
  amount={sheet.amount}
115
129
  currency={sheet.currency}
116
- user={FPEngine.getUser() ?? null}
130
+ user={user ?? null}
117
131
  account={FPEngine.getAccount() ?? null}
118
132
  onClose={close}
119
133
  onPaymentReceived={FPEngine.getCallbacks().onPaymentReceived}
@@ -0,0 +1,229 @@
1
+ import React from "react";
2
+ import { useState } from "react";
3
+ import { Animated, Modal, StyleSheet, Text, TouchableOpacity, View } from "react-native"
4
+
5
+ import { C, R, S, F, shadow } from '../theme';
6
+ import OTPInputs from "../components/OtpInput";
7
+ import styled from "styled-components/native";
8
+ import InLoading from "../components/LoadingAnimation/InLoading";
9
+ import { FPEngine } from "../../engine/FPEngine";
10
+ import { getFPStore } from "../../store/FPStore";
11
+
12
+
13
+
14
+ const ProcessingRow = styled.View`
15
+ width: 100%;
16
+ display: flex;
17
+ justify-content: center;
18
+ align-items: center;
19
+ `;
20
+
21
+ const ButtonRow = styled(View)`
22
+ flex-direction: row;
23
+ gap: 12px;
24
+ margin-top: 32px;
25
+ `;
26
+
27
+ const ContinueButton = styled(TouchableOpacity)`
28
+ flex: 1;
29
+ background-color: #003333;
30
+ border-radius: 50px;
31
+ padding-vertical: 17px;
32
+ align-items: center;
33
+ justify-content: center;
34
+ shadow-color: #fff;
35
+ shadow-offset: 0px 4px;
36
+ shadow-opacity: 0.3;
37
+ shadow-radius: 10px;
38
+ elevation: 6;
39
+ `;
40
+
41
+ const ContinueButtonText = styled(Text)`
42
+ color: #ffffff;
43
+ font-size: 16px;
44
+ font-weight: 700;
45
+ letter-spacing: -0.2px;
46
+ `;
47
+
48
+ const SubText = styled(Text)`
49
+ width: 90%;
50
+ font-size: 11px;
51
+ color: #666;
52
+ `;
53
+
54
+ const Container = styled(View)`
55
+ width: 100%;
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ gap: 10px;
60
+ `
61
+
62
+ const FPValidateBvn = () => {
63
+ const [processing, setProcessing] = useState(false);
64
+ const scale = React.useRef(new Animated.Value(0.85)).current;
65
+ const buttonAnim = React.useRef(new Animated.Value(0)).current;
66
+ const [otp, setOtp] = useState<string>('');
67
+ const [error, setError] = useState("")
68
+
69
+
70
+ const resendSms = async()=>{
71
+ setError("");
72
+ setProcessing(true);
73
+ try{
74
+ // TODO: Implement BVN validation logic here
75
+ await FPEngine.verifyBvn();
76
+ } catch (error: any) {
77
+ console.error('Error validating BVN:', error);
78
+ setError(error.message || "BVN not found")
79
+ } finally {
80
+ setProcessing(false);
81
+ }
82
+ }
83
+
84
+ const handleValidateBvn = async() => {
85
+ setProcessing(true);
86
+ setError("");
87
+ try{
88
+ // TODO: Implement BVN validation logic here
89
+ const values: any = {
90
+ otp,
91
+ temp_id: getFPStore().tempId
92
+ };
93
+ await FPEngine.verifySmsOtp(values);
94
+ } catch (error: any) {
95
+ console.error('Error validating BVN:', error);
96
+ setError(error.message || "Invalid token")
97
+ } finally {
98
+ setProcessing(false);
99
+ }
100
+ }
101
+
102
+ React.useEffect(() => {
103
+ resendSms();
104
+ Animated.sequence([
105
+ Animated.timing(buttonAnim, {
106
+ toValue: 1,
107
+ duration: 300,
108
+ useNativeDriver: true,
109
+ }),
110
+ ]).start();
111
+ }, []);
112
+ return (
113
+ <Modal
114
+ visible
115
+ transparent
116
+ animationType="slide"
117
+ statusBarTranslucent
118
+ >
119
+ <View
120
+ style={{
121
+ flex: 1,
122
+ backgroundColor: 'rgba(0,0,0,0.5)',
123
+ justifyContent: 'center',
124
+ alignItems: 'center',
125
+ }}
126
+ >
127
+ <Animated.View style={{ transform: [{ scale }] }}>
128
+ <View
129
+ style={[st.card, {
130
+ borderRadius: 20,
131
+ padding: 30,
132
+ }]}
133
+ >
134
+
135
+ <Container>
136
+ <Text style={{ color: C.brand, fontSize: 16, fontWeight: '600', marginBottom: 6 }}>
137
+ Validate your BVN
138
+ </Text>
139
+ <OTPInputs count={4} callBack={setOtp} />
140
+ <SubText>SMS has been sent to the phone number tied to your BVN</SubText>
141
+ <TouchableOpacity onPress={resendSms}>
142
+ <Text>Resend token</Text>
143
+ </TouchableOpacity>
144
+ {error && <Text style={{ color: 'red', fontSize: 12, marginTop: 6 }}>{error}</Text>}
145
+ </Container>
146
+
147
+
148
+ {processing ? (
149
+ <ProcessingRow>
150
+ <ButtonRow>
151
+ <InLoading />
152
+ </ButtonRow>
153
+ </ProcessingRow>
154
+ ) : (
155
+ <ButtonRow>
156
+ <ContinueButton
157
+ disabled={!otp}
158
+ onPress={handleValidateBvn}
159
+ activeOpacity={0.85}
160
+ >
161
+ <ContinueButtonText>Verify Me</ContinueButtonText>
162
+ </ContinueButton>
163
+ </ButtonRow>
164
+ )}
165
+ </View>
166
+ </Animated.View>
167
+ </View>
168
+ </Modal>
169
+ );
170
+ };
171
+
172
+ const st = StyleSheet.create({
173
+ scrim: {
174
+ flex: 1,
175
+ backgroundColor: 'rgba(11,29,53,0.6)',
176
+ justifyContent: 'flex-end',
177
+ padding: S.lg,
178
+ paddingBottom: 48,
179
+ },
180
+ card: {
181
+ width: '70%',
182
+ backgroundColor: C.white,
183
+ borderRadius: 28,
184
+ padding: S.xl,
185
+ alignItems: 'center',
186
+ ...shadow.lg,
187
+ },
188
+ avatar: {
189
+ width: 72,
190
+ height: 72,
191
+ borderRadius: 36,
192
+ backgroundColor: C.brandLight,
193
+ justifyContent: 'center',
194
+ alignItems: 'center',
195
+ marginBottom: S.md,
196
+ },
197
+ avatarText: { fontSize: 28, fontWeight: '800', color: C.brand },
198
+ headline: {
199
+ fontSize: F.sm,
200
+ fontWeight: '700',
201
+ color: C.muted,
202
+ letterSpacing: 0.8,
203
+ textTransform: 'uppercase',
204
+ marginBottom: 4,
205
+ },
206
+ sender: { fontSize: F.xl, fontWeight: '800', color: C.ink },
207
+ sub: { fontSize: F.md, color: C.muted, marginBottom: S.sm },
208
+ amount: { fontSize: 40, fontWeight: '800', color: C.ink, marginBottom: S.md },
209
+ noteBox: {
210
+ backgroundColor: C.surface,
211
+ borderRadius: R.md,
212
+ padding: S.md,
213
+ width: '100%',
214
+ marginBottom: S.md,
215
+ },
216
+ noteLabel: { fontSize: F.xs, color: C.muted, marginBottom: 2 },
217
+ note: { fontSize: F.md, color: C.ink },
218
+ disclaimer: {
219
+ fontSize: F.sm,
220
+ color: C.muted,
221
+ textAlign: 'center',
222
+ lineHeight: 20,
223
+ marginBottom: S.lg,
224
+ },
225
+ btnRow: { flexDirection: 'row', gap: S.sm, width: '100%' },
226
+ declineBtn: { flex: 1 },
227
+ acceptBtn: { flex: 1 },
228
+ });
229
+ export default FPValidateBvn;
@@ -206,9 +206,9 @@ interface Props {
206
206
  export function ResultScreen({ transaction, onClose }: Props) {
207
207
  const [loading, setLoading] = useState<boolean>(false);
208
208
  const [transactionDetail, setTransactionDetail] = useState<any>(null)
209
- const slideAnim = useRef(new Animated.Value(60)).current;
210
- const opacAnim = useRef(new Animated.Value(0)).current;
211
- const scaleAnim = useRef(new Animated.Value(0.5)).current;
209
+ const opacAnim = useRef(new Animated.Value(1)).current; // ← start visible
210
+ const slideAnim = useRef(new Animated.Value(0)).current; // ← start in place
211
+ const scaleAnim = useRef(new Animated.Value(1)).current; // ← start full size
212
212
 
213
213
  const isSuccess = transactionDetail?.status.toLowerCase() === 'successful';
214
214
  const accentColor = isSuccess ? C.green : C.red;
@@ -250,19 +250,14 @@ export function ResultScreen({ transaction, onClose }: Props) {
250
250
 
251
251
  }
252
252
 
253
- useEffect(() => {
254
- Animated.parallel([
255
- Animated.spring(scaleAnim, { toValue: 1, useNativeDriver: true }),
256
- Animated.timing(opacAnim, { toValue: 1, duration: 300, useNativeDriver: true }),
257
- Animated.timing(slideAnim, { toValue: 0, duration: 350, useNativeDriver: true }),
258
- ]).start();
259
- }, [scaleAnim, opacAnim, slideAnim]);
253
+
260
254
 
261
255
  useEffect(() => {
262
256
  const timer = setTimeout(onClose, COUNTDOWN_SECONDS * 1000);
263
257
  return () => clearTimeout(timer);
264
258
  }, [onClose]);
265
259
 
260
+
266
261
  useEffect(()=>{
267
262
  if(transaction){
268
263
  loadTransaction();
@@ -274,6 +269,7 @@ export function ResultScreen({ transaction, onClose }: Props) {
274
269
  return <Gradients />;
275
270
  }
276
271
 
272
+
277
273
  return (
278
274
  <Container style={{ opacity: opacAnim, transform: [{ translateY: slideAnim }] }}>
279
275