@finspringinnovations/fdsdk 0.0.1 → 0.0.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/components/CompanyHeader.js +40 -36
- package/lib/components/InterestRateCard.js +3 -2
- package/lib/components/PaymentDetailsCard.js +8 -7
- package/lib/components/PendingFDBottomSheet.js +2 -2
- package/lib/components/TextFieldWithLabel.js +1 -1
- package/lib/components/TrustBox.js +2 -2
- package/lib/config/appDataConfig.d.ts +53 -2
- package/lib/config/appDataConfig.js +39 -6
- package/lib/hooks/usePaymentSSE.d.ts +24 -0
- package/lib/hooks/usePaymentSSE.js +135 -0
- package/lib/hooks/usePaymentStatusTimer.d.ts +25 -0
- package/lib/hooks/usePaymentStatusTimer.js +122 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +5 -2
- package/lib/navigation/RootNavigator.js +6 -1
- package/lib/navigation/index.d.ts +2 -0
- package/lib/navigation/index.js +13 -11
- package/lib/screens/FDCalculator.d.ts +1 -0
- package/lib/screens/FDCalculator.js +64 -48
- package/lib/screens/FDList.js +10 -12
- package/lib/screens/NomineeDetail.js +21 -15
- package/lib/screens/PayNow.js +6 -6
- package/lib/screens/Payment.d.ts +1 -0
- package/lib/screens/Payment.js +34 -13
- package/lib/screens/PaymentStatus.js +33 -42
- package/lib/theme/ThemeContext.d.ts +2 -0
- package/lib/theme/ThemeContext.js +4 -2
- package/lib/theme/index.d.ts +6 -1
- package/lib/theme/index.js +24 -8
- package/lib/utils/sseParser.d.ts +7 -0
- package/lib/utils/sseParser.js +27 -0
- package/package.json +2 -2
- package/src/components/CompanyHeader.tsx +50 -44
- package/src/components/InterestRateCard.tsx +3 -2
- package/src/components/PaymentDetailsCard.tsx +45 -40
- package/src/components/PendingFDBottomSheet.tsx +2 -2
- package/src/components/TextFieldWithLabel.tsx +1 -1
- package/src/components/TrustBox.tsx +2 -2
- package/src/config/appDataConfig.ts +70 -5
- package/src/hooks/usePaymentSSE.ts +155 -0
- package/src/hooks/usePaymentStatusTimer.ts +169 -0
- package/src/index.tsx +4 -1
- package/src/navigation/RootNavigator.tsx +7 -0
- package/src/navigation/index.tsx +16 -17
- package/src/screens/FDCalculator.tsx +64 -40
- package/src/screens/FDList.tsx +11 -11
- package/src/screens/NomineeDetail.tsx +20 -14
- package/src/screens/PayNow.tsx +7 -7
- package/src/screens/Payment.tsx +45 -14
- package/src/screens/PaymentStatus.tsx +44 -57
- package/src/theme/ThemeContext.tsx +6 -1
- package/src/theme/index.ts +30 -8
- package/src/utils/sseParser.ts +40 -0
|
@@ -6,7 +6,7 @@ import SafeAreaWrapper from '../components/SafeAreaWrapper';
|
|
|
6
6
|
import { PaymentDetailsCard } from '../components';
|
|
7
7
|
import ActionButton from '../components/ActionButton';
|
|
8
8
|
import { useColors, useTypography, useTheme } from '../theme/ThemeContext';
|
|
9
|
-
import {
|
|
9
|
+
import { usePaymentRetryMutation } from '../api/fdApi';
|
|
10
10
|
import { useGetCustomerApplicationsMutation } from '../api/customerApi';
|
|
11
11
|
import { useAppSelector } from '../store';
|
|
12
12
|
import { getUserInfoForAPI } from '../config/appDataConfig';
|
|
@@ -16,6 +16,7 @@ import { useRoute } from '@react-navigation/native';
|
|
|
16
16
|
import { BANK_STRINGS } from '../constants/strings/bank';
|
|
17
17
|
import { COMMON_STRINGS } from '../constants/strings/common';
|
|
18
18
|
import { getApiConfig } from '../config/apiConfig';
|
|
19
|
+
import { usePaymentStatusTimer } from '../hooks/usePaymentStatusTimer';
|
|
19
20
|
|
|
20
21
|
export interface PaymentStatusProps {
|
|
21
22
|
onRetry?: () => void;
|
|
@@ -45,7 +46,7 @@ const PaymentStatus: React.FC<PaymentStatusProps> = ({
|
|
|
45
46
|
// Local state to track current status (can be updated from API responses)
|
|
46
47
|
const [currentStatus, setCurrentStatus] = useState<'success' | 'failed' | 'pending'>(status);
|
|
47
48
|
|
|
48
|
-
const styles = createStyles(colors, typography, currentStatus);
|
|
49
|
+
const styles = createStyles(colors, typography, currentStatus, themeName);
|
|
49
50
|
const route = useRoute();
|
|
50
51
|
|
|
51
52
|
// Get transaction ID from navigation props (route params)
|
|
@@ -63,12 +64,23 @@ const PaymentStatus: React.FC<PaymentStatusProps> = ({
|
|
|
63
64
|
const entityId = useAppSelector((state: any) => state?.onboarding?.entityid);
|
|
64
65
|
const providerId = useAppSelector((state: any) => state?.onboarding?.providerId);
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
67
|
+
const {
|
|
68
|
+
startTimer,
|
|
69
|
+
stopTimer,
|
|
70
|
+
triggerStatusCheck,
|
|
71
|
+
isCheckingStatus,
|
|
72
|
+
} = usePaymentStatusTimer({
|
|
73
|
+
transactionId: finalTransactionId,
|
|
74
|
+
overrides: {
|
|
75
|
+
providerId,
|
|
76
|
+
workflowInstanceId,
|
|
77
|
+
applicationid: applicationId,
|
|
78
|
+
entityid: entityId,
|
|
79
|
+
},
|
|
80
|
+
onStatusUpdate: (nextStatus) => {
|
|
81
|
+
setCurrentStatus(nextStatus);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
72
84
|
|
|
73
85
|
// Payment Retry API
|
|
74
86
|
const [paymentRetry, {
|
|
@@ -105,43 +117,20 @@ const PaymentStatus: React.FC<PaymentStatusProps> = ({
|
|
|
105
117
|
return amount.toLocaleString('en-IN');
|
|
106
118
|
};
|
|
107
119
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const userInfo = getUserInfoForAPI();
|
|
115
|
-
|
|
116
|
-
// Prepare the API request
|
|
117
|
-
const paymentReverseFeedRequest = {
|
|
118
|
-
// Headers
|
|
119
|
-
providerId: providerId,
|
|
120
|
-
workflowInstanceId: workflowInstanceId,
|
|
121
|
-
userreferenceid: userInfo.id,
|
|
122
|
-
applicationid: applicationId,
|
|
123
|
-
entityid: entityId,
|
|
124
|
-
// Body
|
|
125
|
-
transactionId: finalTransactionId,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const response = await paymentReverseFeed(paymentReverseFeedRequest).unwrap();
|
|
129
|
-
|
|
130
|
-
// Handle the response based on payment status
|
|
131
|
-
if (response?.data?.paymentStatus?.toLowerCase() === 'success') {
|
|
132
|
-
setCurrentStatus('success');
|
|
133
|
-
|
|
134
|
-
} else if (response?.data?.paymentStatus?.toLowerCase() === 'failed') {
|
|
135
|
-
setCurrentStatus('failed');
|
|
136
|
-
|
|
137
|
-
}
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (currentStatus === 'pending') {
|
|
122
|
+
startTimer();
|
|
123
|
+
} else {
|
|
124
|
+
stopTimer();
|
|
125
|
+
}
|
|
138
126
|
|
|
139
|
-
|
|
127
|
+
return () => {
|
|
128
|
+
stopTimer();
|
|
129
|
+
};
|
|
130
|
+
}, [currentStatus, startTimer, stopTimer]);
|
|
140
131
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// ]);
|
|
144
|
-
}
|
|
132
|
+
const handlePaymentReverseFeed = async () => {
|
|
133
|
+
await triggerStatusCheck();
|
|
145
134
|
};
|
|
146
135
|
|
|
147
136
|
// Handle payment retry when status is failed
|
|
@@ -321,10 +310,10 @@ const PaymentStatus: React.FC<PaymentStatusProps> = ({
|
|
|
321
310
|
<SafeAreaWrapper
|
|
322
311
|
includeTop={true}
|
|
323
312
|
bottomPadding={0}
|
|
324
|
-
statusBarColor=
|
|
325
|
-
statusBarStyle=
|
|
313
|
+
statusBarColor={colors.background}
|
|
314
|
+
statusBarStyle={themeName === 'dark' ? 'light-content' : 'dark-content'}
|
|
326
315
|
>
|
|
327
|
-
{Platform.OS === 'ios' && <StatusBar barStyle=
|
|
316
|
+
{Platform.OS === 'ios' && <StatusBar barStyle={themeName === 'dark' ? 'light-content' : 'dark-content'} />}
|
|
328
317
|
|
|
329
318
|
<View style={styles.container}>
|
|
330
319
|
{/* <Text style={styles.headerTitle}>
|
|
@@ -423,22 +412,21 @@ const PaymentStatus: React.FC<PaymentStatusProps> = ({
|
|
|
423
412
|
disabled={isLoadingPaymentRetry}
|
|
424
413
|
/>
|
|
425
414
|
)}
|
|
426
|
-
{currentStatus === 'pending' && (
|
|
415
|
+
{/* {currentStatus === 'pending' && (
|
|
427
416
|
<ActionButton
|
|
428
|
-
title={
|
|
417
|
+
title={isCheckingStatus ? COMMON_STRINGS.CHECKING : BANK_STRINGS.REFRESH_STATUS_BUTTON}
|
|
429
418
|
onPress={handlePaymentReverseFeed}
|
|
430
|
-
disabled={
|
|
419
|
+
disabled={isCheckingStatus}
|
|
431
420
|
/>
|
|
432
|
-
)}
|
|
421
|
+
)} */}
|
|
433
422
|
</View>
|
|
434
423
|
</View>
|
|
435
424
|
</SafeAreaWrapper>
|
|
436
425
|
);
|
|
437
426
|
};
|
|
438
427
|
|
|
439
|
-
const createStyles = (colors: any, typography: any, status: 'success' | 'failed' | 'pending') => {
|
|
440
|
-
const
|
|
441
|
-
const cardBgColor = status === 'success' ? '#E8F5F0' : status === 'failed' ? '#FFF3F3' : '#FFF8E1';
|
|
428
|
+
const createStyles = (colors: any, typography: any, status: 'success' | 'failed' | 'pending', themeName: string) => {
|
|
429
|
+
const isDark = themeName === 'dark';
|
|
442
430
|
|
|
443
431
|
return StyleSheet.create({
|
|
444
432
|
container: {
|
|
@@ -475,7 +463,7 @@ const createStyles = (colors: any, typography: any, status: 'success' | 'failed'
|
|
|
475
463
|
width: 60,
|
|
476
464
|
height: 60,
|
|
477
465
|
borderRadius: 30,
|
|
478
|
-
backgroundColor: status === 'success' ? '#ffffff' : status === 'failed' ? 'rgba(244, 67, 54, 0.0)' : 'rgba(255, 152, 0, 0.2)',
|
|
466
|
+
backgroundColor: status === 'success' ? (isDark ? colors.surface : '#ffffff') : status === 'failed' ? 'rgba(244, 67, 54, 0.0)' : 'rgba(255, 152, 0, 0.2)',
|
|
479
467
|
alignItems: 'center',
|
|
480
468
|
justifyContent: 'center',
|
|
481
469
|
},
|
|
@@ -492,7 +480,6 @@ const createStyles = (colors: any, typography: any, status: 'success' | 'failed'
|
|
|
492
480
|
textAlign: 'center',
|
|
493
481
|
marginBottom: 30,
|
|
494
482
|
marginTop: -30,
|
|
495
|
-
|
|
496
483
|
},
|
|
497
484
|
transactionLabel: {
|
|
498
485
|
...typography.styles.bodyMedium,
|
|
@@ -516,7 +503,7 @@ const createStyles = (colors: any, typography: any, status: 'success' | 'failed'
|
|
|
516
503
|
width: 56,
|
|
517
504
|
height: 56,
|
|
518
505
|
borderRadius: 28,
|
|
519
|
-
backgroundColor:
|
|
506
|
+
backgroundColor: colors.primary,
|
|
520
507
|
alignItems: 'center',
|
|
521
508
|
justifyContent: 'center',
|
|
522
509
|
borderWidth: 4,
|
|
@@ -525,7 +512,7 @@ const createStyles = (colors: any, typography: any, status: 'success' | 'failed'
|
|
|
525
512
|
avatarText: {
|
|
526
513
|
fontSize: 24,
|
|
527
514
|
fontWeight: '600',
|
|
528
|
-
color:
|
|
515
|
+
color: colors.headerText,
|
|
529
516
|
},
|
|
530
517
|
footer: {
|
|
531
518
|
position: 'absolute',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { createContext, useContext, ReactNode } from 'react';
|
|
2
2
|
import { createTheme, type Theme, type ThemeName, defaultTheme } from './index';
|
|
3
|
+
import type { CustomColors } from '../config/appDataConfig';
|
|
3
4
|
|
|
4
5
|
interface ThemeContextType {
|
|
5
6
|
theme: Theme;
|
|
@@ -13,16 +14,20 @@ interface ThemeProviderProps {
|
|
|
13
14
|
children: ReactNode;
|
|
14
15
|
initialTheme?: ThemeName;
|
|
15
16
|
theme?: Theme; // Allow custom theme override
|
|
17
|
+
colorOverrides?: CustomColors | null; // Custom color overrides from SDK initialization
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
19
21
|
children,
|
|
20
22
|
initialTheme = 'primary',
|
|
21
23
|
theme: customTheme,
|
|
24
|
+
colorOverrides,
|
|
22
25
|
}) => {
|
|
23
26
|
const [currentThemeName, setCurrentThemeName] = React.useState<ThemeName>(initialTheme);
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
// If a full custom theme is provided, use it directly.
|
|
29
|
+
// Otherwise, create theme from name and merge any color overrides on top.
|
|
30
|
+
const theme = customTheme || createTheme(currentThemeName, colorOverrides);
|
|
26
31
|
|
|
27
32
|
const setTheme = React.useCallback((themeName: ThemeName) => {
|
|
28
33
|
setCurrentThemeName(themeName);
|
package/src/theme/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { colors, type ThemeName, type ColorScheme } from './colors';
|
|
2
2
|
import { typography } from './typography';
|
|
3
3
|
import { shadows } from './shadows';
|
|
4
|
+
import type { CustomColors } from '../config/appDataConfig';
|
|
4
5
|
|
|
5
6
|
// Spacing scale
|
|
6
7
|
export const spacing = {
|
|
@@ -32,14 +33,35 @@ export interface Theme {
|
|
|
32
33
|
borderRadius: typeof borderRadius;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Theme factory function.
|
|
38
|
+
* If customColors are provided, they override the corresponding colors from the base theme.
|
|
39
|
+
*/
|
|
40
|
+
export const createTheme = (themeName: ThemeName = 'primary', customColors?: CustomColors | null): Theme => {
|
|
41
|
+
const baseColors = colors[themeName] as ColorScheme;
|
|
42
|
+
|
|
43
|
+
// Merge custom color overrides on top of the base theme colors
|
|
44
|
+
const mergedColors = customColors
|
|
45
|
+
? { ...baseColors, ...filterDefinedValues(customColors) } as ColorScheme
|
|
46
|
+
: baseColors;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
colors: mergedColors,
|
|
50
|
+
typography,
|
|
51
|
+
shadows,
|
|
52
|
+
spacing,
|
|
53
|
+
borderRadius,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Utility to filter out undefined/null values so only explicitly set colors override the theme.
|
|
59
|
+
*/
|
|
60
|
+
const filterDefinedValues = (obj: Record<string, any>): Record<string, any> => {
|
|
61
|
+
return Object.fromEntries(
|
|
62
|
+
Object.entries(obj).filter(([_, value]) => value !== undefined && value !== null)
|
|
63
|
+
);
|
|
64
|
+
};
|
|
43
65
|
|
|
44
66
|
// Helper function to get border with opacity
|
|
45
67
|
export const getBorderColor = (theme: Theme, opacity: number = 0.36) => {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface SSEEvent {
|
|
2
|
+
eventType: string;
|
|
3
|
+
data: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function parseSSEBuffer(
|
|
7
|
+
buffer: { value: string },
|
|
8
|
+
chunk: string
|
|
9
|
+
): SSEEvent[] {
|
|
10
|
+
const results: SSEEvent[] = [];
|
|
11
|
+
|
|
12
|
+
buffer.value += chunk;
|
|
13
|
+
|
|
14
|
+
let idx: number;
|
|
15
|
+
|
|
16
|
+
while ((idx = buffer.value.indexOf('\n\n')) >= 0) {
|
|
17
|
+
|
|
18
|
+
const block = buffer.value.slice(0, idx);
|
|
19
|
+
|
|
20
|
+
buffer.value = buffer.value.slice(idx + 2);
|
|
21
|
+
|
|
22
|
+
let eventType = 'message';
|
|
23
|
+
|
|
24
|
+
const dataparts: string[] = [];
|
|
25
|
+
|
|
26
|
+
for (const line of block.split(/\r?\n/)) {
|
|
27
|
+
if (line.startsWith('event:')) {
|
|
28
|
+
eventType = line.slice(6).trim();
|
|
29
|
+
} else if (line.startsWith('data:')) {
|
|
30
|
+
dataparts.push(line.slice(5).trim());
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data = dataparts.join('\n').trim();
|
|
35
|
+
if (data) {
|
|
36
|
+
results.push({ eventType, data });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return results;
|
|
40
|
+
}
|