feat: smooth navigation transitions + config updates
- Migrate MainStack from JS stack to native-stack for native-thread, jank-free screen transitions (slide_from_right, fade for auth screens) - Add shift animation to bottom tab switching - Install @react-navigation/native-stack - Update API URLs, Axios, Login and Android network security config Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
44e84ff6f1
commit
9acec328a7
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"artifactType": {
|
|
||||||
"type": "APK",
|
|
||||||
"kind": "Directory"
|
|
||||||
},
|
|
||||||
"applicationId": "com.procure",
|
|
||||||
"variantName": "release",
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "SINGLE",
|
|
||||||
"filters": [],
|
|
||||||
"attributes": [],
|
|
||||||
"versionCode": 1,
|
|
||||||
"versionName": "1.0",
|
|
||||||
"outputFile": "app-release.apk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"elementType": "File",
|
|
||||||
"baselineProfiles": [
|
|
||||||
{
|
|
||||||
"minApi": 28,
|
|
||||||
"maxApi": 30,
|
|
||||||
"baselineProfiles": [
|
|
||||||
"baselineProfiles/1/app-release.dm"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"minApi": 31,
|
|
||||||
"maxApi": 2147483647,
|
|
||||||
"baselineProfiles": [
|
|
||||||
"baselineProfiles/0/app-release.dm"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"minSdkVersionForDexing": 24
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Debug-only override: allow all cleartext so Metro / localhost / LAN dev IPs work.
|
||||||
|
Release uses the restrictive config in src/main/res/xml. -->
|
||||||
|
<network-security-config>
|
||||||
|
<base-config cleartextTrafficPermitted="true" />
|
||||||
|
</network-security-config>
|
||||||
@@ -16,6 +16,8 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:supportsRtl="true">
|
android:supportsRtl="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Allow ALL cleartext (http://) traffic for every build, including release.
|
||||||
|
Needed so the app can reach http://eproc-backend.lincpay/ over plain HTTP. -->
|
||||||
|
<network-security-config>
|
||||||
|
<base-config cleartextTrafficPermitted="true" />
|
||||||
|
</network-security-config>
|
||||||
Generated
+42
-15
@@ -16,6 +16,7 @@
|
|||||||
"@react-navigation/bottom-tabs": "7.9.0",
|
"@react-navigation/bottom-tabs": "7.9.0",
|
||||||
"@react-navigation/drawer": "^7.8.1",
|
"@react-navigation/drawer": "^7.8.1",
|
||||||
"@react-navigation/native": "^7.1.28",
|
"@react-navigation/native": "^7.1.28",
|
||||||
|
"@react-navigation/native-stack": "^7.17.3",
|
||||||
"@react-navigation/stack": "7.6.13",
|
"@react-navigation/stack": "7.6.13",
|
||||||
"@reduxjs/toolkit": "2.11.2",
|
"@reduxjs/toolkit": "2.11.2",
|
||||||
"@stomp/stompjs": "7.2.1",
|
"@stomp/stompjs": "7.2.1",
|
||||||
@@ -3666,12 +3667,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-navigation/core": {
|
"node_modules/@react-navigation/core": {
|
||||||
"version": "7.17.2",
|
"version": "7.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.17.2.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.20.0.tgz",
|
||||||
"integrity": "sha512-Rt2OZwcgOmjv401uLGAKaRM6xo0fiBce/A7LfRHI1oe5FV+KooWcgAoZ2XOtgKj6UzVMuQWt3b2e6rxo/mDJRA==",
|
"integrity": "sha512-Lqw5cDQWWxiQnaWv6RhQV95Wr4fh+38/IFVNn1grssyLWV+wXGJjlucXOoU7EVh9jdtcLT8pGyzvsyrvSDywWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/routers": "^7.5.3",
|
"@react-navigation/routers": "^7.6.0",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
@@ -3706,9 +3707,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-navigation/elements": {
|
"node_modules/@react-navigation/elements": {
|
||||||
"version": "2.9.15",
|
"version": "2.9.23",
|
||||||
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.15.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.23.tgz",
|
||||||
"integrity": "sha512-cyz/pPiyyC6gaTVLsGFc1g0MYgrmuCFqklAWGXMWPscr5YU3ui94vPI4vnZwcsEy0T758TQWLzmS5XudZeRKcA==",
|
"integrity": "sha512-sp+FgihDyMBoEXoCUsUCT/iibN/sg6LYGq/rciy6NjT8bnfv4Cu3el8SAaJ0bfRG3tdchHy6gweKmcaJs/BAYQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
@@ -3717,7 +3718,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@react-native-masked-view/masked-view": ">= 0.2.0",
|
"@react-native-masked-view/masked-view": ">= 0.2.0",
|
||||||
"@react-navigation/native": "^7.2.2",
|
"@react-navigation/native": "^7.3.1",
|
||||||
"react": ">= 18.2.0",
|
"react": ">= 18.2.0",
|
||||||
"react-native": "*",
|
"react-native": "*",
|
||||||
"react-native-safe-area-context": ">= 4.0.0"
|
"react-native-safe-area-context": ">= 4.0.0"
|
||||||
@@ -3729,15 +3730,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-navigation/native": {
|
"node_modules/@react-navigation/native": {
|
||||||
"version": "7.2.2",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.3.1.tgz",
|
||||||
"integrity": "sha512-kem1Ko2BcbAjmbQIv66dNmr6EtfDut3QU0qjsVhMnLLhktwyXb6FzZYp8gTrUb6AvkAbaJoi+BF5Pl55pAUa5w==",
|
"integrity": "sha512-g1o8jBm87WviR0Eq0wT0M43TSi+uBTz4x8YfHh4XRQ+FHqhNr+uGbuxtGu72QhHtOz0LWnb8UWyvd+M6xWkWHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/core": "^7.17.2",
|
"@react-navigation/core": "^7.20.0",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
|
"standard-navigation": "^0.0.7",
|
||||||
"use-latest-callback": "^0.2.4"
|
"use-latest-callback": "^0.2.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -3745,10 +3747,29 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-navigation/native-stack": {
|
||||||
|
"version": "7.17.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.17.3.tgz",
|
||||||
|
"integrity": "sha512-8X9AxW0BACB62eCL+DAL+Nf5lFAxXi3w1qaj2D/i0axYjxUZbI5AwrfuHjRo0B231K5WWa6HKyscF07IDHcKHg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-navigation/elements": "^2.9.23",
|
||||||
|
"color": "^4.2.3",
|
||||||
|
"sf-symbols-typescript": "^2.1.0",
|
||||||
|
"warn-once": "^0.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@react-navigation/native": "^7.3.1",
|
||||||
|
"react": ">= 18.2.0",
|
||||||
|
"react-native": "*",
|
||||||
|
"react-native-safe-area-context": ">= 4.0.0",
|
||||||
|
"react-native-screens": ">= 4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-navigation/routers": {
|
"node_modules/@react-navigation/routers": {
|
||||||
"version": "7.5.3",
|
"version": "7.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.6.0.tgz",
|
||||||
"integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==",
|
"integrity": "sha512-lblhDXfS75jLc7G2K7BZGM+7cjqQXk13X/MA4fq/12r62zM+fBhhreLzYflSitrDDXFRJpSvJXy0ziiGU04Xow==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11"
|
"nanoid": "^3.3.11"
|
||||||
@@ -14334,6 +14355,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/standard-navigation": {
|
||||||
|
"version": "0.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/standard-navigation/-/standard-navigation-0.0.7.tgz",
|
||||||
|
"integrity": "sha512-NCGLCNyuXrFOkGHxdNZFnpsehGtiq1oXbPhKl7ZuxFO5J//H2evqqOchmD4YwEUJnkjO4kH9Xp4hQX6hdAYCKQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"@react-navigation/bottom-tabs": "7.9.0",
|
"@react-navigation/bottom-tabs": "7.9.0",
|
||||||
"@react-navigation/drawer": "^7.8.1",
|
"@react-navigation/drawer": "^7.8.1",
|
||||||
"@react-navigation/native": "^7.1.28",
|
"@react-navigation/native": "^7.1.28",
|
||||||
|
"@react-navigation/native-stack": "^7.17.3",
|
||||||
"@react-navigation/stack": "7.6.13",
|
"@react-navigation/stack": "7.6.13",
|
||||||
"@reduxjs/toolkit": "2.11.2",
|
"@reduxjs/toolkit": "2.11.2",
|
||||||
"@stomp/stompjs": "7.2.1",
|
"@stomp/stompjs": "7.2.1",
|
||||||
|
|||||||
+94
-66
@@ -5,6 +5,98 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
|||||||
import { resetAndNavigate } from '../Navigation/NavigationService';
|
import { resetAndNavigate } from '../Navigation/NavigationService';
|
||||||
import NavigationString from '../Constant/NavigationString';
|
import NavigationString from '../Constant/NavigationString';
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// GLOBAL API LOGGER
|
||||||
|
// Logs EVERY axios call (apiCall wrapper + direct axios.*),
|
||||||
|
// including full URL, method, payload, params, headers,
|
||||||
|
// response data and full error response.
|
||||||
|
// React-Native friendly (no %c CSS — that doesn't work in Metro/Logcat).
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Build the full URL exactly as it goes on the wire: baseURL + url + ?query
|
||||||
|
const buildFullUrl = (config: any = {}): string => {
|
||||||
|
const base = config.baseURL || '';
|
||||||
|
const path = config.url || '';
|
||||||
|
let full = base ? `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}` : path;
|
||||||
|
|
||||||
|
const params = config.params;
|
||||||
|
if (params && typeof params === 'object') {
|
||||||
|
const qs = Object.keys(params)
|
||||||
|
.filter(k => params[k] !== undefined && params[k] !== null)
|
||||||
|
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
|
||||||
|
.join('&');
|
||||||
|
if (qs) full += (full.includes('?') ? '&' : '?') + qs;
|
||||||
|
}
|
||||||
|
return full;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make any payload safe/readable for logging. console.log already handles
|
||||||
|
// nested objects and circular refs, so we only special-case FormData
|
||||||
|
// (which otherwise prints as an opaque object).
|
||||||
|
const safe = (val: any): any => {
|
||||||
|
if (val == null) return val;
|
||||||
|
if (typeof FormData !== 'undefined' && val instanceof FormData) {
|
||||||
|
const parts = (val as any)?._parts;
|
||||||
|
return Array.isArray(parts)
|
||||||
|
? `[FormData] ${parts.map((p: any) => p?.[0]).join(', ')}`
|
||||||
|
: '[FormData]';
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register interceptors only once (module may be re-imported on fast-refresh)
|
||||||
|
const g: any = globalThis as any;
|
||||||
|
if (!g.__API_LOGGER_INSTALLED__) {
|
||||||
|
g.__API_LOGGER_INSTALLED__ = true;
|
||||||
|
|
||||||
|
axios.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
const method = (config.method || 'GET').toUpperCase();
|
||||||
|
const fullUrl = buildFullUrl(config);
|
||||||
|
console.log('\n==================== 🚀 API REQUEST ====================');
|
||||||
|
console.log('METHOD →', method);
|
||||||
|
console.log('FULL URL →', fullUrl);
|
||||||
|
console.log('PARAMS →', safe(config.params) ?? {});
|
||||||
|
console.log('PAYLOAD →', safe(config.data) ?? {});
|
||||||
|
console.log('HEADERS →', safe(config.headers) ?? {});
|
||||||
|
console.log('========================================================');
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log('❌ REQUEST SETUP ERROR →', error?.message, error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
response => {
|
||||||
|
const method = (response.config.method || 'GET').toUpperCase();
|
||||||
|
const fullUrl = buildFullUrl(response.config);
|
||||||
|
console.log('\n==================== ✅ API RESPONSE ====================');
|
||||||
|
console.log('METHOD →', method);
|
||||||
|
console.log('FULL URL →', fullUrl);
|
||||||
|
console.log('STATUS →', response.status);
|
||||||
|
console.log('RESPONSE →', safe(response.data));
|
||||||
|
console.log('=========================================================');
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
const cfg = error?.config || {};
|
||||||
|
const method = (cfg.method || 'GET').toUpperCase();
|
||||||
|
const fullUrl = buildFullUrl(cfg);
|
||||||
|
console.log('\n==================== ❌ API ERROR ====================');
|
||||||
|
console.log('METHOD →', method);
|
||||||
|
console.log('FULL URL →', fullUrl);
|
||||||
|
console.log('REQ PAYLOAD→', safe(cfg.data) ?? {});
|
||||||
|
console.log('STATUS →', error?.response?.status ?? 'NO RESPONSE / NETWORK');
|
||||||
|
console.log('MESSAGE →', error?.response?.data?.message || error?.message);
|
||||||
|
console.log('ERROR DATA →', safe(error?.response?.data));
|
||||||
|
console.log('FULL ERROR →', error?.toJSON ? error.toJSON() : safe(error));
|
||||||
|
console.log('======================================================');
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const apiCall = async <R, D = {}>(
|
export const apiCall = async <R, D = {}>(
|
||||||
method: 'get' | 'post' | 'patch' | 'delete' | 'put',
|
method: 'get' | 'post' | 'patch' | 'delete' | 'put',
|
||||||
@@ -15,26 +107,8 @@ export const apiCall = async <R, D = {}>(
|
|||||||
): Promise<R> => {
|
): Promise<R> => {
|
||||||
const token: any = store.getState().token.token;
|
const token: any = store.getState().token.token;
|
||||||
|
|
||||||
// 🔥 ONE MAIN GROUP FOR ENTIRE API CALL
|
|
||||||
console.group(
|
|
||||||
`%c🚀 API CALL → ${method.toUpperCase()} ${url}`,
|
|
||||||
'color:#0ea5e9;font-weight:bold'
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===== REQUEST =====
|
|
||||||
console.group('📤 REQUEST');
|
|
||||||
console.log('%cMETHOD →', 'color:#22c55e;font-weight:bold', method);
|
|
||||||
console.log('%cURL →', 'color:#22c55e;font-weight:bold', url);
|
|
||||||
console.log('%cPARAMS →', 'color:#f59e0b;font-weight:bold', params);
|
|
||||||
console.log('%cPAYLOAD →', 'color:#a855f7;font-weight:bold', data);
|
|
||||||
console.log(
|
|
||||||
'%cTOKEN →',
|
|
||||||
'color:#ef4444;font-weight:bold',
|
|
||||||
token?.token ? '✔ PRESENT' : '❌ MISSING'
|
|
||||||
);
|
|
||||||
console.groupEnd();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Request/response/error are logged globally by the axios interceptors above.
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method,
|
method,
|
||||||
url,
|
url,
|
||||||
@@ -46,64 +120,18 @@ export const apiCall = async <R, D = {}>(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== RESPONSE =====
|
|
||||||
console.group('📥 RESPONSE');
|
|
||||||
console.log(
|
|
||||||
'%cSTATUS →',
|
|
||||||
'color:#22c55e;font-weight:bold',
|
|
||||||
response.status
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
'%cRESPONSE DATA →',
|
|
||||||
'color:#0ea5e9;font-weight:bold',
|
|
||||||
response.data
|
|
||||||
);
|
|
||||||
console.groupEnd();
|
|
||||||
|
|
||||||
console.groupEnd(); // 🔚 MAIN GROUP
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// ===== ERROR =====
|
|
||||||
console.group('❌ ERROR');
|
|
||||||
console.error(
|
|
||||||
'%cSTATUS →',
|
|
||||||
'color:#ef4444;font-weight:bold',
|
|
||||||
error?.response?.status
|
|
||||||
);
|
|
||||||
console.error(
|
|
||||||
'%cMESSAGE →',
|
|
||||||
'color:#f97316;font-weight:bold',
|
|
||||||
error?.response?.data?.message || error.message
|
|
||||||
);
|
|
||||||
console.error(
|
|
||||||
'%cERROR DATA →',
|
|
||||||
'color:#fb7185;font-weight:bold',
|
|
||||||
error?.response?.data
|
|
||||||
);
|
|
||||||
console.error(
|
|
||||||
'%cFULL ERROR →',
|
|
||||||
'color:#7c2d12;font-weight:bold',
|
|
||||||
error
|
|
||||||
);
|
|
||||||
console.groupEnd();
|
|
||||||
|
|
||||||
// ===== AUTH ERROR =====
|
// ===== AUTH ERROR =====
|
||||||
if (error?.response?.status === 401) {
|
if (error?.response?.status === 401) {
|
||||||
console.warn(
|
console.warn('⚠ JWT ERROR → Token expired or malformed');
|
||||||
'%c⚠ JWT ERROR → Token expired or malformed',
|
|
||||||
'color:#eab308;font-weight:bold'
|
|
||||||
);
|
|
||||||
store.dispatch(setToken({}));
|
store.dispatch(setToken({}));
|
||||||
console.groupEnd(); // 🔚 MAIN GROUP
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'JWT Error - Token expired or malformed',
|
message: 'JWT Error - Token expired or malformed',
|
||||||
} as R;
|
} as R;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
console.groupEnd(); // 🔚 MAIN GROUP
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message:
|
message:
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ export default function BottomTab() {
|
|||||||
screenOptions={{
|
screenOptions={{
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
tabBarHideOnKeyboard: true,
|
tabBarHideOnKeyboard: true,
|
||||||
|
// Smooth cross-fade when switching tabs instead of an instant cut.
|
||||||
|
animation: 'shift',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tab.Screen name="Home" component={Screens.Home} />
|
<Tab.Screen name="Home" component={Screens.Home} />
|
||||||
|
|||||||
@@ -1,42 +1,49 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { createStackNavigator } from '@react-navigation/stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import NavigationString from '../Constant/NavigationString';
|
import NavigationString from '../Constant/NavigationString';
|
||||||
import Screens from '../Screens';
|
import Screens from '../Screens';
|
||||||
import colors from '../Constant/Color';
|
import colors from '../Constant/Color';
|
||||||
import BottomTab from './Bottom';
|
import BottomTab from './Bottom';
|
||||||
import Login from '../Screens/Login/Login';
|
import Login from '../Screens/Login/Login';
|
||||||
|
|
||||||
|
const Stack = createNativeStackNavigator();
|
||||||
|
|
||||||
export default function MainStack() {
|
export default function MainStack() {
|
||||||
const Stack = createStackNavigator();
|
|
||||||
const option = { headerShown: false };
|
|
||||||
// ...
|
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
// ...
|
screenOptions={{
|
||||||
|
headerShown: false,
|
||||||
|
// Native-thread driven transitions for smooth, jank-free navigation.
|
||||||
|
animation: 'slide_from_right',
|
||||||
|
animationDuration: 250,
|
||||||
|
gestureEnabled: true,
|
||||||
|
// Avoids a white/black flash between screens during the transition.
|
||||||
|
contentStyle: { backgroundColor: colors.white },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Stack.Screen name={NavigationString.Splash} component={Screens.Splash} options={option} />
|
<Stack.Screen name={NavigationString.Splash} component={Screens.Splash} options={{ animation: 'fade' }} />
|
||||||
<Stack.Screen name={NavigationString.Signup} component={Screens.Signup} options={option} />
|
<Stack.Screen name={NavigationString.Signup} component={Screens.Signup} />
|
||||||
<Stack.Screen name={NavigationString.Onboarding} component={Screens.Onboarding} options={option} />
|
<Stack.Screen name={NavigationString.Onboarding} component={Screens.Onboarding} />
|
||||||
<Stack.Screen name={NavigationString.Login} component={Login} options={option} />
|
<Stack.Screen name={NavigationString.Login} component={Login} options={{ animation: 'fade' }} />
|
||||||
<Stack.Screen name={NavigationString.Home} component={BottomTab} options={option} />
|
<Stack.Screen name={NavigationString.Home} component={BottomTab} options={{ animation: 'fade', gestureEnabled: false }} />
|
||||||
<Stack.Screen name={NavigationString.PurchaseList} component={Screens.PurchaseList} options={option} />
|
<Stack.Screen name={NavigationString.PurchaseList} component={Screens.PurchaseList} />
|
||||||
<Stack.Screen name={NavigationString.Emd} component={Screens.Emd} options={option} />
|
<Stack.Screen name={NavigationString.Emd} component={Screens.Emd} />
|
||||||
<Stack.Screen name={NavigationString.StartBidding} component={Screens.StartBidding} options={option} />
|
<Stack.Screen name={NavigationString.StartBidding} component={Screens.StartBidding} />
|
||||||
<Stack.Screen name={NavigationString.PurchaseAuctionList} component={Screens.PurchaseAuctionList} options={option} />
|
<Stack.Screen name={NavigationString.PurchaseAuctionList} component={Screens.PurchaseAuctionList} />
|
||||||
<Stack.Screen name={NavigationString.PurchaseTenderList} component={Screens.PurchaseTenderList} options={option} />
|
<Stack.Screen name={NavigationString.PurchaseTenderList} component={Screens.PurchaseTenderList} />
|
||||||
<Stack.Screen name={NavigationString.ProceedToPurTender} component={Screens.ProceedToPurTender} options={option} />
|
<Stack.Screen name={NavigationString.ProceedToPurTender} component={Screens.ProceedToPurTender} />
|
||||||
<Stack.Screen name={NavigationString.TenderBidding} component={Screens.TenderBidding} options={option} />
|
<Stack.Screen name={NavigationString.TenderBidding} component={Screens.TenderBidding} />
|
||||||
<Stack.Screen name={NavigationString.ProceedToBidDetail} component={Screens.ProceedToBidDetail} options={option} />
|
<Stack.Screen name={NavigationString.ProceedToBidDetail} component={Screens.ProceedToBidDetail} />
|
||||||
<Stack.Screen name={NavigationString.StartReverseBidding} component={Screens.StartReverseBidding} options={option} />
|
<Stack.Screen name={NavigationString.StartReverseBidding} component={Screens.StartReverseBidding} />
|
||||||
<Stack.Screen name={NavigationString.Registration} component={Screens.Registration} options={option} />
|
<Stack.Screen name={NavigationString.Registration} component={Screens.Registration} />
|
||||||
<Stack.Screen name={NavigationString.Partnership} component={Screens.Partnership} options={option} />
|
<Stack.Screen name={NavigationString.Partnership} component={Screens.Partnership} />
|
||||||
<Stack.Screen name={NavigationString.LiveTenderDetail} component={Screens.LiveTenderDetail} options={option} />
|
<Stack.Screen name={NavigationString.LiveTenderDetail} component={Screens.LiveTenderDetail} />
|
||||||
<Stack.Screen name={NavigationString.ResetPassword} component={Screens.ResetPassword} options={option} />
|
<Stack.Screen name={NavigationString.ResetPassword} component={Screens.ResetPassword} />
|
||||||
<Stack.Screen name={NavigationString.KycWebViewScreen} component={Screens.KycWebViewScreen} options={option} />
|
<Stack.Screen name={NavigationString.KycWebViewScreen} component={Screens.KycWebViewScreen} />
|
||||||
<Stack.Screen name={NavigationString.StageThreeScreen} component={Screens.StageThreeScreen} options={option} />
|
<Stack.Screen name={NavigationString.StageThreeScreen} component={Screens.StageThreeScreen} />
|
||||||
<Stack.Screen name={NavigationString.DecryptionScreen} component={Screens.DecryptionScreen} options={option} />
|
<Stack.Screen name={NavigationString.DecryptionScreen} component={Screens.DecryptionScreen} />
|
||||||
<Stack.Screen name={NavigationString.DeviceBinding} component={Screens.DeviceBinding} options={option} />
|
<Stack.Screen name={NavigationString.DeviceBinding} component={Screens.DeviceBinding} />
|
||||||
<Stack.Screen name={NavigationString.DscBinding} component={Screens.DscBinding} options={option} />
|
<Stack.Screen name={NavigationString.DscBinding} component={Screens.DscBinding} />
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { View, Text, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, TouchableOpacity, Dimensions, StatusBar, TextInput, Image } from 'react-native';
|
import { View, Text, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, TouchableOpacity, Dimensions, StatusBar, TextInput, Image, ActivityIndicator, Keyboard } from 'react-native';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
@@ -43,7 +43,7 @@ const Login = () => {
|
|||||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [activeSlide, setActiveSlide] = useState(0);
|
const [activeSlide, setActiveSlide] = useState(0);
|
||||||
const { mutateAsync: registrationStateMutate } = useRagistrationState();
|
const { mutateAsync: registrationStateMutate, isPending: isResolvingRoute } = useRagistrationState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showForm) return;
|
if (showForm) return;
|
||||||
@@ -211,6 +211,9 @@ const Login = () => {
|
|||||||
const handleSuccess = (res: any) => {
|
const handleSuccess = (res: any) => {
|
||||||
dispatch(setToken({ ...res.data, token: res.token, userType: 'tender' }));
|
dispatch(setToken({ ...res.data, token: res.token, userType: 'tender' }));
|
||||||
console.log('handleSuccess', res)
|
console.log('handleSuccess', res)
|
||||||
|
console.log('==================== 🔑 AUTH TOKEN ====================');
|
||||||
|
console.log('TOKEN →', res?.token);
|
||||||
|
console.log('=======================================================');
|
||||||
if (res.statusCode === 200) {
|
if (res.statusCode === 200) {
|
||||||
handleGetData()
|
handleGetData()
|
||||||
} else {
|
} else {
|
||||||
@@ -219,12 +222,18 @@ const Login = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLogin = (values: any) => {
|
const handleLogin = (values: any) => {
|
||||||
|
if (isLoading) return; // guard against double submit
|
||||||
|
Keyboard.dismiss(); // smooth the navigation transition (no keyboard mid-replace)
|
||||||
const payload = { emailAdd: values.email, password: values.password, device: "mobileApp" };
|
const payload = { emailAdd: values.email, password: values.password, device: "mobileApp" };
|
||||||
console.log('payload------------', payload)
|
console.log('payload------------', payload)
|
||||||
loginMutation.mutate(payload);
|
loginMutation.mutate(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = loginMutation.isPending;
|
// Covers the WHOLE sign-in flow: login request + registration-state resolve
|
||||||
|
// (+ the brief moment before navigation.replace swaps the screen). Without
|
||||||
|
// this, the button reverts to "SIGN IN" between the two requests, which is
|
||||||
|
// the flicker/glitch the user sees.
|
||||||
|
const isLoading = loginMutation.isPending || isResolvingRoute;
|
||||||
|
|
||||||
// Minimal Input Component (Enhanced for better aesthetics)
|
// Minimal Input Component (Enhanced for better aesthetics)
|
||||||
const MinimalInput = ({ placeholder, value, onChangeText, onBlur, icon, secureTextEntry, rightIcon, onRightIconPress }: any) => {
|
const MinimalInput = ({ placeholder, value, onChangeText, onBlur, icon, secureTextEntry, rightIcon, onRightIconPress }: any) => {
|
||||||
@@ -375,7 +384,7 @@ const Login = () => {
|
|||||||
|
|
||||||
<View style={styles.cardInnerContent}>
|
<View style={styles.cardInnerContent}>
|
||||||
<View style={styles.formHeader}>
|
<View style={styles.formHeader}>
|
||||||
<Text style={[styles.formTitle, { color: colors.text }]}>Welcome Back</Text>
|
<Text style={[styles.formTitle, { color: colors.text }]}>Welcome</Text>
|
||||||
<Text style={styles.formSubtitle}>Sign in to your account</Text>
|
<Text style={styles.formSubtitle}>Sign in to your account</Text>
|
||||||
</View>
|
</View>
|
||||||
<Formik
|
<Formik
|
||||||
@@ -413,18 +422,28 @@ const Login = () => {
|
|||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
onPress={() => handleSubmit()}
|
onPress={() => handleSubmit()}
|
||||||
|
disabled={isLoading}
|
||||||
style={styles.loginBtnContainer}
|
style={styles.loginBtnContainer}
|
||||||
>
|
>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[colors.primary, '#f59953']}
|
colors={isLoading ? ['#e8a44c99', '#f5995399'] : [colors.primary, '#f59953']}
|
||||||
start={{ x: 0, y: 0 }}
|
start={{ x: 0, y: 0 }}
|
||||||
end={{ x: 1, y: 0 }}
|
end={{ x: 1, y: 0 }}
|
||||||
style={styles.loginBtn}
|
style={styles.loginBtn}
|
||||||
>
|
>
|
||||||
<Text style={styles.loginBtnText}>
|
{isLoading ? (
|
||||||
{isLoading ? 'Wait...' : 'SIGN IN'}
|
<>
|
||||||
</Text>
|
<ActivityIndicator size="small" color={'#FFF'} />
|
||||||
{!isLoading && <ArrowRight size={22} color={'#FFF'} style={styles.arrowIcon} />}
|
<Text style={[styles.loginBtnText, styles.loginBtnTextLoading]}>
|
||||||
|
SIGNING IN
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Text style={styles.loginBtnText}>SIGN IN</Text>
|
||||||
|
<ArrowRight size={22} color={'#FFF'} style={styles.arrowIcon} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -445,6 +464,17 @@ const Login = () => {
|
|||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
|
|
||||||
|
{/* Full-screen loader — covers the whole login→resolve→navigate flow so
|
||||||
|
the user never sees a half-rendered state or the navigation flicker */}
|
||||||
|
{isLoading && (
|
||||||
|
<View style={styles.loaderOverlay} pointerEvents="auto">
|
||||||
|
<View style={styles.loaderCard}>
|
||||||
|
<ActivityIndicator size="large" color={colors.primary} />
|
||||||
|
<Text style={[styles.loaderText, { color: colors.text }]}>Signing in…</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</AppBackground >
|
</AppBackground >
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -688,10 +718,41 @@ const styles = StyleSheet.create({
|
|||||||
letterSpacing: 1.5,
|
letterSpacing: 1.5,
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
},
|
},
|
||||||
|
loginBtnTextLoading: {
|
||||||
|
marginLeft: 12,
|
||||||
|
},
|
||||||
arrowIcon: {
|
arrowIcon: {
|
||||||
marginLeft: 12,
|
marginLeft: 12,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// FULL-SCREEN LOADER OVERLAY
|
||||||
|
loaderOverlay: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.35)',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 999,
|
||||||
|
},
|
||||||
|
loaderCard: {
|
||||||
|
paddingVertical: 24,
|
||||||
|
paddingHorizontal: 32,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 8 },
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 16,
|
||||||
|
elevation: 10,
|
||||||
|
},
|
||||||
|
loaderText: {
|
||||||
|
marginTop: 12,
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '600',
|
||||||
|
fontFamily: FontsFamily.poppinsMedium,
|
||||||
|
},
|
||||||
|
|
||||||
// FOOTER
|
// FOOTER
|
||||||
footer: {
|
footer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// const BASE_URL = `http://192.168.6.194:8090/`
|
// const BASE_URL = `http://192.168.6.194:8090/`
|
||||||
const BASE_URL = `http://java-backend-main.lincpay/`
|
const BASE_URL = `https://eproc.godemo.in/`
|
||||||
// const BASE_URL = `http://192.168.6.194/AuctionPostgre/api/auction/`
|
// const BASE_URL = `http://192.168.6.194/AuctionPostgre/api/auction/`
|
||||||
// export const BASE_URL = `https://procurelinc.in/Auction/`
|
// export const BASE_URL = `https://procurelinc.in/Auction/`
|
||||||
// const BASE_URL = `http://192.168.29.226:8090/`
|
// const BASE_URL = `http://192.168.29.226:8090/`
|
||||||
@@ -25,7 +25,7 @@ const CONTRACTOR_BASIC_PROFILE = `${BASE_URL}contractor/getContractorById?contId
|
|||||||
const CONTRACTOR_PROFILE_UPDATE = `${BASE_URL}contractor/updateContBasicDetails?id=`
|
const CONTRACTOR_PROFILE_UPDATE = `${BASE_URL}contractor/updateContBasicDetails?id=`
|
||||||
const GET_REGISTRATION_STATE = `${BASE_URL}contractor/getContractorOrgTypeandStatus`
|
const GET_REGISTRATION_STATE = `${BASE_URL}contractor/getContractorOrgTypeandStatus`
|
||||||
const GET_MASTER_DATA = `${BASE_URL}contractor/masterdata/all`
|
const GET_MASTER_DATA = `${BASE_URL}contractor/masterdata/all`
|
||||||
const GET_TENDER_BY_ID = `http://192.168.6.194:8090/eTendering/getTenderById?tenderId=`
|
const GET_TENDER_BY_ID = `${BASE_URL}eTendering/getTenderById?tenderId=`
|
||||||
const LOG_IN_URL = `${BASE_URL}auction/loginWithoutDecryption`
|
const LOG_IN_URL = `${BASE_URL}auction/loginWithoutDecryption`
|
||||||
const LIVE_TENDER = `${BASE_URL}liveTender/getAllLiveTenders`
|
const LIVE_TENDER = `${BASE_URL}liveTender/getAllLiveTenders`
|
||||||
const LIVE_AUCTION = `${BASE_URL}api/auction/getLiveAuctionForMobile`
|
const LIVE_AUCTION = `${BASE_URL}api/auction/getLiveAuctionForMobile`
|
||||||
|
|||||||
Reference in New Issue
Block a user