diff --git a/android/app/release/app-release.apk b/android/app/release/app-release.apk deleted file mode 100644 index 3fa37e6..0000000 Binary files a/android/app/release/app-release.apk and /dev/null differ diff --git a/android/app/release/baselineProfiles/0/app-release.dm b/android/app/release/baselineProfiles/0/app-release.dm deleted file mode 100644 index 5b14806..0000000 Binary files a/android/app/release/baselineProfiles/0/app-release.dm and /dev/null differ diff --git a/android/app/release/baselineProfiles/1/app-release.dm b/android/app/release/baselineProfiles/1/app-release.dm deleted file mode 100644 index d504419..0000000 Binary files a/android/app/release/baselineProfiles/1/app-release.dm and /dev/null differ diff --git a/android/app/release/output-metadata.json b/android/app/release/output-metadata.json deleted file mode 100644 index 8c8d5bf..0000000 --- a/android/app/release/output-metadata.json +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/android/app/src/debug/res/xml/network_security_config.xml b/android/app/src/debug/res/xml/network_security_config.xml new file mode 100644 index 0000000..ce127bc --- /dev/null +++ b/android/app/src/debug/res/xml/network_security_config.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 86fcf14..15c600a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -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"> + + + + diff --git a/package-lock.json b/package-lock.json index cc87f4e..33c4651 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index af3a034..e6803ba 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/Axios/Axios.ts b/src/Axios/Axios.ts index cd34618..025b23e 100644 --- a/src/Axios/Axios.ts +++ b/src/Axios/Axios.ts @@ -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 ( method: 'get' | 'post' | 'patch' | 'delete' | 'put', @@ -15,26 +107,8 @@ export const apiCall = async ( ): Promise => { 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 ( }, }); - // ===== 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: diff --git a/src/Navigation/Bottom.tsx b/src/Navigation/Bottom.tsx index ed6114e..20dc7cb 100644 --- a/src/Navigation/Bottom.tsx +++ b/src/Navigation/Bottom.tsx @@ -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', }} > diff --git a/src/Navigation/MainStack.jsx b/src/Navigation/MainStack.jsx index 3c4a542..e5c1041 100644 --- a/src/Navigation/MainStack.jsx +++ b/src/Navigation/MainStack.jsx @@ -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 ( - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/src/Screens/Login/Login.tsx b/src/Screens/Login/Login.tsx index 8f8678f..7d98094 100644 --- a/src/Screens/Login/Login.tsx +++ b/src/Screens/Login/Login.tsx @@ -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 = () => { - Welcome Back + Welcome Sign in to your account { handleSubmit()} + disabled={isLoading} style={styles.loginBtnContainer} > - - {isLoading ? 'Wait...' : 'SIGN IN'} - - {!isLoading && } + {isLoading ? ( + <> + + + SIGNING IN + + + ) : ( + <> + SIGN IN + + + )} @@ -445,6 +464,17 @@ const Login = () => { + + {/* Full-screen loader β€” covers the whole loginβ†’resolveβ†’navigate flow so + the user never sees a half-rendered state or the navigation flicker */} + {isLoading && ( + + + + Signing in… + + + )} ); }; @@ -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', diff --git a/src/Services/ApiCall/ApiUrls.tsx b/src/Services/ApiCall/ApiUrls.tsx index e518fa2..d867022 100644 --- a/src/Services/ApiCall/ApiUrls.tsx +++ b/src/Services/ApiCall/ApiUrls.tsx @@ -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`