@airxpay/sdk-ui 1.0.6 → 1.0.8
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/dist/api/client.d.ts +6 -0
- package/dist/api/client.js +104 -0
- package/dist/api/merchant.d.ts +30 -9
- package/dist/api/merchant.js +51 -17
- package/dist/components/steps/KYCVerification.d.ts +2 -2
- package/dist/components/steps/KYCVerification.js +226 -45
- package/dist/components/steps/OnboardingComplete.d.ts +17 -12
- package/dist/components/steps/OnboardingComplete.js +266 -665
- package/dist/components/ui/MerchantOnboard/MerchantOnboarding.js +1 -1
- package/dist/contexts/AirXPayProvider.d.ts +7 -7
- package/dist/contexts/AirXPayProvider.js +41 -34
- package/dist/hooks/MerchantOnboarding.d.ts +7 -3
- package/dist/hooks/MerchantOnboarding.js +16 -2
- package/dist/index.d.ts +12 -7
- package/dist/index.js +23 -8
- package/dist/sdk/airxpay.d.ts +4 -1
- package/dist/types/merchantTypes.d.ts +58 -29
- package/dist/types/merchantTypes.js +4 -1
- package/dist/types/merchantTypes.ts +66 -30
- package/dist/types/type.ts +0 -1
- package/dist/utils/jwt.d.ts +14 -0
- package/dist/utils/jwt.js +40 -0
- package/dist/utils/tokenStorage.d.ts +12 -0
- package/dist/utils/tokenStorage.js +59 -0
- package/package.json +3 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Frontend SDK - API Client
|
|
4
|
+
* Axios instance with interceptors for token management and refresh
|
|
5
|
+
*/
|
|
6
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
7
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
8
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
9
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
10
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
11
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
12
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
16
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.createApiClient = void 0;
|
|
20
|
+
const axios_1 = __importDefault(require("axios"));
|
|
21
|
+
const tokenStorage_1 = require("../utils/tokenStorage");
|
|
22
|
+
const BASE_URL = 'https://api.airxpay.com/api/merchant';
|
|
23
|
+
let isRefreshing = false;
|
|
24
|
+
let failedQueue = [];
|
|
25
|
+
const processQueue = (error, token = null) => {
|
|
26
|
+
failedQueue.forEach(prom => {
|
|
27
|
+
if (error) {
|
|
28
|
+
prom.reject(error);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
prom.resolve(token);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
failedQueue = [];
|
|
35
|
+
};
|
|
36
|
+
const createApiClient = (publicKey) => {
|
|
37
|
+
const client = axios_1.default.create({
|
|
38
|
+
baseURL: BASE_URL,
|
|
39
|
+
timeout: 30000,
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'X-Public-Key': publicKey,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
// Request interceptor - automatically attach token
|
|
46
|
+
client.interceptors.request.use((config) => __awaiter(void 0, void 0, void 0, function* () {
|
|
47
|
+
const token = yield (0, tokenStorage_1.getStoredToken)();
|
|
48
|
+
if (token) {
|
|
49
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
50
|
+
}
|
|
51
|
+
return config;
|
|
52
|
+
}), (error) => Promise.reject(error));
|
|
53
|
+
// Response interceptor - handle 401 and token refresh
|
|
54
|
+
client.interceptors.response.use((response) => response, (error) => __awaiter(void 0, void 0, void 0, function* () {
|
|
55
|
+
var _a;
|
|
56
|
+
const originalRequest = error.config;
|
|
57
|
+
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) !== 401 || originalRequest._retry) {
|
|
58
|
+
return Promise.reject(error);
|
|
59
|
+
}
|
|
60
|
+
if (isRefreshing) {
|
|
61
|
+
// Queue failed requests while refreshing
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
failedQueue.push({ resolve, reject });
|
|
64
|
+
})
|
|
65
|
+
.then(token => {
|
|
66
|
+
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
67
|
+
return client(originalRequest);
|
|
68
|
+
})
|
|
69
|
+
.catch(err => Promise.reject(err));
|
|
70
|
+
}
|
|
71
|
+
originalRequest._retry = true;
|
|
72
|
+
isRefreshing = true;
|
|
73
|
+
try {
|
|
74
|
+
const currentToken = yield (0, tokenStorage_1.getStoredToken)();
|
|
75
|
+
if (!currentToken) {
|
|
76
|
+
throw new Error('No token to refresh');
|
|
77
|
+
}
|
|
78
|
+
// Call refresh token endpoint
|
|
79
|
+
const response = yield axios_1.default.post(`${BASE_URL}/merchant/refresh-token`, null, {
|
|
80
|
+
headers: { Authorization: `Bearer ${currentToken}` },
|
|
81
|
+
});
|
|
82
|
+
const { token: newToken } = response.data;
|
|
83
|
+
if (!newToken) {
|
|
84
|
+
throw new Error('No token in refresh response');
|
|
85
|
+
}
|
|
86
|
+
yield (0, tokenStorage_1.setStoredToken)(newToken);
|
|
87
|
+
// Update auth header
|
|
88
|
+
originalRequest.headers.Authorization = `Bearer ${newToken}`;
|
|
89
|
+
processQueue(null, newToken);
|
|
90
|
+
return client(originalRequest);
|
|
91
|
+
}
|
|
92
|
+
catch (refreshError) {
|
|
93
|
+
processQueue(refreshError, null);
|
|
94
|
+
// Clear invalid token
|
|
95
|
+
yield (0, tokenStorage_1.clearStoredToken)();
|
|
96
|
+
return Promise.reject(refreshError);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
isRefreshing = false;
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
return client;
|
|
103
|
+
};
|
|
104
|
+
exports.createApiClient = createApiClient;
|
package/dist/api/merchant.d.ts
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Frontend SDK - Merchant API
|
|
3
|
+
* All merchant-related API calls
|
|
4
|
+
*/
|
|
5
|
+
import { AxiosInstance } from 'axios';
|
|
6
|
+
import { CreateMerchantPayload, MerchantCreateResponse, MerchantStatus, KycStatus } from '../types/merchantTypes';
|
|
7
|
+
export declare const initializeApi: (publicKey: string) => void;
|
|
8
|
+
export declare const getApiClient: () => AxiosInstance;
|
|
9
|
+
/**
|
|
10
|
+
* Verify public key during initialization
|
|
11
|
+
*/
|
|
12
|
+
export declare const verifyPublicKey: (publicKey: string) => Promise<{
|
|
13
|
+
valid: boolean;
|
|
14
|
+
merchantData?: any;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Create new merchant
|
|
18
|
+
* Automatically stores returned token
|
|
19
|
+
*/
|
|
20
|
+
export declare const createMerchant: (payload: CreateMerchantPayload) => Promise<MerchantCreateResponse>;
|
|
21
|
+
/**
|
|
22
|
+
* Get merchant status
|
|
23
|
+
*/
|
|
24
|
+
export declare const getMerchantStatus: () => Promise<{
|
|
25
|
+
success: boolean;
|
|
26
|
+
status: MerchantStatus;
|
|
27
|
+
mode: "test" | "live";
|
|
6
28
|
isKycCompleted: boolean;
|
|
7
29
|
isBankDetailsCompleted: boolean;
|
|
8
|
-
kycStatus:
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
export declare const verifyPublicKey: (publicKey: string) => Promise<MerchantInitResponse>;
|
|
30
|
+
kycStatus: KycStatus;
|
|
31
|
+
bankDetails?: any;
|
|
32
|
+
}>;
|
package/dist/api/merchant.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Frontend SDK - Merchant API
|
|
4
|
+
* All merchant-related API calls
|
|
5
|
+
*/
|
|
3
6
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
7
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
8
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -10,26 +13,57 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
13
|
});
|
|
11
14
|
};
|
|
12
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.verifyPublicKey = void 0;
|
|
16
|
+
exports.getMerchantStatus = exports.createMerchant = exports.verifyPublicKey = exports.getApiClient = exports.initializeApi = void 0;
|
|
17
|
+
const client_1 = require("./client");
|
|
18
|
+
const tokenStorage_1 = require("../utils/tokenStorage");
|
|
19
|
+
let apiClient = null;
|
|
20
|
+
const initializeApi = (publicKey) => {
|
|
21
|
+
if (!(publicKey === null || publicKey === void 0 ? void 0 : publicKey.trim())) {
|
|
22
|
+
throw new Error('Public key is required');
|
|
23
|
+
}
|
|
24
|
+
apiClient = (0, client_1.createApiClient)(publicKey);
|
|
25
|
+
};
|
|
26
|
+
exports.initializeApi = initializeApi;
|
|
27
|
+
const getApiClient = () => {
|
|
28
|
+
if (!apiClient) {
|
|
29
|
+
throw new Error('API not initialized. Call initializeApi() first.');
|
|
30
|
+
}
|
|
31
|
+
return apiClient;
|
|
32
|
+
};
|
|
33
|
+
exports.getApiClient = getApiClient;
|
|
14
34
|
/**
|
|
15
|
-
*
|
|
16
|
-
* Developer ko baseUrl pass karne ki zarurat nahi
|
|
35
|
+
* Verify public key during initialization
|
|
17
36
|
*/
|
|
18
|
-
const AIRXPAY_BASE_URL = "http://172.20.10.12:7000";
|
|
19
37
|
const verifyPublicKey = (publicKey) => __awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
try {
|
|
39
|
+
const client = (0, exports.getApiClient)();
|
|
40
|
+
const response = yield client.post('/verify', { publicKey });
|
|
41
|
+
return response.data;
|
|
22
42
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
headers: {
|
|
26
|
-
"Content-Type": "application/json",
|
|
27
|
-
},
|
|
28
|
-
body: JSON.stringify({ publicKey }),
|
|
29
|
-
});
|
|
30
|
-
if (!response.ok) {
|
|
31
|
-
throw new Error("Invalid public key or server error");
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new Error('Invalid public key');
|
|
32
45
|
}
|
|
33
|
-
return response.json();
|
|
34
46
|
});
|
|
35
47
|
exports.verifyPublicKey = verifyPublicKey;
|
|
48
|
+
/**
|
|
49
|
+
* Create new merchant
|
|
50
|
+
* Automatically stores returned token
|
|
51
|
+
*/
|
|
52
|
+
const createMerchant = (payload) => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
|
+
const client = (0, exports.getApiClient)();
|
|
54
|
+
const response = yield client.post('/create', payload);
|
|
55
|
+
if (response.data.token) {
|
|
56
|
+
yield (0, tokenStorage_1.setStoredToken)(response.data.token);
|
|
57
|
+
}
|
|
58
|
+
return response.data;
|
|
59
|
+
});
|
|
60
|
+
exports.createMerchant = createMerchant;
|
|
61
|
+
/**
|
|
62
|
+
* Get merchant status
|
|
63
|
+
*/
|
|
64
|
+
const getMerchantStatus = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
+
const client = (0, exports.getApiClient)();
|
|
66
|
+
const response = yield client.get('/status');
|
|
67
|
+
return response.data;
|
|
68
|
+
});
|
|
69
|
+
exports.getMerchantStatus = getMerchantStatus;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Merchant, Mode,
|
|
2
|
+
import { Merchant, Mode, KycStatus } from '../../types/merchantTypes';
|
|
3
3
|
interface KYCVerificationProps {
|
|
4
4
|
initialData: Partial<Merchant>;
|
|
5
5
|
mode: Mode;
|
|
6
|
-
kycStatus:
|
|
6
|
+
kycStatus: KycStatus;
|
|
7
7
|
onNext: (data: Partial<Merchant>) => void;
|
|
8
8
|
onBack: () => void;
|
|
9
9
|
}
|
|
@@ -51,6 +51,73 @@ const react_native_1 = require("react-native");
|
|
|
51
51
|
const react_native_paper_1 = require("react-native-paper");
|
|
52
52
|
const expo_linear_gradient_1 = require("expo-linear-gradient");
|
|
53
53
|
const FileUploader_1 = __importDefault(require("../common/FileUploader"));
|
|
54
|
+
// Text fields configuration
|
|
55
|
+
const TEXT_FIELDS = [
|
|
56
|
+
{
|
|
57
|
+
key: 'panNumber',
|
|
58
|
+
label: 'PAN Number',
|
|
59
|
+
required: true,
|
|
60
|
+
icon: 'card-account-details',
|
|
61
|
+
placeholder: 'ABCDE1234F',
|
|
62
|
+
validation: (value) => {
|
|
63
|
+
if (!(value === null || value === void 0 ? void 0 : value.trim()))
|
|
64
|
+
return 'PAN number is required';
|
|
65
|
+
const panRegex = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/;
|
|
66
|
+
if (!panRegex.test(value.toUpperCase())) {
|
|
67
|
+
return 'Invalid PAN format (e.g., ABCDE1234F)';
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: 'aadhaarNumber',
|
|
74
|
+
label: 'Aadhaar Number',
|
|
75
|
+
required: true,
|
|
76
|
+
icon: 'card-bulleted',
|
|
77
|
+
placeholder: '1234 5678 9012',
|
|
78
|
+
keyboardType: 'numeric',
|
|
79
|
+
maxLength: 14,
|
|
80
|
+
validation: (value) => {
|
|
81
|
+
if (!(value === null || value === void 0 ? void 0 : value.trim()))
|
|
82
|
+
return 'Aadhaar number is required';
|
|
83
|
+
const cleaned = value.replace(/\s/g, '');
|
|
84
|
+
if (!/^\d{12}$/.test(cleaned)) {
|
|
85
|
+
return 'Aadhaar must be 12 digits';
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
key: 'gstNumber',
|
|
92
|
+
label: 'GST Number',
|
|
93
|
+
required: false,
|
|
94
|
+
icon: 'file-certificate',
|
|
95
|
+
placeholder: '22AAAAA0000A1Z5',
|
|
96
|
+
validation: (value) => {
|
|
97
|
+
if (!(value === null || value === void 0 ? void 0 : value.trim()))
|
|
98
|
+
return undefined;
|
|
99
|
+
const gstRegex = /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[Z]{1}[0-9A-Z]{1}$/;
|
|
100
|
+
if (!gstRegex.test(value.toUpperCase())) {
|
|
101
|
+
return 'Invalid GST format';
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: 'registeredBusinessName',
|
|
108
|
+
label: 'Registered Business Name',
|
|
109
|
+
required: false,
|
|
110
|
+
icon: 'store',
|
|
111
|
+
placeholder: 'Your registered business name',
|
|
112
|
+
validation: (value) => {
|
|
113
|
+
if (value && value.length < 3) {
|
|
114
|
+
return 'Business name must be at least 3 characters';
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
// Document uploads configuration
|
|
54
121
|
const REQUIRED_DOCUMENTS = [
|
|
55
122
|
{
|
|
56
123
|
key: 'panCardUrl',
|
|
@@ -58,7 +125,7 @@ const REQUIRED_DOCUMENTS = [
|
|
|
58
125
|
required: true,
|
|
59
126
|
icon: 'card-account-details',
|
|
60
127
|
description: 'Clear image of PAN card',
|
|
61
|
-
acceptedTypes: ['image/jpeg', 'image/jpg', 'image/png']
|
|
128
|
+
acceptedTypes: ['image/jpeg', 'image/jpg', 'image/png', 'application/pdf'],
|
|
62
129
|
},
|
|
63
130
|
{
|
|
64
131
|
key: 'aadhaarUrl',
|
|
@@ -66,7 +133,7 @@ const REQUIRED_DOCUMENTS = [
|
|
|
66
133
|
required: true,
|
|
67
134
|
icon: 'card-bulleted',
|
|
68
135
|
description: 'Both sides of Aadhaar',
|
|
69
|
-
acceptedTypes: ['image/jpeg', 'image/jpg', 'image/png']
|
|
136
|
+
acceptedTypes: ['image/jpeg', 'image/jpg', 'image/png', 'application/pdf'],
|
|
70
137
|
},
|
|
71
138
|
{
|
|
72
139
|
key: 'selfieUrl',
|
|
@@ -74,44 +141,83 @@ const REQUIRED_DOCUMENTS = [
|
|
|
74
141
|
required: true,
|
|
75
142
|
icon: 'face',
|
|
76
143
|
description: 'Clear front-facing photo',
|
|
77
|
-
acceptedTypes: ['image/jpeg', 'image/jpg', 'image/png']
|
|
144
|
+
acceptedTypes: ['image/jpeg', 'image/jpg', 'image/png'],
|
|
78
145
|
},
|
|
79
146
|
{
|
|
80
147
|
key: 'addressProofUrl',
|
|
81
148
|
label: 'Address Proof',
|
|
82
|
-
required:
|
|
149
|
+
required: true,
|
|
83
150
|
icon: 'home',
|
|
84
151
|
description: 'Utility bill or rent agreement',
|
|
85
|
-
acceptedTypes: ['image/jpeg', 'image/jpg', 'image/png', 'application/pdf']
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
key: 'businessRegistrationUrl',
|
|
89
|
-
label: 'Business Registration',
|
|
90
|
-
required: false,
|
|
91
|
-
icon: 'file-document',
|
|
92
|
-
description: 'GST, MSME, or company registration',
|
|
93
|
-
acceptedTypes: ['image/jpeg', 'image/jpg', 'image/png', 'application/pdf']
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
key: 'gstCertificateUrl',
|
|
97
|
-
label: 'GST Certificate',
|
|
98
|
-
required: false,
|
|
99
|
-
icon: 'file-certificate',
|
|
100
|
-
description: 'If applicable',
|
|
101
|
-
acceptedTypes: ['image/jpeg', 'image/jpg', 'image/png', 'application/pdf']
|
|
152
|
+
acceptedTypes: ['image/jpeg', 'image/jpg', 'image/png', 'application/pdf'],
|
|
102
153
|
},
|
|
103
154
|
];
|
|
104
155
|
const KYCVerification = ({ initialData, mode, kycStatus, onNext, onBack, }) => {
|
|
105
|
-
|
|
156
|
+
// Initialize with new KYCDetails structure
|
|
157
|
+
const [kycDetails, setKycDetails] = (0, react_1.useState)(initialData.kycDetails || {});
|
|
106
158
|
const [uploadingFor, setUploadingFor] = (0, react_1.useState)(null);
|
|
107
159
|
const [isVerifying, setIsVerifying] = (0, react_1.useState)(false);
|
|
108
160
|
const [verificationComplete, setVerificationComplete] = (0, react_1.useState)(false);
|
|
161
|
+
const [touched, setTouched] = (0, react_1.useState)({});
|
|
162
|
+
const [fieldErrors, setFieldErrors] = (0, react_1.useState)({});
|
|
109
163
|
// Check if KYC is already verified
|
|
110
164
|
(0, react_1.useEffect)(() => {
|
|
111
165
|
if (kycStatus === 'verified') {
|
|
112
166
|
setVerificationComplete(true);
|
|
113
167
|
}
|
|
114
168
|
}, [kycStatus]);
|
|
169
|
+
// Validate a specific field
|
|
170
|
+
const validateField = (key, value) => {
|
|
171
|
+
const textField = TEXT_FIELDS.find(f => f.key === key);
|
|
172
|
+
if ((textField === null || textField === void 0 ? void 0 : textField.validation) && value !== undefined) {
|
|
173
|
+
return textField.validation(value);
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
};
|
|
177
|
+
// Handle text input change
|
|
178
|
+
const handleTextChange = (key, value) => {
|
|
179
|
+
// Format Aadhaar number with spaces
|
|
180
|
+
if (key === 'aadhaarNumber') {
|
|
181
|
+
value = value.replace(/\D/g, '');
|
|
182
|
+
if (value.length > 12)
|
|
183
|
+
value = value.slice(0, 12);
|
|
184
|
+
// Add space after every 4 digits
|
|
185
|
+
const parts = value.match(/.{1,4}/g);
|
|
186
|
+
value = parts ? parts.join(' ') : value;
|
|
187
|
+
}
|
|
188
|
+
// Format PAN to uppercase
|
|
189
|
+
if (key === 'panNumber' || key === 'gstNumber') {
|
|
190
|
+
value = value.toUpperCase();
|
|
191
|
+
}
|
|
192
|
+
setKycDetails(prev => (Object.assign(Object.assign({}, prev), { [key]: value })));
|
|
193
|
+
// Validate on change
|
|
194
|
+
const error = validateField(key, value);
|
|
195
|
+
setFieldErrors(prev => {
|
|
196
|
+
const newErrors = Object.assign({}, prev);
|
|
197
|
+
if (error) {
|
|
198
|
+
newErrors[key] = error;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
delete newErrors[key];
|
|
202
|
+
}
|
|
203
|
+
return newErrors;
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
const handleBlur = (key) => {
|
|
207
|
+
setTouched(prev => (Object.assign(Object.assign({}, prev), { [key]: true })));
|
|
208
|
+
const value = kycDetails[key];
|
|
209
|
+
const error = validateField(key, value);
|
|
210
|
+
setFieldErrors(prev => {
|
|
211
|
+
const newErrors = Object.assign({}, prev);
|
|
212
|
+
if (error) {
|
|
213
|
+
newErrors[key] = error;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
delete newErrors[key];
|
|
217
|
+
}
|
|
218
|
+
return newErrors;
|
|
219
|
+
});
|
|
220
|
+
};
|
|
115
221
|
const validateDocumentType = (file, documentKey) => {
|
|
116
222
|
const document = REQUIRED_DOCUMENTS.find(doc => doc.key === documentKey);
|
|
117
223
|
if (!document || !document.acceptedTypes)
|
|
@@ -131,7 +237,7 @@ const KYCVerification = ({ initialData, mode, kycStatus, onNext, onBack, }) => {
|
|
|
131
237
|
setUploadingFor(documentKey);
|
|
132
238
|
// Simulate upload delay
|
|
133
239
|
setTimeout(() => {
|
|
134
|
-
|
|
240
|
+
setKycDetails(prev => (Object.assign(Object.assign({}, prev), { [documentKey]: file.uri || 'uploaded_file.jpg' })));
|
|
135
241
|
setUploadingFor(null);
|
|
136
242
|
if (mode === 'test') {
|
|
137
243
|
react_native_1.Alert.alert('Test Mode', 'Document would be auto-approved in test mode');
|
|
@@ -145,9 +251,9 @@ const KYCVerification = ({ initialData, mode, kycStatus, onNext, onBack, }) => {
|
|
|
145
251
|
text: 'Remove',
|
|
146
252
|
style: 'destructive',
|
|
147
253
|
onPress: () => {
|
|
148
|
-
const updated = Object.assign({},
|
|
254
|
+
const updated = Object.assign({}, kycDetails);
|
|
149
255
|
delete updated[documentKey];
|
|
150
|
-
|
|
256
|
+
setKycDetails(updated);
|
|
151
257
|
},
|
|
152
258
|
},
|
|
153
259
|
]);
|
|
@@ -176,15 +282,24 @@ const KYCVerification = ({ initialData, mode, kycStatus, onNext, onBack, }) => {
|
|
|
176
282
|
</react_native_1.View>);
|
|
177
283
|
}
|
|
178
284
|
};
|
|
179
|
-
const
|
|
180
|
-
|
|
285
|
+
const isRequiredFieldsFilled = () => {
|
|
286
|
+
// Check required text fields
|
|
287
|
+
const textFieldsValid = TEXT_FIELDS
|
|
288
|
+
.filter(field => field.required)
|
|
289
|
+
.every(field => {
|
|
290
|
+
const value = kycDetails[field.key];
|
|
291
|
+
return value && value.trim().length > 0;
|
|
292
|
+
});
|
|
293
|
+
// Check required documents
|
|
294
|
+
const documentsValid = REQUIRED_DOCUMENTS
|
|
181
295
|
.filter(doc => doc.required)
|
|
182
|
-
.every(doc =>
|
|
296
|
+
.every(doc => kycDetails[doc.key]);
|
|
297
|
+
return textFieldsValid && documentsValid && Object.keys(fieldErrors).length === 0;
|
|
183
298
|
};
|
|
184
299
|
const handleSubmit = () => {
|
|
185
|
-
// Validate required
|
|
186
|
-
if (!
|
|
187
|
-
react_native_1.Alert.alert('Error', 'Please
|
|
300
|
+
// Validate all required fields
|
|
301
|
+
if (!isRequiredFieldsFilled()) {
|
|
302
|
+
react_native_1.Alert.alert('Error', 'Please fill all required fields and upload required documents');
|
|
188
303
|
return;
|
|
189
304
|
}
|
|
190
305
|
// Show verifying state
|
|
@@ -194,14 +309,14 @@ const KYCVerification = ({ initialData, mode, kycStatus, onNext, onBack, }) => {
|
|
|
194
309
|
setIsVerifying(false);
|
|
195
310
|
if (mode === 'test') {
|
|
196
311
|
onNext({
|
|
197
|
-
|
|
312
|
+
kycDetails: kycDetails,
|
|
198
313
|
isKycCompleted: true,
|
|
199
314
|
kycStatus: 'verified',
|
|
200
315
|
});
|
|
201
316
|
}
|
|
202
317
|
else {
|
|
203
318
|
onNext({
|
|
204
|
-
|
|
319
|
+
kycDetails: kycDetails,
|
|
205
320
|
isKycCompleted: false,
|
|
206
321
|
kycStatus: 'pending',
|
|
207
322
|
});
|
|
@@ -211,9 +326,17 @@ const KYCVerification = ({ initialData, mode, kycStatus, onNext, onBack, }) => {
|
|
|
211
326
|
const handleBack = () => {
|
|
212
327
|
onBack();
|
|
213
328
|
};
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
const
|
|
329
|
+
const requiredTextCount = TEXT_FIELDS.filter(f => f.required).length;
|
|
330
|
+
const requiredDocCount = REQUIRED_DOCUMENTS.filter(d => d.required).length;
|
|
331
|
+
const totalRequired = requiredTextCount + requiredDocCount;
|
|
332
|
+
const filledTextCount = TEXT_FIELDS
|
|
333
|
+
.filter(f => f.required && kycDetails[f.key])
|
|
334
|
+
.length;
|
|
335
|
+
const filledDocCount = REQUIRED_DOCUMENTS
|
|
336
|
+
.filter(d => d.required && kycDetails[d.key])
|
|
337
|
+
.length;
|
|
338
|
+
const filledTotal = filledTextCount + filledDocCount;
|
|
339
|
+
const progress = (filledTotal / totalRequired) * 100;
|
|
217
340
|
// If already verified, show success state
|
|
218
341
|
if (verificationComplete) {
|
|
219
342
|
return (<react_native_1.View style={styles.container}>
|
|
@@ -250,7 +373,7 @@ const KYCVerification = ({ initialData, mode, kycStatus, onNext, onBack, }) => {
|
|
|
250
373
|
<react_native_1.View style={styles.headerText}>
|
|
251
374
|
<react_native_1.Text style={styles.title}>KYC Verification</react_native_1.Text>
|
|
252
375
|
<react_native_1.Text style={styles.subtitle}>
|
|
253
|
-
|
|
376
|
+
Enter your details and upload documents
|
|
254
377
|
</react_native_1.Text>
|
|
255
378
|
</react_native_1.View>
|
|
256
379
|
</react_native_paper_1.Surface>
|
|
@@ -289,7 +412,7 @@ const KYCVerification = ({ initialData, mode, kycStatus, onNext, onBack, }) => {
|
|
|
289
412
|
<expo_linear_gradient_1.LinearGradient colors={['#0066CC', '#0099FF']} style={[styles.progressFill, { width: `${progress}%` }]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }}/>
|
|
290
413
|
</react_native_1.View>
|
|
291
414
|
<react_native_1.Text style={styles.progressText}>
|
|
292
|
-
{
|
|
415
|
+
{filledTotal}/{totalRequired} Completed
|
|
293
416
|
</react_native_1.Text>
|
|
294
417
|
</react_native_1.View>
|
|
295
418
|
|
|
@@ -300,19 +423,38 @@ const KYCVerification = ({ initialData, mode, kycStatus, onNext, onBack, }) => {
|
|
|
300
423
|
<react_native_1.View style={styles.rejectionText}>
|
|
301
424
|
<react_native_1.Text style={styles.rejectionTitle}>KYC Rejected</react_native_1.Text>
|
|
302
425
|
<react_native_1.Text style={styles.rejectionMessage}>
|
|
303
|
-
Please
|
|
426
|
+
Please check and resubmit your details
|
|
304
427
|
</react_native_1.Text>
|
|
305
428
|
</react_native_1.View>
|
|
306
429
|
</react_native_1.View>
|
|
307
430
|
</react_native_paper_1.Surface>)}
|
|
308
431
|
|
|
309
|
-
{/*
|
|
310
|
-
<react_native_1.View style={styles.
|
|
432
|
+
{/* Text Fields Section */}
|
|
433
|
+
<react_native_1.View style={styles.sectionContainer}>
|
|
434
|
+
<react_native_1.Text style={styles.sectionTitle}>Personal Details</react_native_1.Text>
|
|
435
|
+
<react_native_1.Text style={styles.sectionSubtitle}>Enter your identification numbers</react_native_1.Text>
|
|
436
|
+
|
|
437
|
+
{TEXT_FIELDS.map((field, index) => (<react_native_1.View key={field.key} style={styles.fieldContainer}>
|
|
438
|
+
<react_native_1.Text style={styles.label}>
|
|
439
|
+
{field.label} {field.required && <react_native_1.Text style={styles.requiredStar}>*</react_native_1.Text>}
|
|
440
|
+
</react_native_1.Text>
|
|
441
|
+
<react_native_paper_1.TextInput mode="outlined" value={kycDetails[field.key] || ''} onChangeText={(text) => handleTextChange(field.key, text)} onBlur={() => handleBlur(field.key)} keyboardType={field.keyboardType || 'default'} error={!!fieldErrors[field.key]} style={styles.input} outlineColor="#E5E7EB" activeOutlineColor="#0066CC" left={<react_native_paper_1.TextInput.Icon icon={field.icon} color="#6B7280"/>} placeholder={field.placeholder} placeholderTextColor="#9CA3AF" maxLength={field.maxLength}/>
|
|
442
|
+
{fieldErrors[field.key] && (<react_native_paper_1.HelperText type="error" style={styles.errorText}>
|
|
443
|
+
{fieldErrors[field.key]}
|
|
444
|
+
</react_native_paper_1.HelperText>)}
|
|
445
|
+
</react_native_1.View>))}
|
|
446
|
+
</react_native_1.View>
|
|
447
|
+
|
|
448
|
+
{/* Document Upload Section */}
|
|
449
|
+
<react_native_1.View style={[styles.sectionContainer, styles.documentSection]}>
|
|
450
|
+
<react_native_1.Text style={styles.sectionTitle}>Document Uploads</react_native_1.Text>
|
|
451
|
+
<react_native_1.Text style={styles.sectionSubtitle}>Upload clear images of your documents</react_native_1.Text>
|
|
452
|
+
|
|
311
453
|
{REQUIRED_DOCUMENTS.map((doc, index) => {
|
|
312
454
|
var _a;
|
|
313
455
|
return (<react_native_1.View key={doc.key} style={styles.documentItem}>
|
|
314
456
|
{index > 0 && <react_native_1.View style={styles.documentDivider}/>}
|
|
315
|
-
<FileUploader_1.default label={doc.label} required={doc.required} description={doc.description} icon={doc.icon} value={
|
|
457
|
+
<FileUploader_1.default label={doc.label} required={doc.required} description={doc.description} icon={doc.icon} value={kycDetails[doc.key]} onUpload={(file) => handleDocumentUpload(doc.key, file)} onRemove={() => handleDocumentRemove(doc.key)} uploading={uploadingFor === doc.key} mode={mode} accept={((_a = doc.acceptedTypes) === null || _a === void 0 ? void 0 : _a.join(',')) || '*'}/>
|
|
316
458
|
</react_native_1.View>);
|
|
317
459
|
})}
|
|
318
460
|
</react_native_1.View>
|
|
@@ -338,9 +480,9 @@ const KYCVerification = ({ initialData, mode, kycStatus, onNext, onBack, }) => {
|
|
|
338
480
|
|
|
339
481
|
<react_native_1.TouchableOpacity style={[
|
|
340
482
|
styles.submitButton,
|
|
341
|
-
(!
|
|
342
|
-
]} onPress={handleSubmit} disabled={!
|
|
343
|
-
<expo_linear_gradient_1.LinearGradient colors={
|
|
483
|
+
(!isRequiredFieldsFilled() || isVerifying) && styles.submitButtonDisabled
|
|
484
|
+
]} onPress={handleSubmit} disabled={!isRequiredFieldsFilled() || isVerifying}>
|
|
485
|
+
<expo_linear_gradient_1.LinearGradient colors={isRequiredFieldsFilled() && !isVerifying ? ['#0066CC', '#0099FF'] : ['#9CA3AF', '#9CA3AF']} style={styles.submitGradient} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }}>
|
|
344
486
|
{isVerifying ? (<react_native_1.View style={styles.verifyingContent}>
|
|
345
487
|
<react_native_paper_1.ActivityIndicator size="small" color="#FFFFFF"/>
|
|
346
488
|
<react_native_1.Text style={[styles.submitButtonText, styles.verifyingText]}>
|
|
@@ -559,6 +701,45 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
559
701
|
color: '#7F1D1D',
|
|
560
702
|
marginTop: 1,
|
|
561
703
|
},
|
|
704
|
+
sectionContainer: {
|
|
705
|
+
marginBottom: 20,
|
|
706
|
+
},
|
|
707
|
+
documentSection: {
|
|
708
|
+
marginTop: 8,
|
|
709
|
+
},
|
|
710
|
+
sectionTitle: {
|
|
711
|
+
fontSize: 16,
|
|
712
|
+
fontWeight: '600',
|
|
713
|
+
color: '#111827',
|
|
714
|
+
marginBottom: 4,
|
|
715
|
+
},
|
|
716
|
+
sectionSubtitle: {
|
|
717
|
+
fontSize: 12,
|
|
718
|
+
color: '#6B7280',
|
|
719
|
+
marginBottom: 12,
|
|
720
|
+
},
|
|
721
|
+
fieldContainer: {
|
|
722
|
+
marginBottom: 16,
|
|
723
|
+
},
|
|
724
|
+
label: {
|
|
725
|
+
fontSize: 13,
|
|
726
|
+
fontWeight: '500',
|
|
727
|
+
color: '#374151',
|
|
728
|
+
marginBottom: 6,
|
|
729
|
+
},
|
|
730
|
+
requiredStar: {
|
|
731
|
+
color: '#EF4444',
|
|
732
|
+
},
|
|
733
|
+
input: {
|
|
734
|
+
backgroundColor: '#FFFFFF',
|
|
735
|
+
fontSize: 14,
|
|
736
|
+
height: 48,
|
|
737
|
+
},
|
|
738
|
+
errorText: {
|
|
739
|
+
fontSize: 11,
|
|
740
|
+
color: '#EF4444',
|
|
741
|
+
marginTop: 2,
|
|
742
|
+
},
|
|
562
743
|
documentsContainer: {
|
|
563
744
|
marginBottom: 8,
|
|
564
745
|
},
|