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:
Abin Toppo
2026-06-11 11:19:32 +05:30
co-authored by Claude Opus 4.8
parent 44e84ff6f1
commit 9acec328a7
14 changed files with 260 additions and 157 deletions
Binary file not shown.
-37
View File
@@ -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>
+2
View File
@@ -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>
+42 -15
View File
@@ -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",
+1
View File
@@ -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
View File
@@ -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:
+2
View File
@@ -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} />
+35 -28
View File
@@ -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>
); );
} }
+70 -9
View File
@@ -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',
+2 -2
View File
@@ -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`