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:allowBackup="false"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:supportsRtl="true">
|
||||
<activity
|
||||
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/drawer": "^7.8.1",
|
||||
"@react-navigation/native": "^7.1.28",
|
||||
"@react-navigation/native-stack": "^7.17.3",
|
||||
"@react-navigation/stack": "7.6.13",
|
||||
"@reduxjs/toolkit": "2.11.2",
|
||||
"@stomp/stompjs": "7.2.1",
|
||||
@@ -3666,12 +3667,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/core": {
|
||||
"version": "7.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.17.2.tgz",
|
||||
"integrity": "sha512-Rt2OZwcgOmjv401uLGAKaRM6xo0fiBce/A7LfRHI1oe5FV+KooWcgAoZ2XOtgKj6UzVMuQWt3b2e6rxo/mDJRA==",
|
||||
"version": "7.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.20.0.tgz",
|
||||
"integrity": "sha512-Lqw5cDQWWxiQnaWv6RhQV95Wr4fh+38/IFVNn1grssyLWV+wXGJjlucXOoU7EVh9jdtcLT8pGyzvsyrvSDywWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^7.5.3",
|
||||
"@react-navigation/routers": "^7.6.0",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"nanoid": "^3.3.11",
|
||||
@@ -3706,9 +3707,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/elements": {
|
||||
"version": "2.9.15",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.15.tgz",
|
||||
"integrity": "sha512-cyz/pPiyyC6gaTVLsGFc1g0MYgrmuCFqklAWGXMWPscr5YU3ui94vPI4vnZwcsEy0T758TQWLzmS5XudZeRKcA==",
|
||||
"version": "2.9.23",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.23.tgz",
|
||||
"integrity": "sha512-sp+FgihDyMBoEXoCUsUCT/iibN/sg6LYGq/rciy6NjT8bnfv4Cu3el8SAaJ0bfRG3tdchHy6gweKmcaJs/BAYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
@@ -3717,7 +3718,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@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-native": "*",
|
||||
"react-native-safe-area-context": ">= 4.0.0"
|
||||
@@ -3729,15 +3730,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/native": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.2.2.tgz",
|
||||
"integrity": "sha512-kem1Ko2BcbAjmbQIv66dNmr6EtfDut3QU0qjsVhMnLLhktwyXb6FzZYp8gTrUb6AvkAbaJoi+BF5Pl55pAUa5w==",
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.3.1.tgz",
|
||||
"integrity": "sha512-g1o8jBm87WviR0Eq0wT0M43TSi+uBTz4x8YfHh4XRQ+FHqhNr+uGbuxtGu72QhHtOz0LWnb8UWyvd+M6xWkWHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^7.17.2",
|
||||
"@react-navigation/core": "^7.20.0",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"nanoid": "^3.3.11",
|
||||
"standard-navigation": "^0.0.7",
|
||||
"use-latest-callback": "^0.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -3745,10 +3747,29 @@
|
||||
"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": {
|
||||
"version": "7.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz",
|
||||
"integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==",
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.6.0.tgz",
|
||||
"integrity": "sha512-lblhDXfS75jLc7G2K7BZGM+7cjqQXk13X/MA4fq/12r62zM+fBhhreLzYflSitrDDXFRJpSvJXy0ziiGU04Xow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11"
|
||||
@@ -14334,6 +14355,12 @@
|
||||
"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": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@react-navigation/bottom-tabs": "7.9.0",
|
||||
"@react-navigation/drawer": "^7.8.1",
|
||||
"@react-navigation/native": "^7.1.28",
|
||||
"@react-navigation/native-stack": "^7.17.3",
|
||||
"@react-navigation/stack": "7.6.13",
|
||||
"@reduxjs/toolkit": "2.11.2",
|
||||
"@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 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 = {}>(
|
||||
method: 'get' | 'post' | 'patch' | 'delete' | 'put',
|
||||
@@ -15,26 +107,8 @@ export const apiCall = async <R, D = {}>(
|
||||
): Promise<R> => {
|
||||
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 {
|
||||
// Request/response/error are logged globally by the axios interceptors above.
|
||||
const response = await axios({
|
||||
method,
|
||||
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;
|
||||
} 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 =====
|
||||
if (error?.response?.status === 401) {
|
||||
console.warn(
|
||||
'%c⚠ JWT ERROR → Token expired or malformed',
|
||||
'color:#eab308;font-weight:bold'
|
||||
);
|
||||
console.warn('⚠ JWT ERROR → Token expired or malformed');
|
||||
store.dispatch(setToken({}));
|
||||
console.groupEnd(); // 🔚 MAIN GROUP
|
||||
return {
|
||||
success: false,
|
||||
message: 'JWT Error - Token expired or malformed',
|
||||
} as R;
|
||||
}
|
||||
|
||||
|
||||
|
||||
console.groupEnd(); // 🔚 MAIN GROUP
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
|
||||
@@ -193,6 +193,8 @@ export default function BottomTab() {
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarHideOnKeyboard: true,
|
||||
// Smooth cross-fade when switching tabs instead of an instant cut.
|
||||
animation: 'shift',
|
||||
}}
|
||||
>
|
||||
<Tab.Screen name="Home" component={Screens.Home} />
|
||||
|
||||
@@ -1,42 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import NavigationString from '../Constant/NavigationString';
|
||||
import Screens from '../Screens';
|
||||
import colors from '../Constant/Color';
|
||||
import BottomTab from './Bottom';
|
||||
import Login from '../Screens/Login/Login';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
export default function MainStack() {
|
||||
const Stack = createStackNavigator();
|
||||
const option = { headerShown: false };
|
||||
// ...
|
||||
return (
|
||||
<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.Signup} component={Screens.Signup} options={option} />
|
||||
<Stack.Screen name={NavigationString.Onboarding} component={Screens.Onboarding} options={option} />
|
||||
<Stack.Screen name={NavigationString.Login} component={Login} options={option} />
|
||||
<Stack.Screen name={NavigationString.Home} component={BottomTab} options={option} />
|
||||
<Stack.Screen name={NavigationString.PurchaseList} component={Screens.PurchaseList} options={option} />
|
||||
<Stack.Screen name={NavigationString.Emd} component={Screens.Emd} options={option} />
|
||||
<Stack.Screen name={NavigationString.StartBidding} component={Screens.StartBidding} options={option} />
|
||||
<Stack.Screen name={NavigationString.PurchaseAuctionList} component={Screens.PurchaseAuctionList} options={option} />
|
||||
<Stack.Screen name={NavigationString.PurchaseTenderList} component={Screens.PurchaseTenderList} options={option} />
|
||||
<Stack.Screen name={NavigationString.ProceedToPurTender} component={Screens.ProceedToPurTender} options={option} />
|
||||
<Stack.Screen name={NavigationString.TenderBidding} component={Screens.TenderBidding} options={option} />
|
||||
<Stack.Screen name={NavigationString.ProceedToBidDetail} component={Screens.ProceedToBidDetail} options={option} />
|
||||
<Stack.Screen name={NavigationString.StartReverseBidding} component={Screens.StartReverseBidding} options={option} />
|
||||
<Stack.Screen name={NavigationString.Registration} component={Screens.Registration} options={option} />
|
||||
<Stack.Screen name={NavigationString.Partnership} component={Screens.Partnership} options={option} />
|
||||
<Stack.Screen name={NavigationString.LiveTenderDetail} component={Screens.LiveTenderDetail} options={option} />
|
||||
<Stack.Screen name={NavigationString.ResetPassword} component={Screens.ResetPassword} options={option} />
|
||||
<Stack.Screen name={NavigationString.KycWebViewScreen} component={Screens.KycWebViewScreen} options={option} />
|
||||
<Stack.Screen name={NavigationString.StageThreeScreen} component={Screens.StageThreeScreen} options={option} />
|
||||
<Stack.Screen name={NavigationString.DecryptionScreen} component={Screens.DecryptionScreen} options={option} />
|
||||
<Stack.Screen name={NavigationString.DeviceBinding} component={Screens.DeviceBinding} options={option} />
|
||||
<Stack.Screen name={NavigationString.DscBinding} component={Screens.DscBinding} options={option} />
|
||||
<Stack.Screen name={NavigationString.Splash} component={Screens.Splash} options={{ animation: 'fade' }} />
|
||||
<Stack.Screen name={NavigationString.Signup} component={Screens.Signup} />
|
||||
<Stack.Screen name={NavigationString.Onboarding} component={Screens.Onboarding} />
|
||||
<Stack.Screen name={NavigationString.Login} component={Login} options={{ animation: 'fade' }} />
|
||||
<Stack.Screen name={NavigationString.Home} component={BottomTab} options={{ animation: 'fade', gestureEnabled: false }} />
|
||||
<Stack.Screen name={NavigationString.PurchaseList} component={Screens.PurchaseList} />
|
||||
<Stack.Screen name={NavigationString.Emd} component={Screens.Emd} />
|
||||
<Stack.Screen name={NavigationString.StartBidding} component={Screens.StartBidding} />
|
||||
<Stack.Screen name={NavigationString.PurchaseAuctionList} component={Screens.PurchaseAuctionList} />
|
||||
<Stack.Screen name={NavigationString.PurchaseTenderList} component={Screens.PurchaseTenderList} />
|
||||
<Stack.Screen name={NavigationString.ProceedToPurTender} component={Screens.ProceedToPurTender} />
|
||||
<Stack.Screen name={NavigationString.TenderBidding} component={Screens.TenderBidding} />
|
||||
<Stack.Screen name={NavigationString.ProceedToBidDetail} component={Screens.ProceedToBidDetail} />
|
||||
<Stack.Screen name={NavigationString.StartReverseBidding} component={Screens.StartReverseBidding} />
|
||||
<Stack.Screen name={NavigationString.Registration} component={Screens.Registration} />
|
||||
<Stack.Screen name={NavigationString.Partnership} component={Screens.Partnership} />
|
||||
<Stack.Screen name={NavigationString.LiveTenderDetail} component={Screens.LiveTenderDetail} />
|
||||
<Stack.Screen name={NavigationString.ResetPassword} component={Screens.ResetPassword} />
|
||||
<Stack.Screen name={NavigationString.KycWebViewScreen} component={Screens.KycWebViewScreen} />
|
||||
<Stack.Screen name={NavigationString.StageThreeScreen} component={Screens.StageThreeScreen} />
|
||||
<Stack.Screen name={NavigationString.DecryptionScreen} component={Screens.DecryptionScreen} />
|
||||
<Stack.Screen name={NavigationString.DeviceBinding} component={Screens.DeviceBinding} />
|
||||
<Stack.Screen name={NavigationString.DscBinding} component={Screens.DscBinding} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 * as Yup from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -43,7 +43,7 @@ const Login = () => {
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [activeSlide, setActiveSlide] = useState(0);
|
||||
const { mutateAsync: registrationStateMutate } = useRagistrationState();
|
||||
const { mutateAsync: registrationStateMutate, isPending: isResolvingRoute } = useRagistrationState();
|
||||
|
||||
useEffect(() => {
|
||||
if (showForm) return;
|
||||
@@ -211,6 +211,9 @@ const Login = () => {
|
||||
const handleSuccess = (res: any) => {
|
||||
dispatch(setToken({ ...res.data, token: res.token, userType: 'tender' }));
|
||||
console.log('handleSuccess', res)
|
||||
console.log('==================== 🔑 AUTH TOKEN ====================');
|
||||
console.log('TOKEN →', res?.token);
|
||||
console.log('=======================================================');
|
||||
if (res.statusCode === 200) {
|
||||
handleGetData()
|
||||
} else {
|
||||
@@ -219,12 +222,18 @@ const Login = () => {
|
||||
};
|
||||
|
||||
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" };
|
||||
console.log('payload------------', 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)
|
||||
const MinimalInput = ({ placeholder, value, onChangeText, onBlur, icon, secureTextEntry, rightIcon, onRightIconPress }: any) => {
|
||||
@@ -375,7 +384,7 @@ const Login = () => {
|
||||
|
||||
<View style={styles.cardInnerContent}>
|
||||
<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>
|
||||
</View>
|
||||
<Formik
|
||||
@@ -413,18 +422,28 @@ const Login = () => {
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.8}
|
||||
onPress={() => handleSubmit()}
|
||||
disabled={isLoading}
|
||||
style={styles.loginBtnContainer}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={[colors.primary, '#f59953']}
|
||||
colors={isLoading ? ['#e8a44c99', '#f5995399'] : [colors.primary, '#f59953']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
style={styles.loginBtn}
|
||||
>
|
||||
<Text style={styles.loginBtnText}>
|
||||
{isLoading ? 'Wait...' : 'SIGN IN'}
|
||||
</Text>
|
||||
{!isLoading && <ArrowRight size={22} color={'#FFF'} style={styles.arrowIcon} />}
|
||||
{isLoading ? (
|
||||
<>
|
||||
<ActivityIndicator size="small" color={'#FFF'} />
|
||||
<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>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -445,6 +464,17 @@ const Login = () => {
|
||||
|
||||
</ScrollView>
|
||||
</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 >
|
||||
);
|
||||
};
|
||||
@@ -688,10 +718,41 @@ const styles = StyleSheet.create({
|
||||
letterSpacing: 1.5,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
loginBtnTextLoading: {
|
||||
marginLeft: 12,
|
||||
},
|
||||
arrowIcon: {
|
||||
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: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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/`
|
||||
// export const BASE_URL = `https://procurelinc.in/Auction/`
|
||||
// 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 GET_REGISTRATION_STATE = `${BASE_URL}contractor/getContractorOrgTypeandStatus`
|
||||
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 LIVE_TENDER = `${BASE_URL}liveTender/getAllLiveTenders`
|
||||
const LIVE_AUCTION = `${BASE_URL}api/auction/getLiveAuctionForMobile`
|
||||
|
||||
Reference in New Issue
Block a user