diff --git a/.gitignore b/.gitignore index de99955..7108897 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,7 @@ yarn-error.log # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/ +# http://docs.fastlane.tools/best-practices/source-control/ **/fastlane/report.xml **/fastlane/Preview.html diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e012065 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "java.compile.nullAnalysis.mode": "automatic", + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/App.tsx b/App.tsx index 5e7da2f..83f0382 100644 --- a/App.tsx +++ b/App.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; -import { LogBox, StatusBar, View } from 'react-native'; +import { LogBox, View } from 'react-native'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AlertNotificationRoot } from 'react-native-alert-notification'; import { PersistGate } from 'redux-persist/integration/react'; @@ -11,6 +11,7 @@ import { NavigationContainer } from '@react-navigation/native'; import colors from './src/Constant/Color'; import { TextEncoder } from 'text-encoding'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import AppStatusBar from './src/components/AppStatusBar'; LogBox.ignoreAllLogs(true); const App = () => { @@ -25,7 +26,7 @@ const App = () => { - + @@ -38,3 +39,4 @@ const App = () => { }; export default App; + diff --git a/Gemfile b/Gemfile index 6a4c5f1..56e246d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ -source 'https://rubygems.org' +source 'http://rubygems.org' -# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version +# You may use http://rbenv.org/ or http://rvm.io/ to install and use this version ruby ">= 2.6.10" # Exclude problematic versions of cocoapods and activesupport that causes build failures. diff --git a/README.md b/README.md index 3e2c3f8..0db004b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). +This is a new [**React Native**](http://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](http://github.com/react-native-community/cli). # Getting Started -> **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding. +> **Note**: Make sure you have completed the [Set Up Your Environment](http://reactnative.dev/docs/set-up-your-environment) guide before proceeding. ## Step 1: Start Metro @@ -48,7 +48,7 @@ Then, and every time you update your native dependencies, run: bundle exec pod install ``` -For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html). +For more information, please visit [CocoaPods Getting Started guide](http://guides.cocoapods.org/using/getting-started.html). ```sh # Using npm @@ -66,7 +66,7 @@ This is one way to run your app — you can also build it directly from Android Now that you have successfully run the app, let's make changes! -Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes — this is powered by [Fast Refresh](https://reactnative.dev/docs/fast-refresh). +Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes — this is powered by [Fast Refresh](http://reactnative.dev/docs/fast-refresh). When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload: @@ -79,19 +79,19 @@ You've successfully run and modified your React Native App. :partying_face: ### Now what? -- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). -- If you're curious to learn more about React Native, check out the [docs](https://reactnative.dev/docs/getting-started). +- If you want to add this new React Native code to an existing application, check out the [Integration guide](http://reactnative.dev/docs/integration-with-existing-apps). +- If you're curious to learn more about React Native, check out the [docs](http://reactnative.dev/docs/getting-started). # Troubleshooting -If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. +If you're having issues getting the above steps to work, see the [Troubleshooting](http://reactnative.dev/docs/troubleshooting) page. # Learn More To learn more about React Native, take a look at the following resources: -- [React Native Website](https://reactnative.dev) - learn more about React Native. -- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. -- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. -- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. -- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. +- [React Native Website](http://reactnative.dev) - learn more about React Native. +- [Getting Started](http://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. +- [Learn the Basics](http://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. +- [Blog](http://reactnative.dev/blog) - read the latest official React Native **Blog** posts. +- [`@facebook/react-native`](http://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs index 2850d00..08fe375 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -1,4 +1,4 @@ -arguments=--init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Code\\User\\globalStorage\\redhat.java\\1.51.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\init\\init.gradle --init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Code\\User\\globalStorage\\redhat.java\\1.51.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\protobuf\\init.gradle +arguments=--init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Antigravity\\User\\globalStorage\\redhat.java\\1.54.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\init\\init.gradle --init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Antigravity\\User\\globalStorage\\redhat.java\\1.54.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\protobuf\\init.gradle --init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Antigravity\\User\\globalStorage\\redhat.java\\1.54.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\kotlin\\init.gradle --init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Antigravity\\User\\globalStorage\\redhat.java\\1.54.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\groovy\\init.gradle --init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Antigravity\\User\\globalStorage\\redhat.java\\1.54.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\scala\\init.gradle auto.sync=false build.scans.enabled=false connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) diff --git a/android/app/appKeyStore b/android/app/appKeyStore new file mode 100644 index 0000000..76c6a52 Binary files /dev/null and b/android/app/appKeyStore differ diff --git a/android/app/build.gradle b/android/app/build.gradle index 1b69817..ddd4804 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -40,7 +40,7 @@ react { // entryFile = file("../js/MyApplication.android.js") // // A list of extra flags to pass to the 'bundle' commands. - // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle + // See http://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle // extraPackagerArgs = [] /* Hermes Commands */ @@ -99,7 +99,7 @@ android { } release { // Caution! In production, you need to generate your own keystore file. - // see https://reactnative.dev/docs/signed-apk-android. + // see http://reactnative.dev/docs/signed-apk-android. signingConfig signingConfigs.debug minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" diff --git a/android/app/release/app-release.apk b/android/app/release/app-release.apk new file mode 100644 index 0000000..3fa37e6 Binary files /dev/null and b/android/app/release/app-release.apk differ diff --git a/android/app/release/baselineProfiles/0/app-release.dm b/android/app/release/baselineProfiles/0/app-release.dm new file mode 100644 index 0000000..5b14806 Binary files /dev/null and b/android/app/release/baselineProfiles/0/app-release.dm differ diff --git a/android/app/release/baselineProfiles/1/app-release.dm b/android/app/release/baselineProfiles/1/app-release.dm new file mode 100644 index 0000000..d504419 Binary files /dev/null and b/android/app/release/baselineProfiles/1/app-release.dm differ diff --git a/android/app/release/output-metadata.json b/android/app/release/output-metadata.json new file mode 100644 index 0000000..8c8d5bf --- /dev/null +++ b/android/app/release/output-metadata.json @@ -0,0 +1,37 @@ +{ + "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/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 70a16ac..86fcf14 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + + diff --git a/android/gradle.properties b/android/gradle.properties index 5e24e3a..965957c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -10,7 +10,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m -org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m +org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit @@ -19,13 +19,13 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn +# http://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Use this property to specify which architecture you want to build. # You can also override it from the CLI using # ./gradlew -PreactNativeArchitectures=x86_64 -reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 +reactNativeArchitectures=armeabi-v7a,arm64-v8a # Use this property to enable support to the new architecture. # This will allow you to use TurboModules and the Fabric render in diff --git a/android/gradlew b/android/gradlew index faf9300..af55910 100644 --- a/android/gradlew +++ b/android/gradlew @@ -7,7 +7,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# https://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -57,10 +57,10 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# http://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # -# You can find Gradle at https://github.com/gradle/gradle/. +# You can find Gradle at http://github.com/gradle/gradle/. # ############################################################################## @@ -85,7 +85,7 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +# Discard cd standard output in case $CDPATH is set (http://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. diff --git a/android/gradlew.bat b/android/gradlew.bat index 9d21a21..126d351 100644 --- a/android/gradlew.bat +++ b/android/gradlew.bat @@ -5,7 +5,7 @@ @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem -@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem http://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, diff --git a/ios/Podfile b/ios/Podfile index d170564..dc9f648 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -24,7 +24,7 @@ target 'procure' do ) post_install do |installer| - # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 + # http://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 react_native_post_install( installer, config[:reactNativePath], diff --git a/ios/procure/Info.plist b/ios/procure/Info.plist index c8b4298..12d7fcc 100644 --- a/ios/procure/Info.plist +++ b/ios/procure/Info.plist @@ -32,7 +32,7 @@ NSLocationWhenInUseUsageDescription - + We need your location to securely bind this device for tender bidding verification. UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/metro.config.js b/metro.config.js index d47357f..dee28ce 100644 --- a/metro.config.js +++ b/metro.config.js @@ -1,3 +1,4 @@ +const path = require("path"); const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config"); const defaultConfig = getDefaultConfig(__dirname); @@ -5,7 +6,7 @@ const { assetExts, sourceExts } = defaultConfig.resolver; /** * Metro configuration - * https://reactnative.dev/docs/metro + * http://reactnative.dev/docs/metro * * @type {import('metro-config').MetroConfig} */ @@ -17,7 +18,22 @@ const config = { }, resolver: { assetExts: assetExts.filter((ext) => ext !== "svg"), - sourceExts: [...sourceExts, "svg"] + sourceExts: [...sourceExts, "svg"], + blockList: [ + // Exclude native Android build artifacts that are created/deleted + // during Gradle builds, which crash Metro's file watcher (ENOENT) + /node_modules\/.*\/android\/\.cxx\/.*/, + /node_modules\/.*\/android\/build\/.*/, + /node_modules\/.*\/android\/\.gradle\/.*/, + ] + }, + watcher: { + additionalExclusions: [ + // Also exclude from the watcher layer for safety + "**/android/.cxx/**", + "**/android/build/**", + "**/android/.gradle/**", + ] } }; diff --git a/package-lock.json b/package-lock.json index 607b24d..cc87f4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,11 @@ "@react-native-async-storage/async-storage": "2.2.0", "@react-native-community/blur": "4.4.1", "@react-native-documents/picker": "11.0.3", - "@react-native-masked-view/masked-view": "0.3.2", + "@react-native-masked-view/masked-view": "0.2.9", "@react-native-vector-icons/material-icons": "12.4.0", "@react-navigation/bottom-tabs": "7.9.0", - "@react-navigation/native": "7.1.26", + "@react-navigation/drawer": "^7.8.1", + "@react-navigation/native": "^7.1.28", "@react-navigation/stack": "7.6.13", "@reduxjs/toolkit": "2.11.2", "@stomp/stompjs": "7.2.1", @@ -37,16 +38,18 @@ "react-native-blob-util": "0.24.6", "react-native-bouncy-checkbox": "4.1.2", "react-native-date-picker": "5.0.13", + "react-native-device-info": "^15.0.2", "react-native-element-dropdown": "2.12.4", "react-native-fs": "2.20.0", - "react-native-gesture-handler": "2.30.0", - "react-native-get-random-values": "2.0.0", + "react-native-geolocation-service": "^5.3.1", + "react-native-gesture-handler": "^2.30.0", + "react-native-get-random-values": "1.11.0", "react-native-gifted-charts": "1.4.70", "react-native-haptic-feedback": "2.3.3", - "react-native-linear-gradient": "2.8.3", - "react-native-modal": "14.0.0-rc.1", + "react-native-linear-gradient": "^2.8.3", + "react-native-modal": "^14.0.0-rc.1", "react-native-permissions": "5.4.4", - "react-native-reanimated": "3.18.0", + "react-native-reanimated": "^3.18.0", "react-native-responsive-screen": "1.4.2", "react-native-safe-area-context": "5.6.2", "react-native-screens": "4.12.0", @@ -67,7 +70,7 @@ "@babel/preset-env": "7.25.3", "@babel/runtime": "7.25.0", "@react-native-community/cli": "18.0.1", - "@react-native-community/cli-platform-android": "18.0.0", + "@react-native-community/cli-platform-android": "^18.0.0", "@react-native-community/cli-platform-ios": "18.0.0", "@react-native/babel-preset": "0.79.0", "@react-native/eslint-config": "0.79.0", @@ -79,7 +82,7 @@ "@types/react-test-renderer": "19.0.0", "@types/sockjs-client": "1.5.4", "@types/text-encoding": "0.0.40", - "eslint": "8.19.0", + "eslint": "^8.57.0", "jest": "29.6.3", "jsencrypt": "^3.5.4", "prettier": "2.8.8", @@ -105,9 +108,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -119,9 +122,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -177,13 +180,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -221,9 +224,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.3.tgz", + "integrity": "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -231,7 +234,7 @@ "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", + "@babel/traverse": "^7.29.0", "semver": "^6.3.1" }, "engines": { @@ -259,9 +262,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", - "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "dev": true, "license": "MIT", "dependencies": { @@ -439,25 +442,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -908,15 +911,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz", - "integrity": "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1096,9 +1099,9 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", - "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", "dev": true, "license": "MIT", "dependencies": { @@ -1310,16 +1313,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1346,14 +1349,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1614,9 +1617,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz", - "integrity": "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "dev": true, "license": "MIT", "dependencies": { @@ -1646,14 +1649,14 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz", - "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", @@ -2003,17 +2006,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -2022,17 +2025,17 @@ }, "node_modules/@babel/traverse--for-generate-function-map": { "name": "@babel/traverse", - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -2040,9 +2043,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -2075,7 +2078,6 @@ "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2094,7 +2096,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2107,22 +2108,20 @@ "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", - "dev": true, + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -2138,10 +2137,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2149,10 +2147,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2161,6 +2158,15 @@ "node": "*" } }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@fast-csv/format": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", @@ -2200,40 +2206,38 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@hapi/topo": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", - "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2241,10 +2245,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2253,35 +2256,26 @@ "node": "*" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "license": "Apache-2.0", "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" + "node": ">=12.22" }, - "engines": { - "node": "20 || >=22" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "license": "BSD-3-Clause" + }, "node_modules/@isaacs/ttlcache": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", @@ -2391,9 +2385,9 @@ } }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "license": "MIT", "engines": { "node": ">=8" @@ -2792,7 +2786,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -2806,7 +2799,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -2816,7 +2808,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -2852,7 +2843,7 @@ "version": "18.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-18.0.1.tgz", "integrity": "sha512-nEEYwfyP00j9i4nr/HhwPabDscoBhhCjxDBpcKERi2oTYHcBr3FTu9PV1gbeJCa4vRCHr6b6VgOcVTdy99NddQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@react-native-community/cli-clean": "18.0.1", @@ -2882,7 +2873,7 @@ "version": "18.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-18.0.1.tgz", "integrity": "sha512-brXqk//lmA2Vs5lGq9YLhk7X4IYBSrDRte6t1AktouJpCrH4Tp1sl45yJDS2CHOi/OY1oOfI3kA61tXNX5a/5A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@react-native-community/cli-tools": "18.0.1", @@ -2895,7 +2886,7 @@ "version": "18.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-18.0.1.tgz", "integrity": "sha512-O4DDJVMx+DYfwEgF/6lB4hoI9sVjrYW6AlLqeJY/D2XH2e4yqK/Pr3SAi4sOMgvjvYZKzLHqIQVxx54v+LyMQA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@react-native-community/cli-tools": "18.0.1", @@ -2907,55 +2898,23 @@ } }, "node_modules/@react-native-community/cli-config-android": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-18.0.0.tgz", - "integrity": "sha512-pgnhEO2cmOeb+bBFEBZFYjeFjDTqWoV0JTorTiugj9bb4RQRCl8cr35baVlBGhxAuaio3722CsJ9GRF1oHjP8w==", - "dev": true, + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-18.0.1.tgz", + "integrity": "sha512-1wzmGLfS7qgzm0ZfwX/f6Lat/af8/UYdjwtb3ap6RfKNclvIoap0wN6uBeiANmLfk0/BhoG8K1vKtIPwlU/V1A==", + "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "18.0.0", + "@react-native-community/cli-tools": "18.0.1", "chalk": "^4.1.2", "fast-glob": "^3.3.2", "fast-xml-parser": "^4.4.1" } }, - "node_modules/@react-native-community/cli-config-android/node_modules/@react-native-community/cli-tools": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-18.0.0.tgz", - "integrity": "sha512-oR6FcDEcSDYos79vZy4+Tj8jgAE0Xf5HEiRXMJFGISYLRx7tvslSaK8SodUOW9TZe2bCZOb5QSvj8zeMpORmxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vscode/sudo-prompt": "^9.0.0", - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "launch-editor": "^2.9.1", - "mime": "^2.4.1", - "ora": "^5.4.1", - "prompts": "^2.4.2", - "semver": "^7.5.2" - } - }, - "node_modules/@react-native-community/cli-config-android/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@react-native-community/cli-config-apple": { "version": "18.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-18.0.1.tgz", "integrity": "sha512-ybr1ZrOSd/Z+oCJ1qVSKVQauvneObTu3VjvYPhhrme7tUUSaYmd3iikaWonbKk5rVp+2WqOFR6Cy7XqVfwwG8A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@react-native-community/cli-tools": "18.0.1", @@ -2968,7 +2927,7 @@ "version": "18.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-18.0.1.tgz", "integrity": "sha512-B1UWpiVeJ45DX0ip1Et62knAHLzeA1B3XcTJu16PscednnNxV6GBH52kRUoWMsB8HQ8f9IWdFTol8LAp5Y0wwg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@react-native-community/cli-config": "18.0.1", @@ -2988,48 +2947,21 @@ "yaml": "^2.2.1" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/@react-native-community/cli-config-android": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-18.0.1.tgz", - "integrity": "sha512-1wzmGLfS7qgzm0ZfwX/f6Lat/af8/UYdjwtb3ap6RfKNclvIoap0wN6uBeiANmLfk0/BhoG8K1vKtIPwlU/V1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "18.0.1", - "chalk": "^4.1.2", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.4.1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/@react-native-community/cli-platform-android": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-18.0.1.tgz", - "integrity": "sha512-DCltVWDR7jfZZG5MXREVKG0fmIr1b0irEhmdkk/R87dG6HJ8tGXWXnAa4Kap8bx2v6lKFXDW5QxNecyyLCOkVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-config-android": "18.0.1", - "@react-native-community/cli-tools": "18.0.1", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "logkitty": "^0.7.1" - } - }, "node_modules/@react-native-community/cli-doctor/node_modules/@react-native-community/cli-platform-ios": { "version": "18.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-18.0.1.tgz", "integrity": "sha512-GtO1FB+xaz+vcHIdvl94AkD5B8Y+H8XHb6QwnYX+A3WwteGsHrR8iD/bLLcRtNPtLaAWMa/RgWJpgs4KG+eU4w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@react-native-community/cli-platform-apple": "18.0.1" } }, "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3039,56 +2971,24 @@ } }, "node_modules/@react-native-community/cli-platform-android": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-18.0.0.tgz", - "integrity": "sha512-3Y3RleN/des1C3oRS6s6fDvFYKN0KwsLrYFRpVx9vzdDnH1OGyFJOy4DbrruSPtdNiHUpvvHnOOxeLMj0+/tmw==", - "dev": true, + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-18.0.1.tgz", + "integrity": "sha512-DCltVWDR7jfZZG5MXREVKG0fmIr1b0irEhmdkk/R87dG6HJ8tGXWXnAa4Kap8bx2v6lKFXDW5QxNecyyLCOkVw==", + "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-config-android": "18.0.0", - "@react-native-community/cli-tools": "18.0.0", + "@react-native-community/cli-config-android": "18.0.1", + "@react-native-community/cli-tools": "18.0.1", "chalk": "^4.1.2", "execa": "^5.0.0", "logkitty": "^0.7.1" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/@react-native-community/cli-tools": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-18.0.0.tgz", - "integrity": "sha512-oR6FcDEcSDYos79vZy4+Tj8jgAE0Xf5HEiRXMJFGISYLRx7tvslSaK8SodUOW9TZe2bCZOb5QSvj8zeMpORmxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vscode/sudo-prompt": "^9.0.0", - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "launch-editor": "^2.9.1", - "mime": "^2.4.1", - "ora": "^5.4.1", - "prompts": "^2.4.2", - "semver": "^7.5.2" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@react-native-community/cli-platform-apple": { "version": "18.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-18.0.1.tgz", "integrity": "sha512-7WxGXT/ui7VtyLVjx5rkYkkTMlbufI6p5BdRKjGp/zQDnDzs/0rle0JEHJxwgvs5VQnt+VOnHBMipkQAhydIqQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@react-native-community/cli-config-apple": "18.0.1", @@ -3155,9 +3055,9 @@ } }, "node_modules/@react-native-community/cli-platform-ios/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3171,7 +3071,7 @@ "version": "18.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-18.0.1.tgz", "integrity": "sha512-ZRy2IjEM4ljP05bZcnXho0sCxVGI/9SkWkLuzXl+cRu/4I8vLRleihn2GJCopg82QHLLrajUCHhpDKE8NJWcRw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@react-native-community/cli-tools": "18.0.1", @@ -3190,7 +3090,7 @@ "version": "18.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-18.0.1.tgz", "integrity": "sha512-WxWFXwfYhHR2eYiB4lkHZVC/PmIkRWeVHBQKmn0h1mecr3GrHYO4BzW1jpD5Xt6XZ9jojQ9wE5xrCqXjiMSAIQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vscode/sudo-prompt": "^9.0.0", @@ -3206,10 +3106,10 @@ } }, "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3222,17 +3122,17 @@ "version": "18.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-18.0.1.tgz", "integrity": "sha512-pGxr/TSP9Xiw2+9TUn3OWLdcuI4+PJozPsCYZVTGWJ96X6Pv7YX/rNy4emIDkaWaFZ7IWgWXUA725KhEINSf3Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "joi": "^17.2.1" } }, "node_modules/@react-native-community/cli/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3252,9 +3152,9 @@ } }, "node_modules/@react-native-masked-view/masked-view": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.3.2.tgz", - "integrity": "sha512-XwuQoW7/GEgWRMovOQtX3A4PrXhyaZm0lVUiY8qJDvdngjLms9Cpdck6SmGAUNqQwcj2EadHC1HwL0bEyoa/SQ==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.2.9.tgz", + "integrity": "sha512-Hs4vKBKj+15VxHZHFtMaFWSBxXoOE5Ea8saoigWhahp8Mepssm0ezU+2pTl7DK9z8Y9s5uOl/aPb4QmBZ3R3Zw==", "license": "MIT", "peerDependencies": { "react": ">=16", @@ -3262,9 +3162,9 @@ } }, "node_modules/@react-native-vector-icons/common": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/@react-native-vector-icons/common/-/common-12.4.0.tgz", - "integrity": "sha512-t9W0q+AW7WH1Oj5aEg7wGNXDLZJb5sIVkAWo5qtad3PcbBADqoCdikRK/ToLK+xlB0TxjcuL0T74ogudMkYGeA==", + "version": "12.4.2", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/common/-/common-12.4.2.tgz", + "integrity": "sha512-15ViuwfYdkQ79VE6PiQjsGyQcEvhv4lgguhIeQU4pXLXRAFhl89Q8FdKDrGZh7+a/8WMqT1v2+JtghdoGKDE6Q==", "license": "MIT", "dependencies": { "find-up": "^7.0.0", @@ -3532,9 +3432,9 @@ "license": "MIT" }, "node_modules/@react-native/community-cli-plugin/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3747,29 +3647,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.79.0", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.0.tgz", - "integrity": "sha512-tCT1sHSI1O5KSclDwNfnkLTLe3cgiyYWjIlmNxWJHqhCCz017HGOS/oH0zs0ZgxYwN7xCzTkqY330XMDo+yj2g==", - "license": "MIT", - "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/react": "^19.0.0", - "react": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@react-navigation/bottom-tabs": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.9.0.tgz", @@ -3789,9 +3666,9 @@ } }, "node_modules/@react-navigation/core": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.14.0.tgz", - "integrity": "sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.17.2.tgz", + "integrity": "sha512-Rt2OZwcgOmjv401uLGAKaRM6xo0fiBce/A7LfRHI1oe5FV+KooWcgAoZ2XOtgKj6UzVMuQWt3b2e6rxo/mDJRA==", "license": "MIT", "dependencies": { "@react-navigation/routers": "^7.5.3", @@ -3807,10 +3684,31 @@ "react": ">= 18.2.0" } }, + "node_modules/@react-navigation/drawer": { + "version": "7.9.9", + "resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-7.9.9.tgz", + "integrity": "sha512-ZeHhx5MH7Y/qG+28KU0PDtBjNcNnpvnafPwIoSzSrN8M55HvtQex90TP3ylmHtErhw2RDWlp30vpmWvG0wvFIA==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.9.15", + "color": "^4.2.3", + "react-native-drawer-layout": "^4.2.2", + "use-latest-callback": "^0.2.4" + }, + "peerDependencies": { + "@react-navigation/native": "^7.2.2", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-gesture-handler": ">= 2.0.0", + "react-native-reanimated": ">= 2.0.0", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, "node_modules/@react-navigation/elements": { - "version": "2.9.5", - "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.5.tgz", - "integrity": "sha512-iHZU8rRN1014Upz73AqNVXDvSMZDh5/ktQ1CMe21rdgnOY79RWtHHBp9qOS3VtqlUVYGkuX5GEw5mDt4tKdl0g==", + "version": "2.9.15", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.15.tgz", + "integrity": "sha512-cyz/pPiyyC6gaTVLsGFc1g0MYgrmuCFqklAWGXMWPscr5YU3ui94vPI4vnZwcsEy0T758TQWLzmS5XudZeRKcA==", "license": "MIT", "dependencies": { "color": "^4.2.3", @@ -3819,7 +3717,7 @@ }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", - "@react-navigation/native": "^7.1.28", + "@react-navigation/native": "^7.2.2", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" @@ -3831,12 +3729,12 @@ } }, "node_modules/@react-navigation/native": { - "version": "7.1.26", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.26.tgz", - "integrity": "sha512-RhKmeD0E2ejzKS6z8elAfdfwShpcdkYY8zJzvHYLq+wv183BBcElTeyMLcIX6wIn7QutXeI92Yi21t7aUWfqNQ==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.2.2.tgz", + "integrity": "sha512-kem1Ko2BcbAjmbQIv66dNmr6EtfDut3QU0qjsVhMnLLhktwyXb6FzZYp8gTrUb6AvkAbaJoi+BF5Pl55pAUa5w==", "license": "MIT", "dependencies": { - "@react-navigation/core": "^7.13.7", + "@react-navigation/core": "^7.17.2", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", @@ -3905,7 +3803,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" @@ -3915,20 +3813,20 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "license": "MIT" }, "node_modules/@sinonjs/commons": { @@ -4291,15 +4189,6 @@ "react": "^18 || ^19" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4462,7 +4351,6 @@ "version": "19.0.0", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.0.tgz", "integrity": "sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -4679,9 +4567,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -4745,20 +4633,26 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, "node_modules/@vscode/sudo-prompt": { "version": "9.3.2", "resolved": "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz", "integrity": "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@xmldom/xmldom": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", - "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", + "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=14.6" } }, "node_modules/abort-controller": { @@ -4796,9 +4690,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4811,7 +4705,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -4836,10 +4729,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -4891,7 +4783,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "colorette": "^1.0.7", @@ -4903,7 +4795,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -4913,7 +4805,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^4.1.0" @@ -4963,7 +4855,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/archiver": { @@ -5198,7 +5090,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -5326,14 +5218,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", - "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { @@ -5355,13 +5247,13 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", - "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.6" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -5460,12 +5352,15 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.10.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", + "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/big-integer": { @@ -5532,10 +5427,10 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "dev": true, + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "devOptional": true, "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -5546,7 +5441,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -5560,7 +5455,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -5570,7 +5465,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/boolbase": { @@ -5580,9 +5475,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -5601,9 +5496,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "funding": [ { "type": "opencollective", @@ -5620,11 +5515,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -5715,21 +5610,21 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -5820,9 +5715,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", "funding": [ { "type": "opencollective", @@ -5972,7 +5867,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" @@ -5985,7 +5880,7 @@ "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -6012,7 +5907,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -6090,7 +5985,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -6109,14 +6004,14 @@ "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/commander": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^12.20.0 || >=14" @@ -6141,7 +6036,7 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" @@ -6154,7 +6049,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -6173,7 +6068,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -6183,7 +6078,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/concat-map": { @@ -6226,7 +6121,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -6239,9 +6134,9 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", - "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", "dev": true, "license": "MIT", "dependencies": { @@ -6259,10 +6154,10 @@ "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "devOptional": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.1", @@ -6336,7 +6231,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6431,7 +6325,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -6486,9 +6379,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", "license": "MIT" }, "node_modules/debug": { @@ -6512,7 +6405,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6528,9 +6421,9 @@ } }, "node_modules/dedent": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", - "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -6546,7 +6439,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -6562,7 +6454,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "clone": "^1.0.2" @@ -6680,7 +6572,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" @@ -6843,9 +6734,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.282", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.282.tgz", - "integrity": "sha512-FCPkJtpst28UmFzd903iU7PdeVTfY0KAeJy+Lk0GLZRwgwYHn/irRcaCbQQOmr5Vytc/7rcavsYLvTM8RiHYhQ==", + "version": "1.5.349", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz", + "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==", "license": "ISC" }, "node_modules/emittery": { @@ -6901,7 +6792,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -6911,7 +6802,7 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "envinfo": "dist/cli.js" @@ -6942,7 +6833,7 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.2.tgz", "integrity": "sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -6957,9 +6848,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -7043,15 +6934,15 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", - "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.24.1", + "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", @@ -7063,7 +6954,7 @@ "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", - "safe-array-concat": "^1.1.3" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -7153,48 +7044,50 @@ } }, "node_modules/eslint": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", - "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, "license": "MIT", "dependencies": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.2", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" @@ -7414,9 +7307,9 @@ } }, "node_modules/eslint-plugin-jest/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7490,9 +7383,9 @@ "license": "MIT" }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7512,9 +7405,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -7524,18 +7417,24 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7564,25 +7463,6 @@ "node": ">=4.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, "node_modules/eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", @@ -7594,10 +7474,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7608,7 +7487,6 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -7625,7 +7503,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7635,10 +7512,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -7651,7 +7527,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", @@ -7669,7 +7544,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7695,7 +7569,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -7708,7 +7581,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -7786,6 +7658,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "license": "MIT", "bin": { "uuid": "dist/bin/uuid" @@ -7795,7 +7668,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -7876,7 +7749,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -7893,7 +7766,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -7912,14 +7785,13 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, "license": "MIT" }, "node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", - "dev": true, + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.6.tgz", + "integrity": "sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==", + "devOptional": true, "funding": [ { "type": "github", @@ -7928,7 +7800,7 @@ ], "license": "MIT", "dependencies": { - "strnum": "^1.1.1" + "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" @@ -7938,7 +7810,6 @@ "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -7969,7 +7840,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" @@ -7979,9 +7849,9 @@ } }, "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7989,9 +7859,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -8071,7 +7941,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -8088,7 +7957,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -8100,10 +7968,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "license": "ISC" }, "node_modules/flow-enums-runtime": { @@ -8113,9 +7980,9 @@ "license": "MIT" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -8225,7 +8092,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -8326,13 +8193,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true, - "license": "MIT" - }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -8419,7 +8279,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -8460,7 +8320,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -8481,7 +8341,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -8491,9 +8350,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -8501,9 +8360,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -8516,7 +8375,6 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.20.2" @@ -8587,7 +8445,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, "license": "MIT" }, "node_modules/gzip-size": { @@ -8682,9 +8539,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -8782,7 +8639,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=10.17.0" @@ -8792,7 +8649,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -8825,7 +8682,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -8853,9 +8709,9 @@ "license": "MIT" }, "node_modules/immer": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", - "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "version": "11.1.6", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.6.tgz", + "integrity": "sha512-uwrF08UBQfxk49i9WcUeCx045wjB1zXEHNJmbYHPVVspxmjwSeWCoKbB8DEIvs3XkBJV6lcRAyLaWJ2+u3MMCw==", "license": "MIT", "funding": { "type": "opencollective", @@ -9108,7 +8964,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9133,7 +8988,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -9172,7 +9027,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -9185,7 +9039,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -9240,6 +9094,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -9298,7 +9161,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -9359,7 +9222,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -9415,7 +9278,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -9431,7 +9294,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -9461,9 +9323,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -10306,9 +10168,9 @@ "license": "MIT" }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -10450,7 +10312,7 @@ "version": "17.13.3", "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.3.0", @@ -10507,7 +10369,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, "license": "MIT" }, "node_modules/json-parse-better-errors": { @@ -10526,14 +10387,12 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, "license": "MIT" }, "node_modules/json5": { @@ -10552,7 +10411,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, + "devOptional": true, "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" @@ -10625,7 +10484,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -10635,17 +10493,17 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/launch-editor": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", - "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", - "dev": true, + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", + "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", + "devOptional": true, "license": "MIT", "dependencies": { "picocolors": "^1.1.1", @@ -10713,7 +10571,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -10773,7 +10630,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -10786,15 +10642,15 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -10875,7 +10731,6 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, "license": "MIT" }, "node_modules/lodash.throttle": { @@ -10900,7 +10755,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -10917,7 +10772,7 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-fragments": "^0.2.1", @@ -10932,7 +10787,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -10944,7 +10799,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -10958,7 +10813,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -10971,7 +10826,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -10987,7 +10842,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -11000,7 +10855,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -11015,14 +10870,14 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/logkitty/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "cliui": "^6.0.0", @@ -11045,7 +10900,7 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "camelcase": "^5.0.0", @@ -11133,9 +10988,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -11179,7 +11034,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -11213,7 +11068,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 8" @@ -11661,7 +11516,7 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "mime": "cli.js" @@ -11674,7 +11529,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -11705,20 +11560,20 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -11737,10 +11592,10 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -11773,9 +11628,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "funding": [ { "type": "github", @@ -11794,14 +11649,13 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -11821,12 +11675,30 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12.0.0" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -11834,16 +11706,16 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", "license": "MIT" }, "node_modules/node-stream-zip": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -11866,7 +11738,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.0.0" @@ -12022,7 +11894,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -12041,7 +11913,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -12057,7 +11929,7 @@ "version": "6.4.0", "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-wsl": "^1.1.0" @@ -12070,7 +11942,6 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -12088,7 +11959,7 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "bl": "^4.1.0", @@ -12129,7 +12000,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -12145,7 +12015,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -12239,7 +12108,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12252,25 +12120,25 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -12292,9 +12160,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -12382,12 +12250,12 @@ } }, "node_modules/plist": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", - "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", + "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", "license": "MIT", "dependencies": { - "@xmldom/xmldom": "^0.8.8", + "@xmldom/xmldom": "^0.9.10", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" }, @@ -12408,7 +12276,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -12434,7 +12301,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/types": "^26.6.2", @@ -12450,7 +12317,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -12467,7 +12334,7 @@ "version": "15.0.20", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.20.tgz", "integrity": "sha512-KIkX+/GgfFitlASYCGoSF+T4XRXhOubJLhkLVtSfsRTe9jWMmuM2g28zQ41BtPTG7TRBb2xHW+LCNVE9QR/vsg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -12477,7 +12344,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/process-nextick-args": { @@ -12499,7 +12366,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "kleur": "^3.0.3", @@ -12542,7 +12409,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -12566,10 +12432,10 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", - "dev": true, + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -12618,7 +12484,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -12648,7 +12513,7 @@ "version": "2.5.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -12719,9 +12584,9 @@ } }, "node_modules/react-is": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", - "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", + "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==", "license": "MIT" }, "node_modules/react-native": { @@ -12827,6 +12692,27 @@ "react-native": "*" } }, + "node_modules/react-native-blob-util/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/react-native-blob-util/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/react-native-blob-util/node_modules/glob": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", @@ -12845,15 +12731,15 @@ } }, "node_modules/react-native-blob-util/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -12915,9 +12801,9 @@ } }, "node_modules/react-native-bundle-visualizer/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -12965,6 +12851,31 @@ "react-native": ">= 0.64.3" } }, + "node_modules/react-native-device-info": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-15.0.2.tgz", + "integrity": "sha512-dd71eXG2l3Cwp66IvKNadMTB8fhU3PEjyVddI97sYan+D4bgIAUmgGDhbSOFvHcGavksb2U17kiQYaDiK2WK2g==", + "license": "MIT", + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/react-native-drawer-layout": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-4.2.2.tgz", + "integrity": "sha512-UG/PTTeyyr43KahbgoGyXri8LMO5USHY3/RUpeKBKwCc7xLVGnDLOVNSRrJw0dDc7YmPbmAyJ4oxp8nKboKKuw==", + "license": "MIT", + "dependencies": { + "color": "^4.2.3", + "use-latest-callback": "^0.2.4" + }, + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*", + "react-native-gesture-handler": ">= 2.0.0", + "react-native-reanimated": ">= 2.0.0" + } + }, "node_modules/react-native-element-dropdown": { "version": "2.12.4", "resolved": "https://registry.npmjs.org/react-native-element-dropdown/-/react-native-element-dropdown-2.12.4.tgz", @@ -13000,13 +12911,20 @@ } } }, + "node_modules/react-native-geolocation-service": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-native-geolocation-service/-/react-native-geolocation-service-5.3.1.tgz", + "integrity": "sha512-LTXPtPNmrdhx+yeWG47sAaCgQc3nG1z+HLLHlhK/5YfOgfLcAb9HAkhREPjQKPZOUx8pKZMIpdGFUGfJYtimXQ==", + "license": "MIT" + }, "node_modules/react-native-gesture-handler": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.30.0.tgz", - "integrity": "sha512-5YsnKHGa0X9C8lb5oCnKm0fLUPM6CRduvUUw2Bav4RIj/C3HcFh4RIUnF8wgG6JQWCL1//gRx4v+LVWgcIQdGA==", + "version": "2.31.2", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.31.2.tgz", + "integrity": "sha512-rw5q74i2AfS7YGYdbxQDhOU7xqgY6WRM1132/CCm3erqjblhECZDZFHIm0tteHoC9ih24wogVBVVzcTBQtZ+5A==", "license": "MIT", "dependencies": { "@egjs/hammerjs": "^2.0.17", + "@types/react-test-renderer": "^19.1.0", "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4" }, @@ -13015,16 +12933,25 @@ "react-native": "*" } }, + "node_modules/react-native-gesture-handler/node_modules/@types/react-test-renderer": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz", + "integrity": "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/react-native-get-random-values": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-native-get-random-values/-/react-native-get-random-values-2.0.0.tgz", - "integrity": "sha512-wx7/aPqsUIiWsG35D+MsUJd8ij96e3JKddklSdrdZUrheTx89gPtz3Q2yl9knBArj5u26Cl23T88ai+Q0vypdQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz", + "integrity": "sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ==", "license": "MIT", "dependencies": { "fast-base64-decode": "^1.0.0" }, "peerDependencies": { - "react-native": ">=0.81" + "react-native": ">=0.56" } }, "node_modules/react-native-gifted-charts": { @@ -13113,9 +13040,9 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.18.0", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.18.0.tgz", - "integrity": "sha512-eVcNcqeOkMW+BUWAHdtvN3FKgC8J8wiEJkX6bNGGQaLS7m7e4amTfjIcqf/Ta+lerZLurmDaQ0lICI1CKPrb1Q==", + "version": "3.19.5", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.19.5.tgz", + "integrity": "sha512-bd4AwIkBAaY4BjrgpSoKjEaRG/tXD756F5nGuiH5IMBSKN8tRdUEA8hWZCyIo/R6/kha/tVSoCqodVUACh7ZWw==", "license": "MIT", "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", @@ -13228,6 +13155,29 @@ "react-native": "*" } }, + "node_modules/react-native/node_modules/@react-native/virtualized-lists": { + "version": "0.79.0", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.0.tgz", + "integrity": "sha512-tCT1sHSI1O5KSclDwNfnkLTLe3cgiyYWjIlmNxWJHqhCCz017HGOS/oH0zs0ZgxYwN7xCzTkqY330XMDo+yj2g==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": "^19.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-native/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -13276,9 +13226,9 @@ "license": "MIT" }, "node_modules/react-native/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -13357,9 +13307,9 @@ } }, "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -13458,19 +13408,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/regexpu-core": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", @@ -13495,9 +13432,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" @@ -13519,7 +13456,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/requires-port": { @@ -13535,12 +13472,13 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" @@ -13601,7 +13539,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "onetime": "^5.1.0", @@ -13615,7 +13553,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -13642,7 +13579,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -13663,14 +13599,14 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -13738,9 +13674,18 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -13874,7 +13819,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/set-function-length": { @@ -13948,7 +13893,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -13961,7 +13905,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13999,13 +13942,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -14076,7 +14019,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/slash": { @@ -14092,7 +14035,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.0", @@ -14107,7 +14050,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" @@ -14120,7 +14063,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" @@ -14130,7 +14073,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/snake-case": { @@ -14594,7 +14537,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -14604,7 +14547,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -14617,7 +14559,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -14657,18 +14599,18 @@ "license": "MIT" }, "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", + "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", "license": "MIT", "dependencies": { - "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.0.0", + "sax": "^1.5.0" }, "bin": { "svgo": "bin/svgo" @@ -14767,9 +14709,9 @@ } }, "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.2.tgz", + "integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -14815,9 +14757,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -14825,9 +14767,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -14847,7 +14789,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, "license": "MIT" }, "node_modules/throat": { @@ -14965,7 +14906,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -14987,7 +14927,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -15000,7 +14939,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "media-typer": "0.3.0", @@ -15088,7 +15027,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -15172,7 +15111,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4.0.0" @@ -15275,7 +15214,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -15343,13 +15281,6 @@ "uuid": "dist-node/bin/uuid" } }, - "node_modules/v8-compile-cache": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", - "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", - "dev": true, - "license": "MIT" - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -15369,7 +15300,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -15400,7 +15331,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "defaults": "^1.0.3" @@ -15439,7 +15370,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -15519,7 +15449,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/which-typed-array": { @@ -15565,7 +15495,6 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -15668,10 +15597,10 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "dev": true, + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "devOptional": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -15714,7 +15643,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 24d053f..af3a034 100644 --- a/package.json +++ b/package.json @@ -7,16 +7,18 @@ "ios": "react-native run-ios", "lint": "eslint .", "start": "react-native start", - "test": "jest" + "test": "jest", + "create:users": "node scripts/create-users.js" }, "dependencies": { "@react-native-async-storage/async-storage": "2.2.0", "@react-native-community/blur": "4.4.1", "@react-native-documents/picker": "11.0.3", - "@react-native-masked-view/masked-view": "0.3.2", + "@react-native-masked-view/masked-view": "0.2.9", "@react-native-vector-icons/material-icons": "12.4.0", "@react-navigation/bottom-tabs": "7.9.0", - "@react-navigation/native": "7.1.26", + "@react-navigation/drawer": "^7.8.1", + "@react-navigation/native": "^7.1.28", "@react-navigation/stack": "7.6.13", "@reduxjs/toolkit": "2.11.2", "@stomp/stompjs": "7.2.1", @@ -39,16 +41,18 @@ "react-native-blob-util": "0.24.6", "react-native-bouncy-checkbox": "4.1.2", "react-native-date-picker": "5.0.13", + "react-native-device-info": "^15.0.2", "react-native-element-dropdown": "2.12.4", "react-native-fs": "2.20.0", - "react-native-gesture-handler": "2.30.0", - "react-native-get-random-values": "2.0.0", + "react-native-geolocation-service": "^5.3.1", + "react-native-gesture-handler": "^2.30.0", + "react-native-get-random-values": "1.11.0", "react-native-gifted-charts": "1.4.70", "react-native-haptic-feedback": "2.3.3", - "react-native-linear-gradient": "2.8.3", - "react-native-modal": "14.0.0-rc.1", + "react-native-linear-gradient": "^2.8.3", + "react-native-modal": "^14.0.0-rc.1", "react-native-permissions": "5.4.4", - "react-native-reanimated": "3.18.0", + "react-native-reanimated": "^3.18.0", "react-native-responsive-screen": "1.4.2", "react-native-safe-area-context": "5.6.2", "react-native-screens": "4.12.0", @@ -69,7 +73,7 @@ "@babel/preset-env": "7.25.3", "@babel/runtime": "7.25.0", "@react-native-community/cli": "18.0.1", - "@react-native-community/cli-platform-android": "18.0.0", + "@react-native-community/cli-platform-android": "^18.0.0", "@react-native-community/cli-platform-ios": "18.0.0", "@react-native/babel-preset": "0.79.0", "@react-native/eslint-config": "0.79.0", @@ -81,7 +85,7 @@ "@types/react-test-renderer": "19.0.0", "@types/sockjs-client": "1.5.4", "@types/text-encoding": "0.0.40", - "eslint": "8.19.0", + "eslint": "^8.57.0", "jest": "29.6.3", "jsencrypt": "^3.5.4", "prettier": "2.8.8", diff --git a/rn-config-output.json b/rn-config-output.json new file mode 100644 index 0000000..558fa06 Binary files /dev/null and b/rn-config-output.json differ diff --git a/scripts/create-users.js b/scripts/create-users.js new file mode 100644 index 0000000..a3c380d --- /dev/null +++ b/scripts/create-users.js @@ -0,0 +1,179 @@ +#!/usr/bin/env node + +const axios = require('axios'); +const { BASE_URL } = require('../src/Services/ApiCall/ApiUrls'); + +const REGISTER_URL = + `${BASE_URL}contractor/registerContractorStage1`; +const DESIGNATION_URL = + `${BASE_URL}contractor/getAllDesignationContractor`; +const DEFAULT_PASSWORD = 'P@ssword123'; + +function getArgValue(name, fallback) { + const prefix = `--${name}=`; + const arg = process.argv.find(item => item.startsWith(prefix)); + if (!arg) return fallback; + return arg.slice(prefix.length); +} + +function toPositiveInt(value, fallback) { + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed <= 0) return fallback; + return parsed; +} + +function buildMobile(seed, index) { + // Keep 10 digits and start with 9 to match app-side validation. + const dynamicPart = (seed * 1000 + index) % 999999999; + return String(9000000000 + dynamicPart).padStart(10, '9').slice(0, 10); +} + +function toAlphabeticSuffix(index) { + let n = index; + let label = ''; + while (n > 0) { + const rem = (n - 1) % 26; + label = String.fromCharCode(65 + rem) + label; + n = Math.floor((n - 1) / 26); + } + return label || 'A'; +} + +async function getDefaultDesignation() { + try { + const { data } = await axios.get(DESIGNATION_URL, { timeout: 15000 }); + const list = data?.data; + if (Array.isArray(list) && list.length > 0) { + const firstId = Number(list[0]?.designationid); + if (Number.isFinite(firstId) && firstId > 0) return firstId; + } + } catch (error) { + const message = error?.response?.data?.message || error?.message || 'Unknown error'; + console.warn(`Failed to fetch designation list, using fallback id=1. ${message}`); + } + return 1; +} + +async function registerUser({ + designation, + emailPrefix, + firstNamePrefix, + seed, + index, + password, +}) { + const serial = seed * 1000 + index; + const emailAdd = `${emailPrefix}${serial}@example.com`; + const alphaSuffix = toAlphabeticSuffix(index); + const payload = { + firstName: `${firstNamePrefix}${alphaSuffix}`, + middleName: '', + lastName: 'Auto', + dialCode: '+91', + mobNo: buildMobile(seed, index), + emailAdd, + contractorType: 'Indian', + designation, + password, + otherDesignation: null, + conPassword: password, + device: 'mobileApp', + }; + + try { + const { data } = await axios.post(REGISTER_URL, payload, { timeout: 20000 }); + const status = String(data?.status ?? data?.statusCode ?? ''); + const ok = status === '201' || status === '200' || status === 'SUCCESS'; + return { + ok, + email: emailAdd, + mobile: payload.mobNo, + message: data?.message || (ok ? 'Success' : 'Unexpected response'), + rawStatus: status || 'unknown', + details: data, + }; + } catch (error) { + const responseData = error?.response?.data; + const apiMessage = + responseData?.message || + responseData?.error || + error?.message || + 'Request failed'; + return { + ok: false, + email: emailAdd, + mobile: payload.mobNo, + message: apiMessage, + rawStatus: String(error?.response?.status || 'network_error'), + details: responseData, + }; + } +} + +async function main() { + const count = toPositiveInt(getArgValue('count', '100'), 100); + const concurrency = toPositiveInt(getArgValue('concurrency', '5'), 5); + const startIndex = toPositiveInt(getArgValue('start', '1'), 1); + const password = getArgValue('password', DEFAULT_PASSWORD); + const emailPrefix = getArgValue('emailPrefix', 'bulkuser'); + const firstNamePrefix = getArgValue('firstNamePrefix', 'BulkUser'); + const seed = Number(String(Date.now()).slice(-6)); + const designation = await getDefaultDesignation(); + + console.log( + `Creating ${count} users (start=${startIndex}, concurrency=${concurrency}, designation=${designation})...`, + ); + + const indexes = Array.from({ length: count }, (_, i) => i + startIndex); + let cursor = 0; + const results = []; + + async function worker() { + while (true) { + const current = cursor; + cursor += 1; + if (current >= indexes.length) break; + const index = indexes[current]; + const result = await registerUser({ + designation, + emailPrefix, + firstNamePrefix, + seed, + index, + password, + }); + results.push(result); + const marker = result.ok ? 'OK' : 'FAIL'; + console.log(`[${marker}] #${index} ${result.email} -> ${result.message}`); + } + } + + await Promise.all(Array.from({ length: concurrency }, () => worker())); + + const success = results.filter(item => item.ok).length; + const failed = results.length - success; + + console.log('\nSummary'); + console.log(`Total: ${results.length}`); + console.log(`Success: ${success}`); + console.log(`Failed: ${failed}`); + + if (failed > 0) { + console.log('\nFailed entries (up to 20):'); + results + .filter(item => !item.ok) + .slice(0, 20) + .forEach(item => { + console.log(`- ${item.email} (${item.mobile}) [${item.rawStatus}] ${item.message}`); + if (item.details) { + console.log(` details: ${JSON.stringify(item.details)}`); + } + }); + process.exitCode = 1; + } +} + +main().catch(error => { + console.error('Bulk user creation crashed:', error?.message || error); + process.exit(1); +}); diff --git a/src/Axios/Axios.ts b/src/Axios/Axios.ts index f9daacf..bc655c0 100644 --- a/src/Axios/Axios.ts +++ b/src/Axios/Axios.ts @@ -102,6 +102,7 @@ export const apiCall = async ( success: false, message: error?.response?.data?.message || 'No Response From Server', + ...(error?.response?.data || {}) } as R; } }; diff --git a/src/Component/Counter/CountdownTimer.tsx b/src/Component/Counter/CountdownTimer.tsx index e979451..02098d6 100644 --- a/src/Component/Counter/CountdownTimer.tsx +++ b/src/Component/Counter/CountdownTimer.tsx @@ -10,7 +10,7 @@ interface CountdownTimerProps { size?: number } -const CountdownTimer: React.FC = React.memo(({ endDate, myStyle = {}, size = 20 }) => { +const CountdownTimer: React.FC = React.memo(({ endDate, myStyle = {}, size = 20 }) => { const calculateTimeLeft = useCallback(() => { if (!endDate) return null; diff --git a/src/Component/Dropdown/MyDropdown.tsx b/src/Component/Dropdown/MyDropdown.tsx index ec923d1..3c6e2d1 100644 --- a/src/Component/Dropdown/MyDropdown.tsx +++ b/src/Component/Dropdown/MyDropdown.tsx @@ -1,37 +1,8 @@ import React from 'react'; import { StyleSheet, Text, View, StyleProp, ViewStyle, TextStyle } from 'react-native'; import { Dropdown } from 'react-native-element-dropdown'; -import { Shield } from 'lucide-react-native'; - -const colors = { - primary: '#F97316', - primaryLight: '#FDBA74', - primaryHighlight: '#f4f1efff', - secondary: '#1B4B66', - secondaryLight: '#639599', - success: '#22C55E', - error: '#EF4444', - warning: '#F59E0B', - info: '#3B82F6', - golden: '#FFD700', - white: '#FFFFFF', - black: '#000000', - darkText: '#2B2B2B', - grayText: '#6B7280', - lightText: '#9CA3AF', - border: '#E5E7EB', - lightGray: '#F9FAFB', - disabled: '#D1D5DB', - placeholder: '#9CA3AF', - backdrop: 'rgba(0,0,0,0.5)', - shadow: 'rgba(0,0,0,0.12)', - Orange: '#F97316', - OrangeLight: '#FDBA74', - OrangeSoft: '#FFF3E8', - darkGray: '#5f6368', - lightestGray: '#f1f3f4', - successLight: '#e6f4ea', -}; +import { Shield, ChevronDown } from 'lucide-react-native'; +import { useTheme } from '../../Hooks/useTheme'; type DropdownItem = { [key: string]: any; @@ -74,9 +45,22 @@ const MyDropdown: React.FC = ({ dropdownStyle, ...rest }) => { + const { colors, spacing, typography, shadows } = useTheme(); + return ( - - {label && {label}} + + {label && ( + + {label} + + )} = ({ onChange={onChange} style={[ styles.dropdown, + { + backgroundColor: colors.surface, + borderColor: focusedField ? colors.primary : colors.border, + borderWidth: 1.5, + borderRadius: 12, + paddingHorizontal: spacing.m, + paddingVertical: 12, + height: 56, + }, + focusedField && shadows.light, style, - { borderColor: focusedField ? colors.primary : '#e0e0e0' }, ]} - containerStyle={[styles.dropdownContainer, dropdownStyle]} - selectedTextStyle={[styles.selectedText, selectedTextStyle]} - placeholderStyle={[styles.placeholderText, placeholderStyle]} + containerStyle={[ + styles.dropdownContainer, + { + backgroundColor: colors.surface, + borderColor: colors.border, + borderRadius: 12, + ...shadows.medium, + }, + dropdownStyle + ]} + selectedTextStyle={[ + styles.selectedText, + { + color: colors.text, + fontSize: typography.size.m, + fontWeight: '500' + }, + selectedTextStyle + ]} + placeholderStyle={[ + styles.placeholderText, + { + color: colors.warmGray, + fontSize: typography.size.m + }, + placeholderStyle + ]} + itemTextStyle={{ color: colors.textSecondary }} + activeColor={colors.background} // Beige for active item bg onFocus={onFocus} onBlur={onBlur} search - searchPlaceholder="Search Class" + searchPlaceholder="Search..." + inputSearchStyle={{ + borderRadius: 8, + backgroundColor: colors.background, + borderColor: colors.border, + color: colors.text + }} renderLeftIcon={() => ( )} + renderRightIcon={() => ( + + )} {...rest} /> @@ -113,25 +144,19 @@ export default MyDropdown; const styles = StyleSheet.create({ label: { - marginVertical: 10, - color: colors.darkText, + // marginVertical: 10, handled inline }, dropdown: { - borderWidth: 1, - borderRadius: 8, - paddingHorizontal: 10, - paddingVertical: 12, - borderColor: colors.border, - backgroundColor: colors.white, + // handled inline with theme }, dropdownContainer: { - borderRadius: 12, - borderColor: colors.border, + // handled inline + marginTop: 4, }, selectedText: { - color: colors.darkText, + // handled inline }, placeholderText: { - color: colors.placeholder, + // handled inline }, }); diff --git a/src/Component/Header/InsideHeader.tsx b/src/Component/Header/InsideHeader.tsx index 8c75d86..d49ef0a 100644 --- a/src/Component/Header/InsideHeader.tsx +++ b/src/Component/Header/InsideHeader.tsx @@ -2,10 +2,9 @@ import React from 'react'; import { View, Text, TouchableOpacity, Image, StyleSheet, Dimensions } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { ArrowLeft } from 'lucide-react-native'; -import colors from '../../Constant/Color'; +import { useTheme } from '../../Hooks/useTheme'; import ImagePath from '../../Constant/ImagePath'; import { useNavigation } from '@react-navigation/native'; -import FontsFamily from '../../Constant/FontsFamily'; import NavigationString from '../../Constant/NavigationString'; const { width } = Dimensions.get('window'); @@ -18,6 +17,7 @@ interface HeaderProps { const InsideHeader: React.FC = ({ title, showArrow = false }) => { const navigation: any = useNavigation(); const insets = useSafeAreaInsets(); + const { colors, spacing, typography, shadows } = useTheme(); return ( = ({ title, showArrow = false }) => { styles.container, { paddingTop: insets.top - 10, + backgroundColor: colors.background, // Off-white + ...shadows.light, // Soft shadow }, ]} > @@ -36,13 +38,21 @@ const InsideHeader: React.FC = ({ title, showArrow = false }) => { style={styles.backButton} activeOpacity={0.7} > - + )} - {title} + + {title} + + {/* Optional: Add Logo or Right Action */} { navigation.navigate(NavigationString.Home) }}> { return ( <> - diff --git a/src/Component/PrimaryButton/PrimaryButton.tsx b/src/Component/PrimaryButton/PrimaryButton.tsx index 645b4d8..a397c83 100644 --- a/src/Component/PrimaryButton/PrimaryButton.tsx +++ b/src/Component/PrimaryButton/PrimaryButton.tsx @@ -1,4 +1,4 @@ -import { StyleSheet, Text, TouchableOpacity, ViewStyle } from 'react-native'; +import { StyleSheet, Text, TouchableOpacity, View, ViewStyle } from 'react-native'; import React from 'react'; import LinearGradient from 'react-native-linear-gradient'; import colors from '../../Constant/Color'; @@ -7,33 +7,25 @@ import colors from '../../Constant/Color'; type PrimaryButtonProps = { title: string; onPress: () => void | any; - style?: ViewStyle; + style?: ViewStyle; disabled?: boolean; }; const PrimaryButton: React.FC = ({ title, onPress, style, disabled }) => { return ( - - {title} - + ); }; @@ -46,14 +38,14 @@ const styles = StyleSheet.create({ paddingHorizontal: 28, marginBottom: 20, minHeight: 54, - + // Orange-themed shadows shadowColor: colors.primary, shadowOffset: { width: 0, height: 6 }, shadowOpacity: 0.3, shadowRadius: 12, elevation: 10, - + overflow: 'hidden', }, touchable: { diff --git a/src/Component/Serach/SeacrhInput.tsx b/src/Component/Serach/SeacrhInput.tsx index 5b4b556..f2f05ec 100644 --- a/src/Component/Serach/SeacrhInput.tsx +++ b/src/Component/Serach/SeacrhInput.tsx @@ -7,7 +7,7 @@ import Animated, { withTiming, interpolateColor, } from "react-native-reanimated"; -import colors from "../../Constant/Color"; +import { useTheme } from "../../Hooks/useTheme"; export interface SearchType { placeholder?: string; @@ -15,7 +15,7 @@ export interface SearchType { label?: string; filter?: boolean; filterPress?: () => void; - debounceDelay?: number; // 👈 optional + debounceDelay?: number; } const AnimatedView = Animated.createAnimatedComponent(View); @@ -26,8 +26,9 @@ export const SearchInput: React.FC = ({ label, filter = true, filterPress, - debounceDelay = 500, // 👈 default debounce + debounceDelay = 500, }) => { + const { colors, shadows } = useTheme(); const isFocused = useSharedValue(0); const [text, setText] = useState(''); const debounceRef = useRef(null); @@ -36,18 +37,17 @@ export const SearchInput: React.FC = ({ const borderColor = interpolateColor( isFocused.value, [0, 1], - [colors.grayText + '50', colors.grayText] + [colors.overlay, colors.primary] // Use theme overlay/primary ); return { borderColor, - borderWidth: withTiming(isFocused.value ? 1.5 : 1), + borderWidth: withTiming(isFocused.value ? 1 : 1), // Constant thin border transform: [{ scale: withTiming(isFocused.value ? 1.01 : 1) }], - shadowOpacity: withTiming(isFocused.value ? 0.1 : 0), }; }); - // 🔥 Debounce logic + // Debounce logic useEffect(() => { if (debounceRef.current) { clearTimeout(debounceRef.current); @@ -66,34 +66,47 @@ export const SearchInput: React.FC = ({ return ( - {label && {label}} + {label && {label}} - + (isFocused.value = withTiming(1))} onBlur={() => (isFocused.value = withTiming(0))} - selectionColor={colors.grayText} + selectionColor={colors.primary} /> {filter && ( - + )} @@ -110,7 +123,6 @@ const styles = StyleSheet.create({ fontSize: 14, fontWeight: '600', marginBottom: 8, - color: colors.black, letterSpacing: 0.3, }, row: { @@ -122,39 +134,32 @@ const styles = StyleSheet.create({ flex: 1, flexDirection: 'row', alignItems: 'center', - height: 48, - backgroundColor: '#F9FAFB', - borderRadius: 12, - paddingHorizontal: 12, + height: 50, // Slightly taller pill + borderRadius: 25, // Full Pill + paddingHorizontal: 16, borderWidth: 1, - shadowColor: "#000", - shadowOffset: { width: 0, height: 2 }, - shadowRadius: 4, - elevation: 2, }, searchIcon: { - marginRight: 8, + marginRight: 10, }, input: { flex: 1, height: '100%', fontSize: 15, - color: colors.black, + fontWeight: '500', paddingVertical: Platform.OS === 'ios' ? 0 : 8, }, filterButton: { - height: 48, - width: 48, - backgroundColor: '#FFFFFF', + height: 50, + width: 50, justifyContent: 'center', alignItems: 'center', - borderRadius: 12, + borderRadius: 25, // Circle borderWidth: 1, - borderColor: colors.grayText + '30', elevation: 2, shadowColor: "#000", - shadowOffset: { width: 0, height: 1 }, + shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, - shadowRadius: 2, + shadowRadius: 4, }, }); diff --git a/src/Component/Skelton/Skelton.tsx b/src/Component/Skelton/Skelton.tsx index ddde75c..76749eb 100644 --- a/src/Component/Skelton/Skelton.tsx +++ b/src/Component/Skelton/Skelton.tsx @@ -1,34 +1,110 @@ -import { View, Dimensions } from 'react-native'; -import React from 'react'; -import SkeletonPlaceholder from 'react-native-skeleton-placeholder'; +import React, { useEffect, useRef } from 'react'; +import { View, Animated, StyleSheet, Dimensions } from 'react-native'; +import { useTheme } from '../../Hooks/useTheme'; -const Skeleton = () => { - const { width } = Dimensions.get('screen'); - const cardWidth = width - 30; // full width with some padding +// 1. Create a Reusable "Bone" Component +const SkeletonBone = ({ width, height, style, borderRadius = 8 }) => { + const { isDarkMode } = useTheme(); + const opacity = useRef(new Animated.Value(0.3)).current; + + // Animation Loop + useEffect(() => { + const animation = Animated.loop( + Animated.sequence([ + Animated.timing(opacity, { + toValue: 1, + duration: 1000, + useNativeDriver: true, + }), + Animated.timing(opacity, { + toValue: 0.3, + duration: 1000, + useNativeDriver: true, + }), + ]) + ); + animation.start(); + + return () => animation.stop(); + }, [opacity]); + + // Dynamic colors based on theme + const boneColor = isDarkMode ? '#3E4145' : '#E1E9EE'; return ( - + + ); +}; + +// 2. Main Skeleton Layout +const Skeleton = () => { + const { colors } = useTheme(); + + return ( + {[...Array(6)].map((_, index) => ( - - - {/* Image */} - + + {/* Header Row: Tag + Price */} + + + + - {/* Title */} - + {/* Title (2 lines) */} + + + + - {/* Subtitle / Price */} - - - + {/* Info Grid (2 cols) */} + + + + + + {/* Action Row */} + + + + + ))} ); }; -export default Skeleton; +const styles = StyleSheet.create({ + row: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 16, + }, + section: { + marginBottom: 16, + }, +}); + +export default Skeleton; \ No newline at end of file diff --git a/src/Component/StepProgressBar.tsx b/src/Component/StepProgressBar.tsx index 85b7f23..7630956 100644 --- a/src/Component/StepProgressBar.tsx +++ b/src/Component/StepProgressBar.tsx @@ -1,12 +1,14 @@ -import React, { useRef, useEffect } from 'react'; -import { - View, - Text, - TouchableOpacity, - StyleSheet, - Animated, -} from 'react-native'; -import colors from '../Constant/Color'; +import React, { useMemo } from 'react'; +import { View, StyleSheet } from 'react-native'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withSpring, + withTiming, +} from 'react-native-reanimated'; +import { useTheme } from '../Hooks/useTheme'; +import AppText from '../components/AppText'; +import { Check } from 'lucide-react-native'; type StepProgressBarProps = { currentStep: number; @@ -14,96 +16,132 @@ type StepProgressBarProps = { onStepPress?: (stepIndex: number) => void; }; +const STEP_LABELS = ['Info', 'Register', 'KYC', 'Address', 'Docs']; + const StepProgressBar: React.FC = ({ currentStep, steps, - onStepPress = () => {}, + onStepPress = () => { }, }) => { - const animations = useRef( - steps.map(() => new Animated.Value(1)) - ).current; - - useEffect(() => { - animations.forEach((anim, index) => { - Animated.timing(anim, { - toValue: index === currentStep ? 1.2 : 1, - duration: 300, - useNativeDriver: true, - }).start(); - }); - }, [currentStep]); + const { colors, spacing, typography, shadows } = useTheme(); + const styles = useMemo(() => makeStyles(colors, spacing), [colors, spacing]); + const totalSteps = steps.length; return ( - {steps.map((label, index) => { - const isActive = index === currentStep; - const isCompleted = index < currentStep; + {/* Progress line background */} + + + {/* Animated fill line */} + + - return ( - onStepPress(index)} - style={styles.stepContainer} - activeOpacity={0.8} - > - - - {label} - - - ); - })} + {/* Step circles */} + + {steps.map((_, index) => { + const isActive = index === currentStep; + const isCompleted = index < currentStep; + + return ( + + + {isCompleted ? ( + + ) : ( + + {index + 1} + + )} + + + {STEP_LABELS[index] || ''} + + + ); + })} + ); }; export default StepProgressBar; -const styles = StyleSheet.create({ +const makeStyles = (colors: any, spacing: any) => StyleSheet.create({ container: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 20, - flexWrap: 'wrap', - justifyContent: 'center', + paddingVertical: spacing.m, + paddingHorizontal: spacing.s, }, - stepContainer: { + lineContainer: { + position: 'absolute', + top: spacing.m + 14, // Center with circle + left: 40, + right: 40, + height: 3, + zIndex: 0, + }, + lineBackground: { + position: 'absolute', + height: 3, + width: '100%', + borderRadius: 2, + }, + lineFill: { + position: 'absolute', + height: 3, + borderRadius: 2, + }, + stepsRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + zIndex: 1, + }, + stepItem: { alignItems: 'center', - width: 70, + width: 52, }, circle: { - width: 50, - height: 5, - borderRadius: 25, + width: 28, + height: 28, + borderRadius: 14, justifyContent: 'center', alignItems: 'center', - backgroundColor: colors.primary, - }, - completedCircle: { - backgroundColor: '#4caf50', - }, - activeCircle: { - backgroundColor: '#2196f3', - }, - label: { - // marginTop: 6, - // fontSize: 12, - textAlign: 'center', - color: '#555', + borderWidth: 2, }, }); diff --git a/src/Component/TermsAndConditionsModal/TermsAndConditionsModal.tsx b/src/Component/TermsAndConditionsModal/TermsAndConditionsModal.tsx index baf175a..af93fc3 100644 --- a/src/Component/TermsAndConditionsModal/TermsAndConditionsModal.tsx +++ b/src/Component/TermsAndConditionsModal/TermsAndConditionsModal.tsx @@ -1,14 +1,20 @@ -import React, { ReactNode } from 'react'; -import { View, Text, Modal, ScrollView, TouchableOpacity, StyleSheet, Dimensions } from 'react-native'; -import { CheckCircle, X } from 'lucide-react-native'; // Updated icons -import colors from '../../Constant/Color'; +import React, { ReactNode, useMemo } from 'react'; +import { View, Modal, ScrollView, TouchableOpacity, StyleSheet } from 'react-native'; +import { CheckCircle, X, FileText, Shield, Users, Brain, Lock, RefreshCw, Scale } from 'lucide-react-native'; +import Animated, { FadeInUp, FadeInDown, FadeIn } from 'react-native-reanimated'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; + type ModalProps = { visible: boolean; onClose: () => void; - onChecked: () => void -} + onChecked: () => void; +}; const TermsAndConditionsModal: React.FC = ({ visible, onClose, onChecked }) => { + const { colors, spacing, shadows, typography } = useTheme(); + const styles = useMemo(() => makeStyles(colors, spacing, shadows, typography), [colors, spacing, shadows, typography]); + return ( = ({ visible, onClose, onChe onRequestClose={onClose} > - - - Terms & Conditions - Last updated: {new Date().toLocaleDateString()} + {/* ═══ Header ═══ */} + + + + + Terms & Conditions + + + Last updated: {new Date().toLocaleDateString()} + + + + + + - -
- - By accessing or using our services, you agree to be bound by these Terms and Conditions. - If you do not agree with any part of these terms, you must not use our services. - -
+ {/* ═══ Content ═══ */} + +
} + styles={styles} + colors={colors} + spacing={spacing} + index={0} + > + + By accessing or using our services, you agree to be bound by these Terms and Conditions. + If you do not agree with any part of these terms, you must not use our services. + +
-
- - You are responsible for maintaining the confidentiality of your account information and - for all activities that occur under your account. You agree to notify us immediately - of any unauthorized use of your account. - -
+
} + styles={styles} + colors={colors} + spacing={spacing} + index={1} + > + + You are responsible for maintaining the confidentiality of your account information and + for all activities that occur under your account. You agree to notify us immediately + of any unauthorized use of your account. + +
-
- - All content included on our platform, such as text, graphics, logos, and software, is - the property of our company and protected by intellectual property laws. You may not - reproduce, distribute, or create derivative works without our express permission. - -
+
} + styles={styles} + colors={colors} + spacing={spacing} + index={2} + > + + All content included on our platform, such as text, graphics, logos, and software, is + the property of our company and protected by intellectual property laws. You may not + reproduce, distribute, or create derivative works without our express permission. + +
-
- - Our company shall not be liable for any indirect, incidental, special, or consequential - damages resulting from the use or inability to use our services, even if we have been - advised of the possibility of such damages. - -
+
} + styles={styles} + colors={colors} + spacing={spacing} + index={3} + > + + Our company shall not be liable for any indirect, incidental, special, or consequential + damages resulting from the use or inability to use our services, even if we have been + advised of the possibility of such damages. + +
-
- - Your use of our services is also governed by our Privacy Policy, which explains how we - collect, use, and protect your personal information. Please review our Privacy Policy - to understand our practices. - -
+
} + styles={styles} + colors={colors} + spacing={spacing} + index={4} + > + + Your use of our services is also governed by our Privacy Policy, which explains how we + collect, use, and protect your personal information. Please review our Privacy Policy + to understand our practices. + +
-
- - We reserve the right to modify these terms at any time. We will notify you of any changes - by posting the new Terms and Conditions on this page. Your continued use of the service - after such modifications constitutes your acceptance of the new terms. - -
+
} + styles={styles} + colors={colors} + spacing={spacing} + index={5} + > + + We reserve the right to modify these terms at any time. We will notify you of any changes + by posting the new Terms and Conditions on this page. Your continued use of the service + after such modifications constitutes your acceptance of the new terms. + +
-
- - These Terms shall be governed by and construed in accordance with the laws of the - jurisdiction in which our company is registered, without regard to its conflict of law provisions. - -
+
} + styles={styles} + colors={colors} + spacing={spacing} + index={6} + > + + These Terms shall be governed by and construed in accordance with the laws of the + jurisdiction in which our company is registered, without regard to its conflict of law provisions. + +
- + + If you have any questions about these Terms, please contact us at legal@company.com. - -
+ + - {/* Accept Button */} - +
+ + {/* ═══ Accept Button ═══ */} + + { onClose(); onChecked(); }} + activeOpacity={0.85} > - - I Accept + + + I Accept + - +
); }; -// Reusable Section component + +// ── Section sub-component ───────────────────────────────────────────── type SectionProps = { title: string; + icon: ReactNode; children: ReactNode; + styles: any; + colors: any; + spacing: any; + index: number; }; -const Section: React.FC = ({ title, children }) => ( - - {title} +const Section: React.FC = ({ title, icon, children, styles, colors, spacing, index }) => ( + + + {icon} + + {title} + + {children} - + ); -const styles = StyleSheet.create({ + +export default TermsAndConditionsModal; + + +// ── Styles ──────────────────────────────────────────────────────────── +const makeStyles = (colors: any, spacing: any, shadows: any, typography: any) => StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.lightGray, // Updated with your colors - }, - scrollContainer: { - paddingBottom: 120, // Space for accept button - flexGrow: 1, + backgroundColor: colors.background, }, header: { - backgroundColor: colors.primary, // Orange header - paddingVertical: 32, - paddingHorizontal: 24, - borderBottomLeftRadius: 24, - borderBottomRightRadius: 24, - marginBottom: 24, - shadowColor: colors.shadow, - shadowOffset: { width: 0, height: 6 }, - shadowOpacity: 0.25, - shadowRadius: 12, - elevation: 8, + backgroundColor: colors.primary, + paddingTop: spacing.xl, + paddingBottom: spacing.l, + paddingHorizontal: spacing.l, + borderBottomLeftRadius: 20, + borderBottomRightRadius: 20, }, - title: { - fontSize: 28, - fontWeight: '800', - color: colors.white, - marginBottom: 4, + headerRow: { + flexDirection: 'row', + alignItems: 'flex-start', + justifyContent: 'space-between', }, - subtitle: { - fontSize: 14, - color: 'rgba(255,255,255,0.85)', + closeButton: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: 'rgba(255,255,255,0.2)', + justifyContent: 'center', + alignItems: 'center', }, - content: { - paddingHorizontal: 24, + scrollContainer: { + padding: spacing.l, paddingBottom: 40, }, section: { - marginBottom: 28, + backgroundColor: colors.surface, + borderRadius: 14, + padding: spacing.m, + marginBottom: spacing.m, + borderWidth: 1, + borderColor: colors.border, + }, + sectionHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + marginBottom: spacing.s, + paddingBottom: spacing.s, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, }, sectionTitle: { - fontSize: 20, fontWeight: '700', - color: colors.darkText, - marginBottom: 12, + fontSize: typography.size.s, }, paragraph: { - fontSize: 16, - lineHeight: 25, - color: colors.grayText, - }, - footerText: { - fontSize: 15, - fontStyle: 'italic', - color: colors.lightText, - marginTop: 24, - textAlign: 'center', lineHeight: 22, }, - acceptButton: { + footerText: { + fontStyle: 'italic', + textAlign: 'center', + lineHeight: 20, + marginTop: spacing.m, + }, + acceptButtonWrapper: { position: 'absolute', - bottom: 40, - left: 24, - right: 24, + bottom: 0, + left: 0, + right: 0, + padding: spacing.m, + paddingBottom: spacing.xl, + backgroundColor: colors.surface, + borderTopWidth: 1, + borderTopColor: colors.border, + }, + acceptButton: { backgroundColor: colors.primary, - borderRadius: 16, + borderRadius: 14, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - paddingVertical: 18, - paddingHorizontal: 20, - shadowColor: colors.shadow, - shadowOffset: { width: 0, height: 8 }, - shadowOpacity: 0.35, - shadowRadius: 16, - elevation: 12, - }, - acceptButtonText: { - color: colors.white, - fontSize: 18, - fontWeight: '700', - marginLeft: 12, - letterSpacing: 0.5, + paddingVertical: 16, }, }); - -export default TermsAndConditionsModal; diff --git a/src/Constant/ImagePath.jsx b/src/Constant/ImagePath.jsx index f7d172f..3d5ab10 100644 --- a/src/Constant/ImagePath.jsx +++ b/src/Constant/ImagePath.jsx @@ -5,9 +5,12 @@ import Diamond from '../Images/diamond.svg' import Hill from '../Images/Svg/Hill.svg' const Logo = require('../Images/logo.png') const Logo2 = require('../Images/logo2.png') + + + const Icon = { -Logo, -Logo2 + Logo, + Logo2 } const ImagePath = { Icon, @@ -15,7 +18,11 @@ const ImagePath = { OnBoard2, OnBoard3, Diamond, - Hill - + Hill, + + DarkBg: require('../Images/Backgrounds/darkBackground.png'), + LightBg: require('../Images/Backgrounds/lightBackground.png'), + DarkBg2: require('../Images/Backgrounds/darkBackground2.png'), + LightBg2: require('../Images/Backgrounds/lightBackground2.png'), } export default ImagePath; \ No newline at end of file diff --git a/src/Constant/NavigationString.tsx b/src/Constant/NavigationString.tsx index 2968173..d4589eb 100644 --- a/src/Constant/NavigationString.tsx +++ b/src/Constant/NavigationString.tsx @@ -24,6 +24,8 @@ const NavigationString = { ResetPassword:'ResetPassword', KycWebViewScreen:'KycWebViewScreen', StageThreeScreen:'StageThree', - DecryptionScreen:'DecryptionScreen' + DecryptionScreen:'DecryptionScreen', + DeviceBinding: 'DeviceBinding', + DscBinding: 'DscBinding' } export default NavigationString \ No newline at end of file diff --git a/src/Hooks/useCreate.ts b/src/Hooks/useCreate.ts new file mode 100644 index 0000000..5927005 --- /dev/null +++ b/src/Hooks/useCreate.ts @@ -0,0 +1,20 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'; +import apiClient from '../api/client'; + +export const useCreate = ( + url: string, + options?: UseMutationOptions +) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (variables: V) => { + const { data } = await apiClient.post(url, variables); + return data; + }, + onSuccess: () => { + // Invalidate queries if needed, handled by component or passed in options + }, + ...options, + }); +}; diff --git a/src/Hooks/useDelete.ts b/src/Hooks/useDelete.ts new file mode 100644 index 0000000..bc10c9d --- /dev/null +++ b/src/Hooks/useDelete.ts @@ -0,0 +1,17 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'; +import apiClient from '../api/client'; + +export const useDelete = ( + url: string, + options?: UseMutationOptions +) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async () => { + const { data } = await apiClient.delete(url); + return data; + }, + ...options, + }); +}; diff --git a/src/Hooks/useFetch.ts b/src/Hooks/useFetch.ts new file mode 100644 index 0000000..45884b6 --- /dev/null +++ b/src/Hooks/useFetch.ts @@ -0,0 +1,17 @@ +import { useQuery, UseQueryOptions, QueryKey } from '@tanstack/react-query'; +import apiClient from '../api/client'; + +export const useFetch = ( + key: QueryKey, + url: string, + options?: UseQueryOptions +) => { + return useQuery({ + queryKey: key, + queryFn: async () => { + const { data } = await apiClient.get(url); + return data; + }, + ...options, + }); +}; diff --git a/src/Hooks/useTheme.ts b/src/Hooks/useTheme.ts new file mode 100644 index 0000000..2a20879 --- /dev/null +++ b/src/Hooks/useTheme.ts @@ -0,0 +1,32 @@ +import { useColorScheme } from 'react-native'; +import { useSelector, useDispatch } from 'react-redux'; +import { lightColors, darkColors, spacing, typography, shadows } from '../theme'; +import { selectThemeMode, setThemeMode } from '../Redux/Slices/ThemeSlice'; + +export const useTheme = () => { + const systemScheme = useColorScheme(); + const themeMode = useSelector(selectThemeMode); + const dispatch = useDispatch(); + + const isDarkMode = + themeMode === 'system' + ? systemScheme === 'dark' + : themeMode === 'dark'; + + const themeColors = isDarkMode ? darkColors : lightColors; + + const toggleTheme = (mode: 'light' | 'dark' | 'system') => { + dispatch(setThemeMode(mode)); + }; + + return { + colors: themeColors, + isDarkMode, + themeMode, + toggleTheme, + spacing, + typography, + shadows, + }; +}; + diff --git a/src/Hooks/useUpdate.ts b/src/Hooks/useUpdate.ts new file mode 100644 index 0000000..6a1a6c2 --- /dev/null +++ b/src/Hooks/useUpdate.ts @@ -0,0 +1,17 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'; +import apiClient from '../api/client'; + +export const useUpdate = ( + url: string, + options?: UseMutationOptions +) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (variables: V) => { + const { data } = await apiClient.put(url, variables); + return data; + }, + ...options, + }); +}; diff --git a/src/Images/Backgrounds/darkBackground.png b/src/Images/Backgrounds/darkBackground.png new file mode 100644 index 0000000..23218cb Binary files /dev/null and b/src/Images/Backgrounds/darkBackground.png differ diff --git a/src/Images/Backgrounds/darkBackground2.png b/src/Images/Backgrounds/darkBackground2.png new file mode 100644 index 0000000..460ce3a Binary files /dev/null and b/src/Images/Backgrounds/darkBackground2.png differ diff --git a/src/Images/Backgrounds/lightBackground.png b/src/Images/Backgrounds/lightBackground.png new file mode 100644 index 0000000..b1020c0 Binary files /dev/null and b/src/Images/Backgrounds/lightBackground.png differ diff --git a/src/Images/Backgrounds/lightBackground2.png b/src/Images/Backgrounds/lightBackground2.png new file mode 100644 index 0000000..b8c3d63 Binary files /dev/null and b/src/Images/Backgrounds/lightBackground2.png differ diff --git a/src/Images/Onboarding/undraw_1.svg b/src/Images/Onboarding/undraw_1.svg index 3ed17ea..9167845 100644 --- a/src/Images/Onboarding/undraw_1.svg +++ b/src/Images/Onboarding/undraw_1.svg @@ -1 +1 @@ -goal \ No newline at end of file +goal \ No newline at end of file diff --git a/src/Images/Onboarding/undraw_2.svg b/src/Images/Onboarding/undraw_2.svg index 233db4b..c758650 100644 --- a/src/Images/Onboarding/undraw_2.svg +++ b/src/Images/Onboarding/undraw_2.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Images/Onboarding/undraw_3.svg b/src/Images/Onboarding/undraw_3.svg index b89eb76..3203c37 100644 --- a/src/Images/Onboarding/undraw_3.svg +++ b/src/Images/Onboarding/undraw_3.svg @@ -1 +1 @@ -virtual_assistant \ No newline at end of file +virtual_assistant \ No newline at end of file diff --git a/src/Navigation/Bottom.tsx b/src/Navigation/Bottom.tsx index 2fb1e70..ed6114e 100644 --- a/src/Navigation/Bottom.tsx +++ b/src/Navigation/Bottom.tsx @@ -1,124 +1,173 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, Dimensions, - Animated, + Platform, } from 'react-native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import * as LucideIcons from 'lucide-react-native'; +import Animated, { + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, + interpolate, + Extrapolation +} from 'react-native-reanimated'; import Screens from '../Screens'; -import colors from '../Constant/Color'; + import NavigationString from '../Constant/NavigationString'; -import EditableExcel from '../Screens/Tender/Component/EnvelopeCExcelPreview'; +import { useTheme } from '../Hooks/useTheme'; +import { typography } from '../theme/typography'; +// import EditableExcel from '../Screens/Tender/Component/EnvelopeCExcelPreview'; const { width } = Dimensions.get('window'); -const TAB_BAR_HEIGHT = 60; +const TAB_BAR_HEIGHT = 70; // Slightly taller for premium feel const TAB_BUTTON_WIDTH = width / 4; const Tab = createBottomTabNavigator(); const TabButton = ({ route, isFocused, onPress }: any) => { - const icons: Record = { - Home: { outline: "Home", filled: "Home" }, + const { colors } = useTheme(); + const icons: Record = { + Home: { outline: "House", filled: "House" }, Auction: { outline: "Gavel", filled: "Gavel" }, Tender: { outline: "FileText", filled: "FileText" }, Profile: { outline: "User", filled: "User" }, + }; - const IconName = LucideIcons[isFocused ? icons[route.name].filled : icons[route.name].outline]; - const scaleAnim = useRef(new Animated.Value(1)).current; - const translateYAnim = useRef(new Animated.Value(0)).current; - const opacityAnim = useRef(new Animated.Value(1)).current; + // Lucide icons don't always have 'filled' variants in the set, so we might use the same icon + // or rely on color/strokeWidth to indicate state. + const iconKey = icons[route.name]?.outline || "HelpCircle"; // Fallback + const IconName = LucideIcons[iconKey] as React.ElementType; + + const scale = useSharedValue(0); + const translateY = useSharedValue(0); useEffect(() => { if (isFocused) { - Animated.parallel([ - Animated.sequence([ - Animated.timing(translateYAnim, { - toValue: -8, - duration: 120, - useNativeDriver: true, - }), - Animated.spring(translateYAnim, { - toValue: 0, - friction: 3, - tension: 80, - useNativeDriver: true, - }), - ]), - Animated.spring(scaleAnim, { - toValue: 1.3, - friction: 5, - tension: 150, - useNativeDriver: true, - }), - Animated.timing(opacityAnim, { - toValue: 1, - duration: 250, - useNativeDriver: true, - }), - ]).start(); + scale.value = withSpring(1, { damping: 12, stiffness: 100 }); + translateY.value = withSpring(-4, { damping: 12, stiffness: 100 }); } else { - Animated.parallel([ - Animated.spring(scaleAnim, { - toValue: 1, - friction: 5, - tension: 150, - useNativeDriver: true, - }), - Animated.timing(opacityAnim, { - toValue: 0.6, - duration: 150, - useNativeDriver: true, - }), - ]).start(); + scale.value = withTiming(0, { duration: 200 }); + translateY.value = withSpring(0, { damping: 12, stiffness: 100 }); } }, [isFocused]); + const animatedPillStyle = useAnimatedStyle(() => { + return { + transform: [{ scale: scale.value }], + opacity: interpolate(scale.value, [0, 1], [0, 1], Extrapolation.CLAMP), + }; + }); + + const animatedIconStyle = useAnimatedStyle(() => { + return { + transform: [{ scale: interpolate(scale.value, [0, 1], [1, 0.9]) }], // Slight shrink on press + }; + }); + + const animatedTextStyle = useAnimatedStyle(() => { + const opacity = interpolate(scale.value, [0, 1], [0, 1]); + const translateX = interpolate(scale.value, [0, 1], [20, 0]); // Slide in from right + return { + opacity, + transform: [{ translateX }], + display: scale.value === 0 ? 'none' : 'flex', // Hide when inactive + marginLeft: 4, // Reduced space + }; + }); + return ( - - {IconName && ( - - )} - + + + {IconName && ( + + )} + - - {route.name === 'BrandDay' ? 'Coupons' : route.name} - + {isFocused && ( + + {route.name === 'BrandDay' ? 'Coupons' : route.name} + + )} + ); }; const CustomTabBar = ({ state, descriptors, navigation }: any) => { + const { colors, shadows } = useTheme(); + const { width: windowWidth } = Dimensions.get('window'); + const TAB_BAR_WIDTH = windowWidth - 40; // 20 margin each side + const TAB_PADDING_H = 10; // 5 padding left + 5 padding right + const TAB_WIDTH = (TAB_BAR_WIDTH - TAB_PADDING_H) / state.routes.length; + + const translateX = useSharedValue(0); + + useEffect(() => { + translateX.value = withSpring(state.index * TAB_WIDTH, { + damping: 15, + stiffness: 150, + mass: 0.5, + }); + }, [state.index]); + + const animatedIndicatorStyle = useAnimatedStyle(() => { + return { + transform: [{ translateX: translateX.value }], + }; + }); + return ( - + + {state.routes.map((route: any, index: number) => { const isFocused = state.index === index; @@ -136,8 +185,9 @@ const CustomTabBar = ({ state, descriptors, navigation }: any) => { }; export default function BottomTab() { + const { colors } = useTheme(); return ( - + } screenOptions={{ @@ -148,9 +198,9 @@ export default function BottomTab() { - {/* */} - - + {/* */} + + ); @@ -159,29 +209,54 @@ export default function BottomTab() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#fff', + }, tabBar: { + position: 'absolute', + bottom: 20, + left: 20, + right: 20, flexDirection: 'row', - height: TAB_BAR_HEIGHT, - backgroundColor: '#fff', - borderTopWidth: 1, - borderTopColor: '#eee', - elevation: 5, + height: 70, + borderRadius: 35, + elevation: 20, // stronger Android elevation + shadowColor: '#000', // iOS shadow color + shadowOffset: { width: 0, height: 10 }, + shadowOpacity: Platform.OS === 'ios' ? 0.25 : 0.2, + shadowRadius: 12, + overflow: 'visible', // allow shadow to render outside rounded corners + zIndex: 9999, + paddingBottom: 0, + justifyContent: 'space-around', + alignItems: 'center', + paddingHorizontal: 5, }, tabButton: { + flex: 1, justifyContent: 'center', alignItems: 'center', height: '100%', }, - iconContainer: { - marginBottom: 4, - justifyContent: 'center', + tabContent: { + flexDirection: 'row', alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 4, // Drastically reduced from 12 + paddingVertical: 4, + borderRadius: 20, + }, + slidingIndicator: { + position: 'absolute', + top: 12, + left: 5, + height: 40, + borderRadius: 30, + zIndex: 0, }, label: { - fontSize: 11, - textAlign: 'center', - includeFontPadding: false, + fontSize: 11, // Slightly smaller text + textAlign: 'left', + textTransform: 'capitalize', + letterSpacing: 0.2, // Tighter spacing }, }); diff --git a/src/Navigation/CustomDrawer.tsx b/src/Navigation/CustomDrawer.tsx new file mode 100644 index 0000000..fad9677 --- /dev/null +++ b/src/Navigation/CustomDrawer.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { View, StyleSheet, TouchableOpacity, Switch, Image } from 'react-native'; +import { DrawerContentScrollView, DrawerItemList } from '@react-navigation/drawer'; +import { useTheme } from '../Hooks/useTheme'; +import AppText from '../components/AppText'; +import { Moon, Sun, Monitor } from 'lucide-react-native'; +import ImagePath from '../Constant/ImagePath'; + +const CustomDrawer = (props: any) => { + const { colors, isDarkMode, themeMode, toggleTheme } = useTheme(); + + const renderThemeOption = (mode: 'light' | 'dark' | 'system', label: string, Icon: any) => { + const isActive = themeMode === mode; + return ( + toggleTheme(mode)} + > + + + {label} + + + ); + }; + + return ( + + + {/* Header */} + + + ProcureLinc + Smart Procurement + + + + + + + {/* Theme Switch Section */} + + Running Theme + + {renderThemeOption('light', 'Light', Sun)} + {renderThemeOption('dark', 'Dark', Moon)} + {renderThemeOption('system', 'System', Monitor)} + + + + + + + Version 1.0.0 + + + + ); +}; + +const styles = StyleSheet.create({ + header: { + padding: 20, + paddingTop: 50, // safe area approximation + alignItems: 'center', + borderBottomWidth: 1, + }, + logo: { + width: 80, + height: 80, + borderRadius: 40, + marginBottom: 10, + }, + section: { + padding: 10, + borderTopWidth: 1, + marginTop: 'auto', + marginBottom: 20 + }, + themeRow: { + flexDirection: 'row', + justifyContent: 'space-around', + }, + themeOption: { + alignItems: 'center', + justifyContent: 'center', + padding: 10, + borderRadius: 12, + flexDirection: 'row' + }, + footer: { + padding: 20, + borderTopWidth: 1, + } +}); + +export default CustomDrawer; diff --git a/src/Navigation/DrawerStack.tsx b/src/Navigation/DrawerStack.tsx new file mode 100644 index 0000000..dcc8dd7 --- /dev/null +++ b/src/Navigation/DrawerStack.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import BottomTab from './Bottom'; +import CustomDrawer from './CustomDrawer'; +import { useTheme } from '../Hooks/useTheme'; +import NavigationString from '../Constant/NavigationString'; +import { Dimensions } from 'react-native'; + +const Drawer = createDrawerNavigator(); +const { width } = Dimensions.get('window'); + +const DrawerStack = () => { + const { colors } = useTheme(); + + return ( + } + screenOptions={{ + headerShown: false, + drawerType: 'slide', + drawerStyle: { + backgroundColor: colors.background, + width: width * 0.75, + }, + drawerActiveTintColor: colors.primary, + drawerInactiveTintColor: colors.textSecondary, + overlayColor: 'rgba(0,0,0,0.5)', + }} + > + + {/* Add other drawer screens here that are NOT in the bottom tab */} + + ); +}; + +export default DrawerStack; diff --git a/src/Navigation/MainStack.jsx b/src/Navigation/MainStack.jsx index 4408db3..3c4a542 100644 --- a/src/Navigation/MainStack.jsx +++ b/src/Navigation/MainStack.jsx @@ -4,35 +4,20 @@ import NavigationString from '../Constant/NavigationString'; import Screens from '../Screens'; import colors from '../Constant/Color'; import BottomTab from './Bottom'; +import Login from '../Screens/Login/Login'; export default function MainStack() { const Stack = createStackNavigator(); const option = { headerShown: false }; - const fadeTransition = { - gestureDirection: 'horizontal', - transitionSpec: { - open: { animation: 'timing', config: { duration: 300 } }, - close: { animation: 'timing', config: { duration: 300 } }, - }, - cardStyleInterpolator: ({ current }) => ({ - cardStyle: { - opacity: current.progress, - }, - }), - }; - + // ... return ( - + @@ -46,10 +31,12 @@ export default function MainStack() { - + - - + + + + ); } diff --git a/src/Redux/Slices/ThemeSlice.ts b/src/Redux/Slices/ThemeSlice.ts new file mode 100644 index 0000000..e953f0b --- /dev/null +++ b/src/Redux/Slices/ThemeSlice.ts @@ -0,0 +1,27 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +type ThemeMode = 'light' | 'dark' | 'system'; + +interface ThemeState { + mode: ThemeMode; +} + +const initialState: ThemeState = { + mode: 'system', +}; + +export const themeSlice = createSlice({ + name: 'theme', + initialState, + reducers: { + setThemeMode: (state, action: PayloadAction) => { + state.mode = action.payload; + }, + }, +}); + +export const { setThemeMode } = themeSlice.actions; + +export const selectThemeMode = (state: any) => state.theme.mode; + +export default themeSlice.reducer; diff --git a/src/Redux/Slices/Token.tsx b/src/Redux/Slices/Token.tsx index b226793..b86451f 100644 --- a/src/Redux/Slices/Token.tsx +++ b/src/Redux/Slices/Token.tsx @@ -16,5 +16,6 @@ export const tokenSlice = createSlice({ } }, }); -export const { setToken , removeToken} = tokenSlice.actions; +export const { setToken, removeToken } = tokenSlice.actions; +export const getToken = (state: any) => state.token.token; export default tokenSlice.reducer; diff --git a/src/Redux/Store.js b/src/Redux/Store.js index a475847..a9e470e 100644 --- a/src/Redux/Store.js +++ b/src/Redux/Store.js @@ -1,12 +1,13 @@ -import {configureStore} from '@reduxjs/toolkit'; -import {combineReducers} from 'redux'; -import {persistStore, persistReducer} from 'redux-persist'; +import { configureStore } from '@reduxjs/toolkit'; +import { combineReducers } from 'redux'; +import { persistStore, persistReducer } from 'redux-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import {tokenSlice} from './Slices/Token'; -import {auctionSlice} from './Slices/SelectedAuction'; +import { tokenSlice } from './Slices/Token'; +import { auctionSlice } from './Slices/SelectedAuction'; import selectPurchaseAuctionReducer from './Slices/selectPurchaseAuction' import selectPurchaseTenderReducer from './Slices/SelectedPurchaseTender'; import { tenderSlice } from './Slices/SelectedTender'; +import { themeSlice } from './Slices/ThemeSlice'; const rootReducer = combineReducers({ token: tokenSlice.reducer, @@ -14,11 +15,12 @@ const rootReducer = combineReducers({ selectPurchaseAuction: selectPurchaseAuctionReducer, selectPurchaseTender: selectPurchaseTenderReducer, tender: tenderSlice.reducer, + theme: themeSlice.reducer, }); const persistConfig = { key: 'root', storage: AsyncStorage, - whitelist: ['token'], + whitelist: ['token', 'theme'], }; const persistReducers = persistReducer(persistConfig, rootReducer); export const store = configureStore({ diff --git a/src/Screens/Auction/Auction.tsx b/src/Screens/Auction/Auction.tsx index 927936e..a0497e4 100644 --- a/src/Screens/Auction/Auction.tsx +++ b/src/Screens/Auction/Auction.tsx @@ -1,22 +1,29 @@ import React, { useState, useRef, useMemo } from 'react'; -import { View, Text, StyleSheet, TouchableOpacity, Animated, Dimensions } from 'react-native'; +import { + View, + StyleSheet, + TouchableOpacity, + Animated, + Dimensions, +} from 'react-native'; import { InsideHeader } from '../../Component/Index'; import { useFetchLiveAuction, useFetchPurchaseAuction } from '../../Services/ApiCall/Hooks'; -import colors from '../../Constant/Color'; import LiveAuction from './LiveAuction'; import PurchaseAuction from './PurchaseAuction'; -import { ShoppingCart, Lock } from 'lucide-react-native'; +import { ShoppingCart, Lock, Radio, Package } from 'lucide-react-native'; import { useNavigation } from '@react-navigation/native'; import NavigationString from '../../Constant/NavigationString'; -import FontsFamily from '../../Constant/FontsFamily'; import { useSelector } from 'react-redux'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; const { width: SCREEN_WIDTH } = Dimensions.get('window'); const HORIZONTAL_PADDING = 32; -const SWITCH_INSETS = 2; -const TAB_HEIGHT = 44; +const SWITCH_INSETS = 3; +const TAB_HEIGHT = 48; const Auction = () => { + const { colors, spacing, shadows } = useTheme(); const Navigation = useNavigation() as any; const { data: liveAuctionData = {} }: any = useFetchLiveAuction(); const { data: purchaseAuctionData = {} } = useFetchPurchaseAuction(); @@ -31,14 +38,14 @@ const Auction = () => { ? purchaseAuctionData.data.length : 0; - const [selectedTab, setSelectedTab] = useState<'live' | 'purchase'>('live'); - const translateX = useRef(new Animated.Value(0)).current; - const TAB_WIDTH = useMemo(() => { const availableWidth = SCREEN_WIDTH - HORIZONTAL_PADDING - SWITCH_INSETS * 2; - return availableWidth / 2.4; + return availableWidth / 2; }, []); + const [selectedTab, setSelectedTab] = useState<'live' | 'purchase'>('purchase'); + const translateX = useRef(new Animated.Value(TAB_WIDTH)).current; + const onTabPress = (tab: 'live' | 'purchase', index: number) => { setSelectedTab(tab); Animated.spring(translateX, { @@ -48,252 +55,295 @@ const Auction = () => { }).start(); }; - // Check if user is tender type const isTenderUser = reduxToken?.userType?.toLowerCase() === 'tender'; + const cartCount = SelectedPurchaseList.length; - // Render restricted access message - const renderRestrictedAccess = () => ( - - - - + const styles = makeStyles(colors, spacing, shadows, TAB_WIDTH, TAB_HEIGHT); + + if (isTenderUser) { + return ( + + + + + {/* Icon ring */} + + + + + + + + Access Restricted + + + + You're logged in as a{' '} + + Tender user + + {'. '}Auction features are not available for your account type. + + + + + Switch to an Auction account to access live & purchase auction features. + + + + Navigation.navigate(NavigationString.Login)} + > + + Switch to Auction Login + + + - - Auction Access Restricted - - - You are currently logged in as a Tender user. Auction features are not available for your account type. - - - - To access auction features, please login with an Auction user account. - - - Navigation.navigate(NavigationString.Login)} - > - Switch to Auction Login - - - ); + ); + } return ( - {isTenderUser ? ( - // Show restricted access message for tender users - renderRestrictedAccess() - ) : ( - // Show normal auction UI for auction users - <> - - + {/* Segmented Control */} + + {/* Sliding pill */} + + + {/* Live Tab */} + onTabPress('live', 0)} + > + + - + Live {liveCount ? `(${liveCount})` : ''} + + - onTabPress('live', 0)} - > - - Live ({liveCount}) - - + {/* Purchase Tab */} + onTabPress('purchase', 1)} + > + + + Purchased {purchaseCount ? `(${purchaseCount})` : ''} + + + + - onTabPress('purchase', 1)} - > - - Purchased ({purchaseCount}) - - - + {/* Content */} + {selectedTab === 'live' ? : } - - {SelectedPurchaseList.length ? ( - - - {SelectedPurchaseList.length} - - - ) : null} - { - Navigation.navigate(NavigationString.PurchaseList); - }} - > - - - + {/* Cart Floating Button */} + Navigation.navigate(NavigationString.PurchaseList)} + > + + {cartCount > 0 && ( + + + {cartCount} + - - {selectedTab === 'live' ? : } - - )} + )} + ); }; export default Auction; -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.white, - }, - topRow: { - flexDirection: 'row', - justifyContent: 'space-around', - alignItems: 'center', - paddingHorizontal: 16, - }, - switchWrapper: { - flexDirection: 'row', - marginVertical: 14, - backgroundColor: '#f1f5f9', - borderRadius: 18, - padding: 2, - height: TAB_HEIGHT, - }, - tab: { - height: TAB_HEIGHT - 4, - alignItems: 'center', - justifyContent: 'center', - zIndex: 2, - }, - tabText: { - fontSize: 13, - color: '#64748b', - lineHeight: 18, - fontFamily: FontsFamily.poppinsSemiBold, - }, - activeText: { - color: colors.primary, - }, - activeTab: { - position: 'absolute', - top: 2, - left: 2, - height: TAB_HEIGHT - 4, - backgroundColor: '#fff', - borderRadius: 16, - shadowColor: '#000', - shadowOpacity: 0.1, - shadowRadius: 6, - elevation: 3, - }, - restrictedContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - padding: 24, - backgroundColor: colors.lightGray, - }, - restrictedCard: { - backgroundColor: colors.white, - borderRadius: 24, - padding: 32, - alignItems: 'center', - maxWidth: 400, - width: '100%', +/* ─── Styles factory ─── */ +const makeStyles = (colors: any, spacing: any, shadows: any, TAB_WIDTH: number, TAB_HEIGHT: number) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, - }, - iconContainer: { - width: 120, - height: 120, - borderRadius: 60, - backgroundColor: colors.primaryHighlight, - justifyContent: 'center', - alignItems: 'center', - marginBottom: 24, - }, - restrictedTitle: { - fontSize: 24, - fontWeight: '700', - color: colors.darkText, - textAlign: 'center', - marginBottom: 16, - fontFamily: FontsFamily.poppinsBold, - }, - restrictedMessage: { - fontSize: 16, - color: colors.grayText, - textAlign: 'center', - lineHeight: 24, - marginBottom: 12, - fontFamily: FontsFamily.poppinsRegular, - }, - restrictedSubMessage: { - fontSize: 14, - color: colors.grayText, - textAlign: 'center', - lineHeight: 22, - marginBottom: 32, - fontFamily: FontsFamily.poppinsRegular, - }, - loginButton: { - backgroundColor: colors.primary, - paddingHorizontal: 32, - paddingVertical: 14, - borderRadius: 12, - shadowColor: colors.primary, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.3, - shadowRadius: 8, - elevation: 4, - }, - loginButtonText: { - color: colors.white, - fontSize: 16, - fontWeight: '600', - fontFamily: FontsFamily.poppinsSemiBold, - }, -}); + /* ── Tab Row ── */ + topRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: spacing.m, + paddingVertical: spacing.s + 2, + }, + switchWrapper: { + flexDirection: 'row', + backgroundColor: colors.surface, + borderRadius: 16, + padding: SWITCH_INSETS, + height: TAB_HEIGHT, + ...shadows.light, + }, + tab: { + height: TAB_HEIGHT - SWITCH_INSETS * 2, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 5, + zIndex: 2, + }, + tabText: { + fontWeight: '600', + letterSpacing: 0.3, + } as any, + activeTabText: { + fontWeight: '700', + } as any, + activeTab: { + position: 'absolute', + top: SWITCH_INSETS, + left: SWITCH_INSETS, + height: TAB_HEIGHT - SWITCH_INSETS * 2, + backgroundColor: colors.background, + borderRadius: 13, + ...shadows.medium, + }, + + /* ── Cart Floating Button ── */ + cartFloatingButton: { + position: 'absolute', + bottom: spacing.xl + 80, + right: spacing.l, + width: 56, + height: 56, + borderRadius: 28, + backgroundColor: colors.primary, + alignItems: 'center', + justifyContent: 'center', + ...shadows.strong, + zIndex: 999, + }, + cartBadge: { + position: 'absolute', + top: 0, + right: 0, + minWidth: 20, + height: 20, + borderRadius: 10, + backgroundColor: colors.background, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 4, + borderWidth: 1.5, + borderColor: colors.primary, + }, + cartBadgeText: { + fontSize: 10, + fontWeight: '800', + lineHeight: 13, + } as any, + + /* ── Restricted Access ── */ + restrictedContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: spacing.l, + backgroundColor: colors.background, + }, + restrictedCard: { + backgroundColor: colors.surface, + borderRadius: 28, + padding: spacing.l, + alignItems: 'center', + width: '100%', + maxWidth: 400, + ...shadows.strong, + }, + lockRing: { + width: 100, + height: 100, + borderRadius: 50, + backgroundColor: `${colors.primary}12`, + alignItems: 'center', + justifyContent: 'center', + marginBottom: spacing.l, + borderWidth: 1.5, + borderColor: `${colors.primary}25`, + }, + lockInner: { + width: 72, + height: 72, + borderRadius: 36, + backgroundColor: `${colors.primary}18`, + alignItems: 'center', + justifyContent: 'center', + }, + restrictedTitle: { + marginBottom: spacing.s + 4, + } as any, + restrictedMessage: { + marginBottom: spacing.s, + lineHeight: 24, + } as any, + hintBox: { + backgroundColor: colors.overlay, + borderRadius: 14, + paddingHorizontal: spacing.m, + paddingVertical: spacing.s + 2, + marginBottom: spacing.l, + width: '100%', + }, + loginButton: { + backgroundColor: colors.buttonPrimary, + paddingHorizontal: spacing.l, + paddingVertical: 14, + borderRadius: 16, + width: '100%', + alignItems: 'center', + ...shadows.medium, + }, + loginBtnText: { + letterSpacing: 1.2, + } as any, + }); diff --git a/src/Screens/Auction/LiveAuction.tsx b/src/Screens/Auction/LiveAuction.tsx index 200fcbf..d9345d1 100644 --- a/src/Screens/Auction/LiveAuction.tsx +++ b/src/Screens/Auction/LiveAuction.tsx @@ -1,162 +1,207 @@ import React, { useCallback, useState, useEffect, useRef } from 'react'; -import { View, FlatList, ActivityIndicator, RefreshControl, Text } from 'react-native'; +import { + View, + FlatList, + ActivityIndicator, + RefreshControl, + StyleSheet, +} from 'react-native'; import Animated, { FadeInDown, FadeOutUp } from 'react-native-reanimated'; +import { RefreshCw } from 'lucide-react-native'; import LiveAuctionTile from './LiveAuctionTile'; import { useFetchLiveAuction } from '../../Services/ApiCall/Hooks'; import { SearchInput } from '../../Component/Serach/SeacrhInput'; import { Skelton } from '../../Component/Index'; -import colors from '../../Constant/Color'; +import SkeltonList from '../../Component/Skelton/SkeltonList'; import { useDispatch, useSelector } from 'react-redux'; import { togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction'; -import { apiCall } from '../../Axios/Axios'; -import { GET_MY_AUCTION } from '../../Services/ApiCall/ApiUrls'; -import SkeltonList from '../../Component/Skelton/SkeltonList'; -import Skeleton from '../../Component/Skelton/Skelton'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; const LiveAuction = () => { - const Dispatch = useDispatch() - const user = useSelector((state: any) => state.token?.token); - const onEndReachedCalledDuringMomentum = useRef(true); - const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction); - const [showRefreshBanner, setShowRefreshBanner] = useState(false); - const [allBoolean, setAllBoolean] = useState({ liveAuctionData: [], isLoading: false }) - const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch, isPending }: any = useFetchLiveAuction(); - // My Methods + const Dispatch = useDispatch(); + const { colors, spacing, shadows } = useTheme(); + const user = useSelector((state: any) => state.token?.token); + const onEndReachedCalledDuringMomentum = useRef(true); + const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction); + const [showRefreshBanner, setShowRefreshBanner] = useState(false); + const { + data, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + isRefetching, + refetch, + isPending, + }: any = useFetchLiveAuction(); - const renderItem = useCallback( - ({ item, index }: any) => { - const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.auctionNumber === item.auctionNumber) - return { Dispatch(togglePurchaseAuction(item)) }} - onDetailsPress={() => { }} - /> - }, - [SelectedPurchaseList] - ); + const renderItem = useCallback( + ({ item, index }: any) => { + const isPurchase = SelectedPurchaseList.findIndex( + (prev: any) => prev.auctionNumber === item.auctionNumber, + ); + return ( + { + console.log('[LiveAuction] purchase toggled:', item?.auctionNumber); + Dispatch(togglePurchaseAuction(item)); + }} + onDetailsPress={() => {}} + /> + ); + }, + [SelectedPurchaseList], + ); - const loadMore = useCallback(() => { - if (onEndReachedCalledDuringMomentum.current) return; - - if (!hasNextPage || isFetchingNextPage) return; - - fetchNextPage(); - onEndReachedCalledDuringMomentum.current = true; - }, [hasNextPage, isFetchingNextPage, fetchNextPage]); - - const renderFooter = useCallback(() => { - if (!isFetchingNextPage) return null; - return ( - - - - ); - }, [isFetchingNextPage]); - - useEffect(() => { - if (isRefetching) { - setShowRefreshBanner(true); - } else { - const timer = setTimeout(() => { - setShowRefreshBanner(false); - }, 1000); - - return () => clearTimeout(timer); - } - }, [isRefetching]); - - - if (isLoading) { - return ; - } + const loadMore = useCallback(() => { + if (onEndReachedCalledDuringMomentum.current) return; + if (!hasNextPage || isFetchingNextPage) return; + console.log('[LiveAuction] loadMore: fetching next page'); + fetchNextPage(); + onEndReachedCalledDuringMomentum.current = true; + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); + const renderFooter = useCallback(() => { + if (!isFetchingNextPage) return null; return ( - - { }} placeholder="Search...." filter={false} /> - - {showRefreshBanner && ( - - - - Refreshing auctions - - - )} - { - isPending ? : - { - onEndReachedCalledDuringMomentum.current = false; - }} - - ListFooterComponent={renderFooter} - - refreshControl={ - - } - - initialNumToRender={6} - maxToRenderPerBatch={6} - windowSize={7} - removeClippedSubviews - /> - } - - - - + + + + Loading more… + + ); + }, [isFetchingNextPage, colors]); + + useEffect(() => { + if (isRefetching) { + console.log('[LiveAuction] refetch started'); + setShowRefreshBanner(true); + } else { + console.log('[LiveAuction] refetch completed'); + const timer = setTimeout(() => setShowRefreshBanner(false), 1000); + return () => clearTimeout(timer); + } + }, [isRefetching]); + + useEffect(() => { + console.log('[LiveAuction] data updated:', { + count: data?.result?.length ?? 0, + hasNextPage, + isFetchingNextPage, + isPending, + }); + }, [data, hasNextPage, isFetchingNextPage, isPending]); + + if (isLoading) { + return ; + } + + return ( + + {/* Search */} + {}} placeholder="Search auctions…" filter={false} /> + + {/* Refresh Banner */} + {showRefreshBanner && ( + + + + Refreshing auctions… + + + )} + + {/* List */} + {isPending ? ( + + ) : ( + item?.auctionId?.toString()} + showsVerticalScrollIndicator={false} + contentContainerStyle={styles.listContent} + onEndReached={loadMore} + onEndReachedThreshold={0.1} + onMomentumScrollBegin={() => { + onEndReachedCalledDuringMomentum.current = false; + }} + ListFooterComponent={renderFooter} + refreshControl={ + + } + initialNumToRender={6} + maxToRenderPerBatch={6} + windowSize={7} + removeClippedSubviews + /> + )} + + ); }; -export default LiveAuction; \ No newline at end of file +export default LiveAuction; + +const styles = StyleSheet.create({ + root: { + flex: 1, + }, + listContent: { + paddingHorizontal: 16, + paddingBottom: 60, + paddingTop: 10, + }, + refreshBanner: { + alignSelf: 'center', + flexDirection: 'row', + alignItems: 'center', + gap: 8, + paddingVertical: 8, + paddingHorizontal: 16, + marginTop: 6, + borderRadius: 20, + zIndex: 10, + position: 'absolute', + top: 60, + }, + refreshText: { + fontWeight: '500', + } as any, + footerLoader: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 8, + paddingVertical: 16, + }, +}); diff --git a/src/Screens/Auction/LiveAuctionTile.tsx b/src/Screens/Auction/LiveAuctionTile.tsx index e190c37..c119cee 100644 --- a/src/Screens/Auction/LiveAuctionTile.tsx +++ b/src/Screens/Auction/LiveAuctionTile.tsx @@ -1,13 +1,26 @@ import React, { memo } from 'react'; -import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import { View, StyleSheet, TouchableOpacity } from 'react-native'; +import Animated, { FadeInDown } from 'react-native-reanimated'; import moment from 'moment'; -import colors from '../../Constant/Color'; import { LiveAuctionTileProps } from './types/types'; import AuctionDetail from './Modal/AuctionDetail'; -import { Calendar, Tag, Info, ArrowRight } from 'lucide-react-native'; +import { + Tag, + Calendar, + Clock, + ShoppingCart, + Trash2, + CheckCircle2, + XCircle, +} from 'lucide-react-native'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; +import NavigationString from '../../Constant/NavigationString'; +import { useNavigation } from '@react-navigation/native'; -const LiveAuctionTile: React.FC = ({ +const LiveAuctionTile: React.FC = ({ data, + index = 0, isPurchased = false, onPurchasePress, auctionDispatchDate, @@ -15,231 +28,252 @@ const LiveAuctionTile: React.FC = ({ purchaseEnd, auctionFees, auctionNumber, - isPurchasedByContractor + isPurchasedByContractor, }) => { + const { colors, spacing, shadows } = useTheme(); const [showDetail, setShowDetail] = React.useState(false); + const purchaseEnded = moment().isAfter(moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss')); + const deadlineFormatted = moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss').format('DD MMM, YY'); + const Navigation = useNavigation() as any; + const getStatus = () => { + if (isPurchasedByContractor) return 'purchased'; + if (purchaseEnded) return 'expired'; + if (isPurchased) return 'in_cart'; + return 'active'; + }; + const status = getStatus(); + + const styles = makeStyles(colors, spacing, shadows); + + if (showDetail) { + return ( + + setShowDetail(false)} /> + + ); + } return ( - - { - !showDetail ? - <> - + {/* ── Row 1: Number + Fee ── */} + + + + + #{auctionNumber} + + + + ₹{auctionFees} + + + + {/* ── Row 2: Title ── */} + + {auctionOfWork ?? 'General Auction Work'} + + + {/* ── Row 3: Date strip ── */} + + {/* Start */} + + + + + Start Date + + + + {auctionDispatchDate ?? '—'} + + + + {/* Separator */} + + + {/* Deadline */} + + + - - {/* Header */} - - setShowDetail(true)}> - - #{auctionNumber} - - ₹{auctionFees} - + + Deadline + + + + {deadlineFormatted} + + + - {/* Work Title */} - - {auctionOfWork ?? 'General Auction Work'} - + {/* ── Row 4: Actions ── */} + + {/* Ghost Details button */} + setShowDetail(true)} + activeOpacity={0.7} + > + + View Details + + - {/* Info Grid */} - - - - - Start - {auctionDispatchDate ?? '-'} - - - - - - - Deadline - - {moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss').format('DD MMM, YY')} - - - - - - {/* Action Bar */} - - setShowDetail(true)}> - - Details - - - - - {purchaseEnded ? (isPurchasedByContractor ? 'Purchased' : 'Expired') : isPurchasedByContractor ? 'Purchased' : isPurchased ? 'Remove' : 'Add to Cart'} - - {!purchaseEnded && !isPurchasedByContractor && ( - - )} - - - - {/* Auction Detail Modal */} - - - : setShowDetail(false)} /> - } - - + {/* CTA */} + {status === 'active' && ( + + + + ADD TO CART + + + )} + {status === 'in_cart' && ( + { Navigation.navigate(NavigationString.PurchaseList) }} + activeOpacity={0.85} + > + + + GO TO CART + + + )} + {status === 'purchased' && ( + + + + PURCHASED + + + )} + {status === 'expired' && ( + + + + EXPIRED + + + )} + + ); }; export default memo(LiveAuctionTile); -const styles = StyleSheet.create({ - cardWrapper: { - marginVertical: 10, - marginHorizontal: 16, - backgroundColor: '#fff', - borderRadius: 24, - flexDirection: 'row', - overflow: 'hidden', +const makeStyles = (colors: any, spacing: any, shadows: any) => + StyleSheet.create({ + card: { + backgroundColor: colors.surface, + borderRadius: 18, + borderWidth: 1, + borderColor: colors.border, + marginVertical: 7, + padding: spacing.m, + ...shadows.medium, + }, - // Modern shadow - shadowColor: '#000', - shadowOffset: { width: 0, height: 10 }, - shadowOpacity: 0.08, - shadowRadius: 20, - elevation: 8, - }, - statusTag: { - width: 6, - height: '100%', - }, - container: { - flex: 1, - padding: 20, - }, - header: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: 12, - }, - badge: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: colors.primary + '25', - paddingHorizontal: 12, - paddingVertical: 5, - borderRadius: 12, - gap: 6, - }, - badgeText: { - fontSize: 12, - fontWeight: '700', - color: colors.primary, - }, - priceText: { - fontSize: 20, - fontWeight: '800', - color: '#0f172a', - }, - workTitle: { - fontSize: 16, - fontWeight: '600', - color: '#334155', - lineHeight: 24, - marginBottom: 16, - }, - infoGrid: { - flexDirection: 'row', - justifyContent: 'space-between', - backgroundColor: '#f0f4f8', - paddingVertical: 12, - paddingHorizontal: 16, - borderRadius: 16, - marginBottom: 18, - }, - infoItem: { - flexDirection: 'row', - alignItems: 'center', - gap: 10, - }, - infoTextGroup: { - flexDirection: 'column', - }, - infoLabel: { - fontSize: 10, - color: '#94a3b8', - textTransform: 'uppercase', - fontWeight: '700', - }, - infoValue: { - fontSize: 13, - fontWeight: '700', - color: '#1e293b', - }, - actionBar: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - gap: 12, - }, - iconBtn: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - paddingHorizontal: 10, - paddingVertical: 6, - borderRadius: 10, - backgroundColor: '#e2e8f0', - }, - iconBtnText: { - fontSize: 14, - fontWeight: '700', - color: colors.primary, - }, - mainButton: { - flex: 1, - height: 46, - borderRadius: 16, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - gap: 8, - }, - btnActive: { - backgroundColor: '#4ade80', // light green - }, - btnPurchased: { - backgroundColor: colors.error, - borderWidth: 2, - borderColor: colors.error, - }, - btnDisabled: { - backgroundColor: '#e2e8f0', - }, - mainButtonText: { - fontSize: 14, - fontWeight: '800', - }, -}); + /* Top row */ + topRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: spacing.s, + }, + numberRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 5, + }, + auctionNo: { + fontWeight: '700', + } as any, + fee: { + fontWeight: '800', + } as any, + + /* Title */ + title: { + fontWeight: '700', + lineHeight: 24, + marginBottom: spacing.m, + } as any, + + /* Date strip */ + dateStrip: { + flexDirection: 'row', + alignItems: 'center', + borderRadius: 12, + paddingVertical: spacing.s + 2, + paddingHorizontal: spacing.m, + marginBottom: spacing.m, + }, + dateCol: { + flex: 1, + }, + dateHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + marginBottom: 3, + }, + dateLabel: { + fontWeight: '500', + } as any, + dateValue: { + fontWeight: '700', + } as any, + separator: { + width: StyleSheet.hairlineWidth, + height: 36, + marginHorizontal: spacing.m, + }, + + /* Actions */ + actionRow: { + flexDirection: 'row', + gap: spacing.s, + alignItems: 'center', + }, + detailsBtn: { + flex: 1, + paddingVertical: 10, + borderWidth: 1, + borderRadius: 14, + alignItems: 'center', + justifyContent: 'center', + }, + detailsText: { + fontWeight: '600', + } as any, + ctaBtn: { + flex: 2, + height: 44, + borderRadius: 12, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 6, + }, + ctaText: { + fontWeight: '800', + letterSpacing: 0.8, + } as any, + }); diff --git a/src/Screens/Auction/Modal/AuctionDetail.tsx b/src/Screens/Auction/Modal/AuctionDetail.tsx index cda3085..013a66a 100644 --- a/src/Screens/Auction/Modal/AuctionDetail.tsx +++ b/src/Screens/Auction/Modal/AuctionDetail.tsx @@ -1,64 +1,66 @@ import React, { memo, useEffect, useState } from 'react'; -import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Platform, StatusBar, Modal } from 'react-native'; -import { Briefcase, Calendar, FileText, Download, Eye, Layers, ArrowLeft, Clock } from 'lucide-react-native'; +import { + View, + StyleSheet, + TouchableOpacity, + ScrollView, + Modal, + ActivityIndicator, +} from 'react-native'; +import { + Briefcase, + Calendar, + FileText, + Download, + Eye, + Layers, + ArrowLeft, + Clock, + IndianRupee, + Wrench, + Mail, +} from 'lucide-react-native'; import { useAuctionItem, useGetAuciton, useGetAucitonDocument } from '../../../Services/ApiCall/Hooks'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { AUCTION_DOCUMENT_DOWNLOAD } from '../../../Services/ApiCall/ApiUrls'; import ReactNativeBlobUtil from 'react-native-blob-util'; import AuctionItemDetail from './AuctionItemDetail'; -import { Loader } from '../../../Component/Index'; -import colors from '../../../Constant/Color'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; import moment from 'moment'; interface Props { visible: boolean; onClose: () => void; data?: any } const AuctionFullDetail: React.FC = ({ visible, onClose, data }) => { + const inset = useSafeAreaInsets(); + const { colors, spacing, shadows } = useTheme(); - const inset = useSafeAreaInsets() - const { data: auctionData, isLoading } = useGetAuciton({ auctionId: data?.auctionId }) - const [aucitonDetail, setAuciotnDetail] = useState(auctionData) - const [allBoolean, setAllBoolean] = useState({ showItemDetail: false, selectedItem: {}, fileLoading: false }); - const { data: auctionDocuments } = useGetAucitonDocument({ auctionId: data?.auctionId }) - const { mutate } = useAuctionItem() + const { data: auctionData } = useGetAuciton({ auctionId: data?.auctionId }); + const [aucitonDetail, setAuciotnDetail] = useState(auctionData); + const [allBoolean, setAllBoolean] = useState({ + showItemDetail: false, + selectedItem: {}, + fileLoading: false, + }); + const { data: auctionDocuments } = useGetAucitonDocument({ auctionId: data?.auctionId }); + const { mutate } = useAuctionItem(); const formatDate = (date?: string) => { - if (!date) return '-'; + if (!date) return '—'; return moment(date, 'DD-MM-YYYY HH:mm:ss').format('DD MMM YYYY, hh:mm A'); }; - const isLive = data?.purchaseEnd ? moment().isBefore(moment(data.purchaseEnd, 'DD-MM-YYYY HH:mm:ss')) : false; - - const Section = ({ title, icon: Icon, children }: any) => ( - - - - {title} - - {children} - - ); - - const InfoBox = ({ label, value, full = false }: any) => ( - - {label} - {value || '-'} - - ); - + const isLive = data?.purchaseEnd + ? moment().isBefore(moment(data.purchaseEnd, 'DD-MM-YYYY HH:mm:ss')) + : false; const handleDownload = async (id: number, fileName: string) => { try { - setAllBoolean((prev: any) => ({ ...prev, fileLoading: true })); + setAllBoolean(prev => ({ ...prev, fileLoading: true })); const { dirs } = ReactNativeBlobUtil.fs; - const path = `${dirs.DownloadDir}/${fileName}.pdf`; - - - const downloadUrl = `${AUCTION_DOCUMENT_DOWNLOAD}?documentId=${id}&isAmendment=false`; - - - const response = await ReactNativeBlobUtil.config({ + await ReactNativeBlobUtil.config({ fileCache: false, addAndroidDownloads: { useDownloadManager: true, @@ -69,315 +71,340 @@ const AuctionFullDetail: React.FC = ({ visible, onClose, data }) => { description: 'Downloading document', mediaScannable: true, }, - }).fetch('GET', downloadUrl, { - Accept: 'application/pdf', - }); - - const stat = await ReactNativeBlobUtil.fs.stat(path); - + }).fetch('GET', downloadUrl, { Accept: 'application/pdf' }); } catch (error: any) { console.log('Error:', error?.message || error); } finally { - setAllBoolean((prev: any) => ({ ...prev, fileLoading: false })); + setAllBoolean(prev => ({ ...prev, fileLoading: false })); } }; - useEffect(() => { - mutate({ auctionNumber: data?.auctionNumber, status: data?.auctionPattern }, { - onSuccess: (res) => { - setAuciotnDetail((prev: any) => ({ ...prev, auctionItems: res.data, auctionDocuments })) - }, onError: (err) => { - console.log(err, '-----auction item error'); - } - }); + mutate( + { auctionNumber: data?.auctionNumber, status: data?.auctionPattern }, + { + onSuccess: (res: any) => { + setAuciotnDetail((prev: any) => ({ ...prev, auctionItems: res.data, auctionDocuments })); + }, + onError: (err: any) => { + console.log(err, '-----auction item error'); + }, + }, + ); }, [data.auctionNumber, auctionDocuments]); + const isAmended = auctionData?.auctionStatus === 'Amended'; + const kd = (key: any, amended: any) => isAmended ? amended : key; + + const styles = makeStyles(colors, spacing, shadows); + return ( - - - - {/* Full Header */} - - - + + {/* ── Header ── */} + + + - - Auction Overview - Ref: {data?.auctionNumber || 'N/A'} + + + + Auction Overview + + + Ref: {data?.auctionNumber ?? 'N/A'} + - - - {isLive ? 'LIVE' : 'CLOSED'} + + + + + {isLive ? 'LIVE' : 'CLOSED'} + - {/* Scrollable Body */} + {/* ── Body ── */} - {/* 1. Basic Info Grid */} -
- - - - - - - - -
- {/* 2. Work Description */} -
- - - - - - - -
+ {/* BASIC DETAILS */} + + + + + + + - {/* 3. Timeline Section */} -
- - - - - Auction Purchase (Start) - {formatDate( - auctionData?.auctionStatus === "Amended" ? auctionData?.amendedkeyDates.auctionPurchase.startDateTime : auctionData?.keyDates.auctionPurchase.startDateTime)} - - - - - - - Purchase Purchase (End) - {formatDate( - auctionData?.auctionStatus === "Amended" ? auctionData?.amendedkeyDates.auctionPurchase.endDateTime : auctionData?.keyDates.auctionPurchase.endDateTime)} - - - - - - - - Bidding Start (Start) - {formatDate( - auctionData?.auctionStatus === "Amended" ? auctionData?.amendedkeyDates.auctionBidding.startDateTime : auctionData?.keyDates.auctionBidding.startDateTime)} - - - - - - - - Bidding Deadline (End) - {formatDate( - auctionData?.auctionStatus === "Amended" ? auctionData?.amendedkeyDates.auctionBidding.endDateTime : auctionData?.keyDates.auctionBidding.endDateTime)} - - - - - -
+ {/* AUCTION DETAILS */} + + + + + + + - {/* 4. Item Table Section */} -
- - - Item - Time - View - - {/* Dummy Item Row */} - {aucitonDetail?.auctionItems?.map((item: any, index: number) => ( - - - {item.productName} - Category: {item?.categoryName} + {/* KEY DATES */} + + + + + + + + {/* AUCTION ITEMS */} + {aucitonDetail?.auctionItems?.length > 0 && ( + + {aucitonDetail.auctionItems.map((item: any, index: number) => ( + + + + {item.productName} + + + {item?.categoryName} · {item.timeDurationType} + - {item.timeDurationType} - { setAllBoolean((prev: any) => ({ ...prev, showItemDetail: true, selectedItem: item })) }}> - + setAllBoolean(prev => ({ ...prev, showItemDetail: true, selectedItem: item }))} + > + ))} - -
+ + )} - {/* 5. Downloads */} -
- {aucitonDetail?.auctionDocuments?.map((doc: any, index: number) => ( - { - handleDownload( - doc.auctionDocumentId, - doc.fileName.replace('.pdf', '') - ) - }}> - - - {doc.fileName} - - {allBoolean.fileLoading ? ( - - + {/* DOCUMENTS */} + {aucitonDetail?.auctionDocuments?.length > 0 && ( + + {aucitonDetail.auctionDocuments.map((doc: any, index: number) => ( + handleDownload(doc.auctionDocumentId, doc.fileName.replace('.pdf', ''))} + > + + - ) : ( - - )} - - - ))} -
+ + {doc.fileName} + + {allBoolean.fileLoading + ? + : + } +
+ ))} + + )} - {allBoolean.showItemDetail && { setAllBoolean((prev: any) => ({ ...prev, showItemDetail: false })) }} visible={allBoolean.showItemDetail} />} + {allBoolean.showItemDetail && ( + setAllBoolean(prev => ({ ...prev, showItemDetail: false }))} + visible={allBoolean.showItemDetail} + /> + )}
-
+ ); }; -const styles = StyleSheet.create({ - fullModal: { - margin: 0, - backgroundColor: '#fff', - }, - safeArea: { - flex: 1, - backgroundColor: '#fff', - }, - header: { - flexDirection: 'row', - alignItems: 'center', - padding: 16, - borderBottomWidth: 1, - borderBottomColor: '#f1f5f9', - backgroundColor: '#fff', - }, - backBtn: { - padding: 8, - marginRight: 8, - }, - headerTitleContainer: { - flex: 1, - }, - headerTitleText: { - fontSize: 18, - fontWeight: '800', - color: '#0f172a', - }, - headerIdText: { - fontSize: 13, - color: '#64748b', - }, - liveIndicator: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: '#f8fafc', - paddingHorizontal: 10, - paddingVertical: 5, - borderRadius: 20, - gap: 6, - borderWidth: 1, - borderColor: '#e2e8f0', - }, - dot: { width: 8, height: 8, borderRadius: 4 }, - liveText: { fontSize: 11, fontWeight: '800', color: '#475569' }, +export default memo(AuctionFullDetail); - scrollView: { flex: 1, backgroundColor: '#f8fafc' }, - scrollContent: { padding: 16, paddingBottom: 30 }, +/* ─── Sub-components ─── */ - sectionContainer: { - backgroundColor: '#fff', - borderRadius: 16, - padding: 16, - marginBottom: 16, - borderWidth: 1, - borderColor: '#eef2f6', - }, - sectionHeader: { - flexDirection: 'row', - alignItems: 'center', - gap: 10, - marginBottom: 20, - paddingBottom: 10, - borderBottomWidth: 1, - borderBottomColor: '#f8fafc', - }, - sectionTitle: { - fontSize: 14, - fontWeight: '800', - color: '#1e293b', - textTransform: 'uppercase', - letterSpacing: 0.5, - }, - grid: { - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', - }, - infoBox: { marginBottom: 16 }, - label: { fontSize: 12, color: '#94a3b8', fontWeight: '600', marginBottom: 4 }, - value: { fontSize: 15, fontWeight: '700', color: '#334155' }, - spacer: { height: 12 }, +const SectionCard = ({ title, icon: Icon, children, colors, spacing, shadows }: any) => { + const styles = makeStyles(colors, spacing, shadows); + return ( + + + + + {title.toUpperCase()} + + + + {children} + + ); +}; - timelineContainer: { paddingLeft: 8 }, - timelineRow: { flexDirection: 'row', alignItems: 'center', gap: 15 }, - iconCircle: { width: 34, height: 34, borderRadius: 10, backgroundColor: '#f1f5f9', alignItems: 'center', justifyContent: 'center' }, - timelineConnector: { width: 2, height: 20, backgroundColor: '#f1f5f9', marginLeft: 16 }, +const InfoRow = ({ label, value, colors, last = false, multiline = false, valueColor }: any) => ( + + + {label} + + + {value || '—'} + + +); - tableWrapper: { backgroundColor: '#f8fafc', borderRadius: 12, padding: 12 }, - tableHead: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: '#e2e8f0', paddingBottom: 8, marginBottom: 12 }, - tableLabel: { fontSize: 11, fontWeight: '800', color: '#94a3b8', flex: 1 }, - tableRow: { flexDirection: 'row', alignItems: 'center' }, - itemMain: { fontSize: 14, fontWeight: '700', color: '#1e293b' }, - itemSub: { fontSize: 12, color: '#64748b' }, - itemQty: { flex: 1, fontSize: 14, fontWeight: '700', color: '#334155' }, - actionIcon: { padding: 6 }, +/* ─── Styles factory ─── */ +const makeStyles = (colors: any, spacing: any, shadows: any) => + StyleSheet.create({ + root: { + flex: 1, + backgroundColor: colors.background, + }, - fileCard: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: '#f8fafc', padding: 12, borderRadius: 12 }, - fileIcon: { width: 40, height: 40, borderRadius: 8, backgroundColor: '#fee2e2', alignItems: 'center', justifyContent: 'center' }, - fileName: { fontSize: 14, fontWeight: '700', color: '#334155' }, - fileSize: { fontSize: 12, color: '#94a3b8' }, + /* Header */ + header: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: spacing.m, + paddingBottom: spacing.m, + backgroundColor: colors.surface, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, + gap: spacing.s, + }, + backBtn: { + width: 36, + height: 36, + borderRadius: 10, + alignItems: 'center', + justifyContent: 'center', + }, + headerCenter: { + flex: 1, + }, + headerTitle: { + fontWeight: '800', + } as any, + statusChip: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: spacing.s + 4, + paddingVertical: 6, + borderRadius: 20, + borderWidth: 1.2, + gap: 5, + }, + statusDot: { + width: 7, + height: 7, + borderRadius: 4, + }, + statusText: { + fontWeight: '800', + letterSpacing: 0.5, + } as any, - footer: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - backgroundColor: '#fff', - padding: 20, - paddingBottom: Platform.OS === 'ios' ? 34 : 20, - borderTopWidth: 1, - borderTopColor: '#f1f5f9', - }, - mainBtn: { - backgroundColor: colors.primary, - height: 56, - borderRadius: 16, - alignItems: 'center', - justifyContent: 'center', - shadowColor: colors.primary, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.2, - shadowRadius: 8, - elevation: 4, - }, - btnDisabled: { backgroundColor: '#cbd5e1' }, - mainBtnText: { color: '#fff', fontSize: 16, fontWeight: '800' }, -}); + scrollContent: { + padding: spacing.m, + paddingBottom: 50, + gap: spacing.m, + }, -export default memo(AuctionFullDetail); \ No newline at end of file + /* Card */ + card: { + backgroundColor: colors.surface, + borderRadius: 18, + paddingHorizontal: spacing.m, + paddingTop: spacing.m, + paddingBottom: spacing.s, + marginBottom: spacing.m, + ...shadows.light, + }, + cardHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.s, + marginBottom: spacing.s + 2, + }, + cardTitle: { + fontWeight: '800', + letterSpacing: 1.2, + } as any, + cardDivider: { + height: StyleSheet.hairlineWidth, + marginBottom: spacing.s, + }, + + /* Items */ + itemRow: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: spacing.s + 2, + borderBottomWidth: StyleSheet.hairlineWidth, + gap: spacing.s, + }, + itemLeft: { + flex: 1, + }, + itemName: { + fontWeight: '700', + marginBottom: 2, + } as any, + eyeBtn: { + width: 32, + height: 32, + borderRadius: 9, + alignItems: 'center', + justifyContent: 'center', + }, + + /* Docs */ + docRow: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: spacing.s + 2, + borderBottomWidth: StyleSheet.hairlineWidth, + gap: spacing.s, + }, + docIcon: { + width: 32, + height: 32, + borderRadius: 9, + alignItems: 'center', + justifyContent: 'center', + }, + docName: { + flex: 1, + fontWeight: '600', + } as any, + }); diff --git a/src/Screens/Auction/Modal/AuctionItemDetail.tsx b/src/Screens/Auction/Modal/AuctionItemDetail.tsx index ad44e46..443d5b5 100644 --- a/src/Screens/Auction/Modal/AuctionItemDetail.tsx +++ b/src/Screens/Auction/Modal/AuctionItemDetail.tsx @@ -1,38 +1,43 @@ -import React, { memo, useState } from 'react'; +import React, { memo, useState, useMemo } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Platform, Modal } from 'react-native'; import { Package, IndianRupee, FileText, Download, ArrowLeft, Info, } from 'lucide-react-native'; -import colors from '../../../Constant/Color'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useAucitonItemDetail } from '../../../Services/ApiCall/Hooks'; import ReactNativeBlobUtil from 'react-native-blob-util'; import { AUCTION_ITEM_DOCUMENT_DOWNLOAD } from '../../../Services/ApiCall/ApiUrls'; import { Loader } from '../../../Component/Index'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; interface Props { visible: boolean; onClose: () => void; data?: any; } -const Section = ({ title, icon: Icon, children }: any) => ( +const Section = ({ title, icon: Icon, children, styles, colors }: any) => ( - {title} + {title} {children} ); -const PriceTile = ({ label, fig, words, full = false }: any) => ( +const PriceTile = ({ label, fig, words, full = false, styles }: any) => ( - {label} - ₹{Number(fig).toLocaleString('en-IN')} - {words} + {label} + ₹{Number(fig).toLocaleString('en-IN')} + {words} ); + const AuctionItemDetail: React.FC = ({ visible, onClose, data }) => { const inset = useSafeAreaInsets() + const { colors, typography } = useTheme(); + const styles = useMemo(() => createStyles(colors, typography), [colors, typography]); + const [allBoolean, setAllBoolean] = useState({ fileLoading: false }); - const { data: itemData } = useAucitonItemDetail({ auctionItemId: data.id }); + const { data: itemData } = useAucitonItemDetail({ auctionItemId: data?.id }); const handleDownload = async (id: number, fileName: string) => { try { @@ -41,10 +46,7 @@ const AuctionItemDetail: React.FC = ({ visible, onClose, data }) => { const path = `${dirs.DownloadDir}/${fileName}.pdf`; - - const downloadUrl = `${AUCTION_ITEM_DOCUMENT_DOWNLOAD}?auctionItemId=${id}`; - const response = await ReactNativeBlobUtil.config({ fileCache: false, @@ -83,11 +85,11 @@ const AuctionItemDetail: React.FC = ({ visible, onClose, data }) => { {/* Navbar */} - + - Item Information - Detail View + Item Information + Detail View @@ -96,69 +98,66 @@ const AuctionItemDetail: React.FC = ({ visible, onClose, data }) => { contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false} > -
+
- {data.productName.toUpperCase()} + {data?.productName?.toUpperCase()} - {data.categoryName} + {data?.categoryName} - Sub Category - {data.subCategoryName} + Sub Category + {data?.subCategoryName} - Duration - {data.timeDurationType} + Duration + {data?.timeDurationType}
-
+
- {/* - - - */}
-
- {data.briefDescription} +
+ {data?.briefDescription}
- {itemData?.length &&
+ {itemData?.length &&
- Sr No. - Document Name - Action + Sr No. + Document Name + Action {itemData?.map((doc: any, index: any) => ( { handleDownload(doc.assetId, doc.fileName) }} key={doc.assetId}> - {index + 1} + {index + 1} - {doc.fileName} + {doc.fileName} {allBoolean.fileLoading ? ( - + ) : ( @@ -173,26 +172,26 @@ const AuctionItemDetail: React.FC = ({ visible, onClose, data }) => { ); }; -const styles = StyleSheet.create({ - fullModal: { margin: 0, backgroundColor: '#f8fafc' }, - safeArea: { flex: 1 }, +const createStyles = (colors: any, typography: any) => StyleSheet.create({ + fullModal: { margin: 0, backgroundColor: colors.background }, + safeArea: { flex: 1, backgroundColor: colors.background }, header: { flexDirection: 'row', alignItems: 'center', padding: 16, - backgroundColor: '#fff', + backgroundColor: colors.surface, borderBottomWidth: 1, - borderBottomColor: '#f1f5f9', + borderBottomColor: colors.border, }, backBtn: { padding: 8, marginRight: 8 }, - headerTitle: { fontSize: 18, fontWeight: '800', color: '#0f172a' }, - headerSub: { fontSize: 12, color: '#94a3b8', fontWeight: '600', textTransform: 'uppercase' }, + headerTitle: { fontSize: 18, color: colors.text }, + headerSub: { fontSize: 12, color: colors.textSecondary, textTransform: 'uppercase' }, scrollView: { flex: 1 }, scrollContent: { padding: 16, paddingBottom: 50 }, sectionCard: { - backgroundColor: '#fff', + backgroundColor: colors.surface, borderRadius: 24, padding: 20, marginBottom: 16, @@ -217,55 +216,54 @@ const styles = StyleSheet.create({ }, sectionTitle: { fontSize: 14, - fontWeight: '800', - color: '#334155', + color: colors.text, textTransform: 'uppercase', letterSpacing: 0.5, }, // Product Identity identityHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 }, - productNameText: { fontSize: 20, fontWeight: '900', color: '#1e293b', flex: 1 }, - categoryBadge: { backgroundColor: '#f1f5f9', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20 }, - categoryText: { fontSize: 11, fontWeight: '800', color: '#64748b', textTransform: 'uppercase' }, + productNameText: { fontSize: 20, color: colors.text, flex: 1 }, + categoryBadge: { backgroundColor: colors.border, paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20 }, + categoryText: { fontSize: 11, color: colors.textSecondary, textTransform: 'uppercase' }, specGrid: { flexDirection: 'row', gap: 24, marginTop: 5 }, specItem: { flex: 1 }, - specValue: { fontSize: 15, fontWeight: '700', color: '#334155', marginTop: 2 }, + specValue: { fontSize: 15, color: colors.text, marginTop: 2 }, // Pricing priceTile: { marginBottom: 16 }, - label: { fontSize: 11, color: '#94a3b8', fontWeight: '700', textTransform: 'uppercase', marginBottom: 4 }, - priceFig: { fontSize: 22, fontWeight: '900', color: colors.black }, - priceWords: { fontSize: 13, color: '#64748b', fontStyle: 'italic', marginTop: 2 }, - hDivider: { height: 1, backgroundColor: '#f1f5f9', marginBottom: 16 }, + label: { fontSize: 11, color: colors.textSecondary, textTransform: 'uppercase', marginBottom: 4 }, + priceFig: { fontSize: 22, color: colors.text }, + priceWords: { fontSize: 13, color: colors.textSecondary, fontStyle: 'italic', marginTop: 2 }, + hDivider: { height: 1, backgroundColor: colors.border, marginBottom: 16 }, row: { flexDirection: 'row', justifyContent: 'space-between' }, // Description - descContent: { fontSize: 15, color: '#475569', lineHeight: 22, fontWeight: '500' }, + descContent: { fontSize: 15, color: colors.textSecondary, lineHeight: 22, fontFamily: typography.fonts.medium }, // Table - docTableHead: { flexDirection: 'row', paddingBottom: 10, borderBottomWidth: 1, borderBottomColor: '#f1f5f9', marginBottom: 10 }, - tableLabel: { fontSize: 11, fontWeight: '800', color: '#94a3b8', flex: 1 }, - docRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#f8fafc', padding: 12, borderRadius: 12 }, - docIndex: { flex: 1, fontSize: 14, fontWeight: '700', color: '#64748b' }, - docTitle: { fontSize: 14, fontWeight: '700', color: '#1e293b' }, - downloadIconBox: { backgroundColor: '#fff', padding: 8, borderRadius: 10, borderWidth: 1, borderColor: '#e2e8f0' }, + docTableHead: { flexDirection: 'row', paddingBottom: 10, borderBottomWidth: 1, borderBottomColor: colors.border, marginBottom: 10 }, + tableLabel: { fontSize: 11, color: colors.textSecondary, flex: 1 }, + docRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.background, padding: 12, borderRadius: 12 }, + docIndex: { flex: 1, fontSize: 14, color: colors.textSecondary }, + docTitle: { fontSize: 14, color: colors.text }, + downloadIconBox: { backgroundColor: colors.surface, padding: 8, borderRadius: 10, borderWidth: 1, borderColor: colors.border }, // Footer footer: { position: 'absolute', bottom: 0, width: '100%', - backgroundColor: '#fff', + backgroundColor: colors.surface, padding: 20, paddingBottom: Platform.OS === 'ios' ? 34 : 20, - borderTopWidth: 1, borderTopColor: '#f1f5f9' + borderTopWidth: 1, borderTopColor: colors.border }, primaryBtn: { backgroundColor: colors.primary, height: 56, borderRadius: 18, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10 }, - primaryBtnText: { color: '#fff', fontSize: 16, fontWeight: '800' } + primaryBtnText: { color: colors.buttonText, fontSize: 16, fontFamily: typography.fonts.bold } }); export default memo(AuctionItemDetail); \ No newline at end of file diff --git a/src/Screens/Auction/PurchaseAuction.tsx b/src/Screens/Auction/PurchaseAuction.tsx index fa4417a..9e3426d 100644 --- a/src/Screens/Auction/PurchaseAuction.tsx +++ b/src/Screens/Auction/PurchaseAuction.tsx @@ -64,7 +64,7 @@ const PurchaseAuction = () => { return ( - { setFilter(prev => ({ ...prev, auctionNumber: val })) }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} /> + { setFilter(prev => ({ ...prev, auctionNumber: val })) }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} filter={false} /> {isPending ? : { - const Navigation = useNavigation() - const Dispatch = useDispatch() - const [loading, setLoading] = useState(false); - const [list, setList] = useState([]); - const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction); - - const LoginUser = useSelector((state: any) => state.token.token); - const { mutate, isPending } = useOfflineAuctionPurchase() - const { mutate: purchaseListMutation, isPending: purchaseListPending } = usePurchaseAuctionList(); + const Navigation: any = useNavigation(); + const Dispatch = useDispatch(); + const { colors, spacing, shadows } = useTheme(); + const [loading, setLoading] = useState(false); + const [list, setList] = useState([]); - const calculateTotalAmount = (item: any) => { - const gstAmount = - (Number(item.auctionFees) + Number(item.portalCharge)) * - (Number(item.gst) / 100); + const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction); + const LoginUser = useSelector((state: any) => state.token.token); - const payableAmount = - Number(item.auctionFees) + Number(item.portalCharge) + gstAmount; - return payableAmount - } + const { mutate, isPending } = useOfflineAuctionPurchase(); + const { mutate: purchaseListMutation, isPending: purchaseListPending } = usePurchaseAuctionList(); - const handleOfflinePurchase = async () => { - const body = { - auctionDetails: list.map((b: any) => ({ - auctionDispatchDate: b?.auctionDispatchDate, - auctionDispatcherNumber: b?.auctionDispatcherNumber, - auctionFees: b?.auctionFees, - auctionId: b?.auctionId, - auctionNumber: b?.auctionNumber, - auctionPattern: b?.auctionPattern, - gst: b?.gst, - gstAmount: b?.gstAmount, - payableAmount: calculateTotalAmount(b), - portalCharge: b?.portalCharge - })), - orderNo: '' - }; + const calculateTotalAmount = (item: any) => { + const gstAmount = + (Number(item.auctionFees) + Number(item.portalCharge)) * + (Number(item.gst) / 100); + return Number(item.auctionFees) + Number(item.portalCharge) + gstAmount; + }; - try { - mutate(body, { - onSuccess: (res) => { - if (res.statusCode == 200) { - showSuccessAlert(res.message) - Dispatch(clearPurchaseAuction()) - Navigation.dispatch( - CommonActions.reset({ - index: 0, - routes: [{ name: NavigationString.Home }], - }) - ); - } - }, - onError: (error) => { - console.log(error, "purchse Error"); - } - }) + const grandTotal = list.reduce( + (sum: number, item: any) => sum + calculateTotalAmount(item), + 0, + ); - } catch (error) { - console.log("purchase offline error", error); - } - } - - const handlePurchase = (type: 'online' | 'offline') => { - setLoading(true); - setTimeout(() => { - setLoading(false); - type === 'online' ? Alert.alert('Online Purchase Successful!') : handleOfflinePurchase(); - - }, 1200); + const handleOfflinePurchase = async () => { + const body = { + auctionDetails: list.map((b: any) => ({ + auctionDispatchDate: b?.auctionDispatchDate, + auctionDispatcherNumber: b?.auctionDispatcherNumber, + auctionFees: b?.auctionFees, + auctionId: b?.auctionId, + auctionNumber: b?.auctionNumber, + auctionPattern: b?.auctionPattern, + gst: b?.gst, + gstAmount: b?.gstAmount, + payableAmount: calculateTotalAmount(b), + portalCharge: b?.portalCharge, + })), + orderNo: '', }; - const renderItem = ({ item }: { item: typeof dummyAuctionList[0] }) => ( - - - {item.auctionNumber} - {item.auctionPattern} - - - - - Dispatch Date: {item.auctionDispatchDate} - - - - - Dispatcher No: {item.auctionDispatcherNumber} - - - - - Auction Fees: ₹{item.auctionFees} - - - - - GST ({item.gst}%): ₹{item.gstAmount} - - - - - Portal Charge: ₹{item.portalCharge} - - - - - Total: ₹{item.payableAmount} - - - ); - - const getPurchaseList = () => { - const auctionNumbers: string[] = SelectedPurchaseList.map( - (item: { auctionNumber: string }) => item.auctionNumber - ); - purchaseListMutation({ auctionNumbers, contractorId: LoginUser.contractorId }, { - onSuccess: (res) => { - console.log("purchaseListMutation",res) - if (res.statusCode == 200) { - - setList(res.data) - } - }, - onError: (error) => { - console.log(error, "purchase list error"); - } - }) + try { + mutate(body, { + onSuccess: res => { + if (res.statusCode === 200) { + showSuccessAlert(res.message); + Dispatch(clearPurchaseAuction()); + Navigation.dispatch( + CommonActions.reset({ index: 0, routes: [{ name: NavigationString.Home }] }), + ); + } + }, + onError: error => console.log(error, 'purchase Error'), + }); + } catch (error) { + console.log('purchase offline error', error); } - useFocusEffect( - React.useCallback(() => { - getPurchaseList() - }, [SelectedPurchaseList]) + }; + + const handlePurchase = (type: 'online' | 'offline') => { + setLoading(true); + setTimeout(() => { + setLoading(false); + type === 'online' ? undefined : handleOfflinePurchase(); + }, 1200); + }; + + const getPurchaseList = () => { + const auctionNumbers: string[] = SelectedPurchaseList.map( + (item: { auctionNumber: string }) => item.auctionNumber, ); + purchaseListMutation( + { auctionNumbers, contractorId: LoginUser.contractorId }, + { + onSuccess: res => { + if (res.statusCode === 200) setList(res.data); + }, + onError: error => console.log(error, 'purchase list error'), + }, + ); + }; + + useFocusEffect( + React.useCallback(() => { + getPurchaseList(); + }, [SelectedPurchaseList]), + ); + + const styles = makeStyles(colors, spacing, shadows); + + /* ── Render Item ── */ + const renderItem = ({ item, index }: { item: any; index: number }) => { + const total = calculateTotalAmount(item); + const gstAmount = + (Number(item.auctionFees) + Number(item.portalCharge)) * + (Number(item.gst) / 100); return ( - - - - {purchaseListPending ? : item.auctionId.toString()} - contentContainerStyle={styles.container} - />} + + {/* Top accent stripe */} + + + + {/* Header */} + + + + {String(index + 1).padStart(2, '0')} + - - - handlePurchase('online')} - > - Purchase Online - - - handlePurchase('offline')} - > - { - isPending ? : <> - Purchase Offline - - - } - - + + + {item.auctionNumber} + + {item.auctionPattern ? ( + + + + {item.auctionPattern} + + + ) : null} + + + + {/* Info Rows */} + } + label="Dispatcher No" + value={item.auctionDispatcherNumber ?? '—'} + colors={colors} + /> + } + label="Dispatch Date" + value={item.auctionDispatchDate ?? '—'} + colors={colors} + /> + + + + {/* Fee Breakdown */} + + + FEE BREAKDOWN + + + + + + + {/* Total Banner */} + + + + + Total Payable + + + + ₹ {total.toFixed(2)} + + + ); + }; + + return ( + + + + {/* Grand Total Strip */} + + + + {list.length} auction{list.length !== 1 ? 's' : ''} + + + ₹ {grandTotal.toFixed(2)} + + + + + + Ready to pay + + + + + {/* List */} + {purchaseListPending ? ( + + ) : ( + item.auctionId?.toString()} + contentContainerStyle={styles.container} + showsVerticalScrollIndicator={false} + /> + )} + + {/* Bottom Buttons */} + + handlePurchase('online')} + > + + + Online + + + + handlePurchase('offline')} + > + {isPending ? ( + + ) : ( + <> + + + Offline + + + )} + + + + {/* Loading Overlay */} + {(loading || isPending) && ( + + + + + Processing purchase… + + + + )} + + ); }; export default PurchaseAuctionList; -const styles = StyleSheet.create({ +/* ─── Sub-components ─── */ + +const InfoRow = ({ + icon, + label, + value, + colors, +}: { + icon: React.ReactNode; + label: string; + value: string; + colors: any; +}) => ( + + {icon} + + {label} + + + {value} + + +); + +const infoRowStyles = StyleSheet.create({ + row: { flexDirection: 'row', alignItems: 'center', paddingVertical: 5, gap: 6 }, + iconWrap: { width: 22, alignItems: 'center' }, + label: { flex: 1, fontWeight: '500' } as any, + value: { fontWeight: '600' } as any, +}); + +const FeeRow = ({ + label, + value, + colors, +}: { + label: string; + value: string; + colors: any; +}) => ( + + {label} + {value} + +); + +const feeRowStyles = StyleSheet.create({ + row: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 3 }, +}); + +/* ─── Styles factory ─── */ +const makeStyles = (colors: any, spacing: any, shadows: any) => + StyleSheet.create({ screen: { - flex: 1, + flex: 1, + backgroundColor: colors.background, }, + + /* Grand total strip */ + grandTotalStrip: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: colors.surface, + marginHorizontal: spacing.m, + marginTop: spacing.m, + marginBottom: spacing.s, + paddingHorizontal: spacing.m, + paddingVertical: spacing.m, + borderRadius: 18, + ...shadows.medium, + }, + grandTotalRight: { + flexDirection: 'row', + alignItems: 'center', + gap: 5, + paddingHorizontal: spacing.s + 2, + paddingVertical: spacing.xs + 2, + borderRadius: 20, + }, + container: { - padding: 16, - paddingBottom: 120, + paddingHorizontal: spacing.m, + paddingTop: spacing.s, + paddingBottom: 120, }, + + /* Card */ card: { - backgroundColor: '#fff', - borderRadius: 16, - padding: 16, - marginBottom: 16, - elevation: 5, - shadowColor: '#000', - shadowOpacity: 0.12, - shadowOffset: { width: 0, height: 3 }, - shadowRadius: 8, + backgroundColor: colors.surface, + borderRadius: 20, + marginBottom: spacing.m, + overflow: 'hidden', + ...shadows.medium, + }, + cardAccent: { + height: 4, + backgroundColor: colors.primary, + }, + cardBody: { + padding: spacing.m, }, cardHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - marginBottom: 12, + flexDirection: 'row', + alignItems: 'flex-start', + marginBottom: spacing.s, }, - title: { - fontSize: 16, - fontWeight: '700', - color: '#0F172A', + indexBadge: { + width: 32, + height: 32, + borderRadius: 10, + alignItems: 'center', + justifyContent: 'center', + marginTop: 2, }, - pattern: { - fontSize: 14, - color: colors.primary, - fontWeight: '600', + patternBadge: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + alignSelf: 'flex-start', + paddingHorizontal: spacing.s, + paddingVertical: 2, + borderRadius: 8, + marginTop: 4, }, - cardRow: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - marginBottom: 4, + divider: { + height: StyleSheet.hairlineWidth, + backgroundColor: colors.border, + marginVertical: spacing.s, }, - text: { - fontSize: 14, - color: '#555', + feeSection: { + borderRadius: 12, + padding: spacing.s + 2, + marginBottom: spacing.s, }, - totalRow: { - marginTop: 10, - backgroundColor: colors.primary, - borderRadius: 12, - paddingVertical: 10, - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - gap: 8, + feeHeader: { + letterSpacing: 1.2, + marginBottom: spacing.xs + 2, + fontWeight: '700', + } as any, + totalBanner: { + borderRadius: 14, + paddingVertical: 12, + paddingHorizontal: spacing.m, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', }, - total: { - fontSize: 15, - fontWeight: '700', - color: '#fff', + totalLeft: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.s, }, - bottomButtonContainer: { - position: 'absolute', - bottom: 0, - left: 0, - flexDirection: 'row', - backgroundColor: colors.white, - width: '100%', - paddingVertical: 20, - paddingHorizontal: 10, - elevation: 5 + + /* Footer */ + footer: { + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + flexDirection: 'row', + paddingHorizontal: spacing.m, + paddingTop: spacing.s + 4, + paddingBottom: spacing.m + 4, + backgroundColor: colors.surface, + gap: spacing.s, + ...shadows.strong, }, button: { - flex: 1, - backgroundColor: colors.primary, - paddingVertical: 14, - borderRadius: 12, - alignItems: 'center', - flexDirection: 'row', - justifyContent: 'center', + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 14, + borderRadius: 16, + gap: spacing.s, }, - buttonText: { - color: '#fff', - fontWeight: '700', - fontSize: 14, + onlineButton: { + backgroundColor: colors.surface, + borderWidth: 1.5, }, + offlineButton: { + ...shadows.medium, + }, + btnText: { + letterSpacing: 1.2, + } as any, + + /* Loading overlay */ loadingOverlay: { - ...StyleSheet.absoluteFillObject, - backgroundColor: 'rgba(0,0,0,0.2)', - justifyContent: 'center', - alignItems: 'center', - zIndex: 10, + ...StyleSheet.absoluteFillObject, + backgroundColor: 'rgba(0,0,0,0.3)', + justifyContent: 'center', + alignItems: 'center', + zIndex: 10, }, -}); + loadingCard: { + borderRadius: 20, + padding: spacing.l, + alignItems: 'center', + ...shadows.strong, + }, + }); diff --git a/src/Screens/Auction/PurchaseAuctionTile.tsx b/src/Screens/Auction/PurchaseAuctionTile.tsx index d037db4..bdc17fe 100644 --- a/src/Screens/Auction/PurchaseAuctionTile.tsx +++ b/src/Screens/Auction/PurchaseAuctionTile.tsx @@ -1,262 +1,298 @@ -import React from 'react'; -import { View, Text, StyleSheet, TouchableOpacity, Platform } from 'react-native'; +import React, { useMemo } from 'react'; +import { View, StyleSheet, TouchableOpacity } from 'react-native'; import Animated, { FadeInUp, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; import moment from 'moment'; import CountdownTimer from '../../Component/Counter/CountdownTimer'; -import colors from '../../Constant/Color'; import AuctionDetail from './Modal/AuctionDetail'; +import AppText from '../../components/AppText'; +import { useTheme } from '../../Hooks/useTheme'; +import { + Tag, + Calendar, + IndianRupee, + ArrowRight, + CircleDot, + Clock, + TimerOff, +} from 'lucide-react-native'; -const PurchaseAuctionTile = ({ item, onPressDetails, onStartBidding, buttonEnable, isAuctionEnd }: any) => { +const PurchaseAuctionTile = ({ + item, + onPressDetails, + onStartBidding, + buttonEnable, + isAuctionEnd, +}: any) => { + const { colors, spacing, shadows } = useTheme(); const [showDetail, setShowDetail] = React.useState(false); const scale = useSharedValue(1); - const elevation = useSharedValue(8); - const animatedStyle = useAnimatedStyle(() => { - return { - transform: [{ scale: scale.value }], - elevation: elevation.value, // Android-safe - }; - }, []); - const handlePressIn = () => { - scale.value = withTiming(0.97, { duration: 120 }); - elevation.value = withTiming(3, { duration: 120 }); - }; + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ scale: scale.value }], + })); - const handlePressOut = () => { - scale.value = withTiming(1, { duration: 160 }); - elevation.value = withTiming(8, { duration: 160 }); - }; + const handlePressIn = () => scale.value = withTiming(0.98, { duration: 140 }); + const handlePressOut = () => scale.value = withTiming(1, { duration: 160 }); - const statusColor = isAuctionEnd ? '#EF4444' : buttonEnable ? colors.primary : '#94A3B8'; + const isAmended = item?.auctionStatus === 'Amended'; + const biddingEnd = isAmended + ? item?.amendedkeyDates?.auctionBidding?.endDateTime?.replace(' kk', '') + : item?.keyDates?.auctionBidding?.endDateTime?.replace(' kk', ''); + const biddingStart = isAmended + ? item?.amendedkeyDates?.auctionBidding?.startDateTime?.replace(' kk', '') + : item?.keyDates?.auctionBidding?.startDateTime?.replace(' kk', ''); - const lightStatusBg = isAuctionEnd ? '#FEF2F2' : buttonEnable ? '#EFF6FF' : '#F8FAFC'; + const statusConfig = useMemo(() => { + if (isAuctionEnd) return { label: 'ENDED', color: colors.error, bg: `${colors.error}15`, Icon: TimerOff }; + if (buttonEnable) return { label: 'LIVE', color: colors.success, bg: `${colors.success}15`, Icon: CircleDot }; + return { label: 'PENDING', color: colors.textSecondary, bg: `${colors.textSecondary}12`, Icon: Clock }; + }, [isAuctionEnd, buttonEnable, colors]); + + const styles = makeStyles(colors, spacing); + + if (showDetail) { + return setShowDetail(false)} />; + } return ( - - {!showDetail ? + setShowDetail(true)} + onPressIn={handlePressIn} + onPressOut={handlePressOut} + style={styles.card} > - setShowDetail(true)} - onPressIn={handlePressIn} - onPressOut={handlePressOut} - style={styles.cardInternal} - > - - - - {/* Header */} - - - - - - {isAuctionEnd ? 'ENDED' : buttonEnable ? 'LIVE' : 'PENDING'} - - - {item.auctionNumber} - - - - - ₹{item.auctionFees?.toLocaleString()} - - Auction Fee - + {/* ── Header ── */} + + + {/* Status pill */} + + + + {statusConfig.label} + - - {/* Date */} - - - Bidding Start: - {moment( - item?.auctionStatus === "Amended" ? item?.amendedkeyDates?.auctionBidding?.startDateTime?.replace(' kk', '') : item?.keyDates?.auctionBidding?.startDateTime?.replace(' kk', ''), - 'DD-MM-YYYY HH:mm:ss' - ).format('DD MMM YYYY, hh:mm A')} - - - Bidding Ends: - {moment( - item?.auctionStatus === "Amended" ? item?.amendedkeyDates?.auctionBidding?.endDateTime?.replace(' kk', '') : item?.keyDates?.auctionBidding?.endDateTime?.replace(' kk', ''), - 'DD-MM-YYYY HH:mm:ss' - ).format('DD MMM YYYY, hh:mm A')} - - - - {/* Footer */} - - - Remaining Time - {buttonEnable && !isAuctionEnd ? ( - - ) : ( - --:--:-- - )} - - - - - {isAuctionEnd - ? 'Bidding Closed' - : !buttonEnable - ? 'Pending Bid' - : 'Proceed to Start Bidding'} - - + {/* Auction number */} + + + + {item?.auctionNumber} + - - : - setShowDetail(false)} /> - } - + + {/* Work title */} + + {item?.auctionOfWork ?? item?.auctionNumber ?? 'Auction Details'} + + + + + + {/* ── Info Grid ── */} + + {/* Fee */} + + + + + + Auction Fee + + ₹{Number(item?.auctionFees ?? 0).toLocaleString('en-IN')} + + + + + {/* Bidding End */} + + + + + + Bidding Ends + + {biddingEnd + ? moment(biddingEnd, 'DD-MM-YYYY HH:mm:ss').format('DD MMM, hh:mm A') + : '—'} + + + + + + {/* ── Footer ── */} + + + Time Remaining + + + {/* Timer */} + + + {buttonEnable && !isAuctionEnd ? ( + + ) : ( + + -- : -- : -- + + )} + + + {/* CTA Button */} + + + {isAuctionEnd ? 'Ended' : !buttonEnable ? 'Pending' : 'Bid Now'} + + {buttonEnable && !isAuctionEnd && ( + + )} + + + + + ); }; export default PurchaseAuctionTile; -const styles = StyleSheet.create({ - shadowContainer: { - marginHorizontal: 5, - marginTop: 14, - marginBottom: 6, - borderRadius: 16, - backgroundColor: '#fff', - elevation: 8, - }, - cardInternal: { - borderRadius: 16, - overflow: 'hidden', - flexDirection: 'row', - backgroundColor: '#fff', - }, - statusAccent: { - width: 5, - height: '100%', - }, - mainInfo: { - flex: 1, - padding: 16, - }, - headerRow: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'flex-start', - marginBottom: 12, - }, - statusBadge: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 8, - paddingVertical: 3, - borderRadius: 6, - marginBottom: 6, - alignSelf: 'flex-start', - }, - statusDot: { - width: 6, - height: 6, - borderRadius: 3, - marginRight: 5, - }, - statusText: { - fontSize: 10, - fontWeight: '800', - letterSpacing: 0.5, - }, - title: { - fontWeight: '800', - fontSize: 16, - color: '#1E293B', - }, - amountContainer: { - alignItems: 'flex-end', - }, - amount: { - fontWeight: '900', - fontSize: 18, - color: '#0F172A', - }, - feeLabel: { - fontSize: 10, - color: '#64748B', - fontWeight: '500', - }, - dateRow: { - backgroundColor: '#F8FAFC', - paddingHorizontal: 10, - paddingVertical: 8, - borderRadius: 10, - marginBottom: 16, - borderWidth: 1, - borderColor: '#F1F5F9', - }, - dateText: { - fontSize: 12, - color: '#334155', - fontWeight: '600', - }, - dateLabel: { - fontWeight: '400', - color: '#64748B', - }, - footer: { - borderTopWidth: 1, - borderTopColor: '#F1F5F9', - paddingTop: 12, - }, - footerTop: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: 12, - }, - footerLabel: { - fontSize: 11, - fontWeight: '600', - color: '#94A3B8', - textTransform: 'uppercase', - }, - endedText: { - fontSize: 13, - fontWeight: '700', - color: '#CBD5E1', - }, - actionButton: { - width: '100%', - height: 48, - borderRadius: 12, - justifyContent: 'center', - alignItems: 'center', - ...Platform.select({ - ios: { - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - }, - }), - }, - disabledButton: { - backgroundColor: '#E2E8F0', - }, - addText: { - color: '#fff', - fontSize: 14, - fontWeight: '800', - letterSpacing: 0.2, - }, -}); +const makeStyles = (colors: any, spacing: any) => + StyleSheet.create({ + container: { + marginHorizontal: spacing.s, + marginBottom: spacing.m, + borderRadius: 16, + borderWidth: 1, + borderColor: colors.border, + backgroundColor: colors.surface, + }, + card: { + borderRadius: 16, + backgroundColor: colors.surface, + padding: spacing.m, + borderWidth: 1, + borderColor: 'rgba(0,0,0,0.03)', + }, + + /* Header */ + header: { + marginBottom: spacing.m, + }, + headerTop: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: spacing.s, + }, + statusBadge: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 10, + paddingVertical: 5, + borderRadius: 20, + gap: 5, + }, + statusText: { + fontWeight: '800', + fontSize: 10, + textTransform: 'uppercase', + letterSpacing: 0.5, + } as any, + numberRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + numberText: { + letterSpacing: 0.4, + fontWeight: '600', + } as any, + workTitle: { + fontWeight: '600', + lineHeight: 20, + } as any, + + divider: { + height: StyleSheet.hairlineWidth, + backgroundColor: colors.border, + marginBottom: spacing.m, + opacity: 0.6, + }, + + /* Info grid */ + infoGrid: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: spacing.l, + gap: spacing.s, + }, + infoRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + flex: 1, + }, + iconBox: { + width: 36, + height: 36, + borderRadius: 10, + alignItems: 'center', + justifyContent: 'center', + }, + infoValue: { + fontWeight: '700', + } as any, + + /* Footer */ + footer: { + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: colors.background, + marginHorizontal: -spacing.m, + marginBottom: -spacing.m, + padding: spacing.m, + borderBottomLeftRadius: 16, + borderBottomRightRadius: 16, + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: colors.border, + }, + timerSection: { + flex: 1, + }, + ctaBtn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 10, + paddingHorizontal: 18, + borderRadius: 12, + gap: 5, + }, + ctaText: { + letterSpacing: 0.8, + } as any, + }); diff --git a/src/Screens/Auction/PurchaseList.tsx b/src/Screens/Auction/PurchaseList.tsx index 32b6498..6e7a0b3 100644 --- a/src/Screens/Auction/PurchaseList.tsx +++ b/src/Screens/Auction/PurchaseList.tsx @@ -1,227 +1,387 @@ import React, { useEffect, useState } from 'react'; -import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native'; -import Animated, { FadeIn, FadeOut, Layout, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; -import { ChevronDown, Trash2, Info } from 'lucide-react-native'; +import { + View, + StyleSheet, + ScrollView, + TouchableOpacity, +} from 'react-native'; +import Animated, { + FadeIn, + FadeOut, + FadeInDown, + Layout, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; +import { ChevronDown, ChevronRight, Trash2, ShoppingBag, Gavel, Tag } from 'lucide-react-native'; import { InsideHeader } from '../../Component/Index'; -import colors from '../../Constant/Color'; import { useNavigation } from '@react-navigation/native'; import NavigationString from '../../Constant/NavigationString'; import { useDispatch, useSelector } from 'react-redux'; import { togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; const PurchaseList = () => { - const Navigation: any = useNavigation() - const Dispatch = useDispatch() + const Navigation: any = useNavigation(); + const Dispatch = useDispatch(); + const { colors, spacing, shadows } = useTheme(); const [expandedIndex, setExpandedIndex] = useState(null); const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction); + const toggleExpand = (index: number) => { setExpandedIndex(prev => (prev === index ? null : index)); }; -useEffect(() => { - if (!SelectedPurchaseList || SelectedPurchaseList.length < 1) { - Navigation.goBack(); - } -}, [SelectedPurchaseList]); + useEffect(() => { + if (!SelectedPurchaseList || SelectedPurchaseList.length < 1) { + Navigation.goBack(); + } + }, [SelectedPurchaseList]); + const styles = makeStyles(colors, spacing, shadows); return ( - + - - {/* Header */} - - Purchase List - {/* */} + {/* Summary Strip */} + + + + + + {SelectedPurchaseList.length} + + {' '}auction{SelectedPurchaseList.length !== 1 ? 's' : ''} selected for purchase + + - {/* Cards */} - {SelectedPurchaseList.map((item: any, index: any) => { - const expanded = expandedIndex === index; - return ( - - toggleExpand(index)} - > - + + {SelectedPurchaseList.map((item: any, index: number) => ( + + ))} - - Auction No: {item.auctionNumber} - - - - Status:{' '} - - {item.status ? 'Open' : 'Closed'} - - - - - {/* Expanded Content */} - {expanded && ( - - {renderRow('Auction Mode', item.auctionMode)} - {renderRow('Pattern', item.auctionPattern)} - {renderRow('Fees', `₹ ${item.auctionFees}`)} - {renderRow('Department', item.deptName)} - {renderRow('Purchase End', item.purchaseEnd)} - - { Dispatch(togglePurchaseAuction(item)) }}> - - Remove - - - )} - - ); - })} + - {/* Footer */} - { Navigation.navigate(NavigationString.PurchaseAuctionList) }}> - Proceed to Purchase - + {/* Floating Proceed Button */} + + Navigation.navigate(NavigationString.PurchaseAuctionList)} + > + + Proceed to Purchase + + + + {SelectedPurchaseList.length} + + + + + ); }; export default PurchaseList; -const CardHeader = ({ title, expanded }: { - title: string; - expanded: boolean; -}) => { - const rotation = useSharedValue(expanded ? 1 : 0); - rotation.value = withTiming(expanded ? 1 : 0, { duration: 220 }); - const arrowStyle = useAnimatedStyle(() => ({ - transform: [{ rotate: `${rotation.value * 180}deg` }], - })); +/* ─── Sub-components ─── */ + +const PurchaseListItem = ({ item, index, expandedIndex, toggleExpand, colors, styles, Dispatch }: any) => { + const expanded = expandedIndex === index; + const isActive = item.status; + const chevronStyle = useChevronStyle(expanded); return ( - - {title} - - - - + + {/* Left Accent Bar */} + + + + toggleExpand(index)} + > + {/* Badge Row */} + + + + + {item.auctionNumber} + + + + + {/* Title + Chevron */} + + + {item.auctionOfWork ?? item.auctionNumber} + + + + + + + {/* Dept Tag */} + + + + {item.deptName || 'Department'} + + + + + {/* Expanded Section */} + {expanded && ( + + + + + + + + + + {/* Remove Button */} + Dispatch(togglePurchaseAuction(item))} + > + + + Remove + + + + )} + + ); }; -const renderRow = (label: string, value: string) => ( - - {label} - {value} +const DetailRow = ({ + label, + value, + colors, + valueColor, +}: { + label: string; + value: string; + colors: any; + valueColor?: string; +}) => ( + + + {label} + + + {value ?? '—'} + ); - -const styles = StyleSheet.create({ - screen: { - flex: 1, - backgroundColor: '#F8FAFC', - }, - container: { - padding: 16, - paddingBottom: 110, - }, - header: { - flexDirection: 'row', - justifyContent: 'space-between', - marginBottom: 14, - }, - heading: { - fontSize: 22, - fontWeight: '800', - color: colors.black, - }, - card: { - backgroundColor: '#fff', - borderRadius: 18, - padding: 16, - marginBottom: 14, - borderWidth: 1, - borderColor: '#E5E7EB', - }, - cardHeader: { +const detailRowStyles = StyleSheet.create({ + row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - }, - title: { - fontSize: 16, - fontWeight: '700', - color: '#0F172A', - flex: 1, - marginRight: 12, - }, - subTitle: { - fontSize: 14, - color: '#64748B', - marginTop: 6, - }, - details: { - marginTop: 16, - paddingTop: 14, - borderTopWidth: 1, - borderTopColor: '#E5E7EB', - }, - row: { - marginVertical: 6, + paddingVertical: 8, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: 'rgba(0,0,0,0.06)', }, label: { - fontSize: 13, - color: '#64748B', - }, + flex: 1, + fontWeight: '500', + } as any, value: { - fontSize: 15, + flex: 1, + textAlign: 'right', fontWeight: '600', - color: '#0F172A', - }, - removeButton: { - marginTop: 18, - backgroundColor: colors.primary, - borderRadius: 14, - paddingVertical: 12, - flexDirection: 'row', - justifyContent: 'center', - gap: 8, - }, - removeText: { - color: '#fff', - fontWeight: '700', - }, - proceedButton: { - position: 'absolute', - bottom: 16, - left: 16, - right: 16, - backgroundColor: colors.primary, - paddingVertical: 16, - borderRadius: 18, - alignItems: 'center', - }, - proceedText: { - color: '#fff', - fontSize: 16, - fontWeight: '800', - }, + } as any, }); + +/* ─── Chevron animated style hook ─── */ +function useChevronStyle(expanded: boolean) { + const rotation = useSharedValue(expanded ? 1 : 0); + rotation.value = withTiming(expanded ? 1 : 0, { duration: 220 }); + return useAnimatedStyle(() => ({ + transform: [{ rotate: `${rotation.value * 180}deg` }], + })); +} + +/* ─── Styles factory ─── */ +const makeStyles = (colors: any, spacing: any, shadows: any) => + StyleSheet.create({ + screen: { + flex: 1, + backgroundColor: colors.background, + }, + summaryStrip: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.surface, + marginHorizontal: spacing.m, + marginTop: spacing.m, + marginBottom: spacing.s, + paddingHorizontal: spacing.m, + paddingVertical: spacing.s + 2, + borderRadius: 14, + gap: spacing.s, + ...shadows.light, + }, + summaryIcon: { + width: 28, + height: 28, + borderRadius: 8, + backgroundColor: `${colors.primary}15`, + alignItems: 'center', + justifyContent: 'center', + }, + container: { + paddingHorizontal: spacing.m, + paddingTop: spacing.s, + }, + card: { + backgroundColor: colors.surface, + borderRadius: 20, + marginBottom: spacing.m, + flexDirection: 'row', + overflow: 'hidden', + ...shadows.medium, + }, + cardExpanded: { + ...shadows.strong, + }, + accentBar: { + width: 5, + borderTopLeftRadius: 20, + borderBottomLeftRadius: 20, + }, + cardInner: { + flex: 1, + paddingHorizontal: spacing.m, + paddingVertical: spacing.m, + }, + badgeRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginBottom: spacing.s, + }, + auctionBadge: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: `${colors.primary}15`, + paddingHorizontal: spacing.s, + paddingVertical: 3, + borderRadius: 8, + gap: 4, + }, + badgeText: { + fontWeight: '600', + letterSpacing: 0.2, + } as any, + titleRow: { + flexDirection: 'row', + alignItems: 'flex-start', + justifyContent: 'space-between', + gap: spacing.s, + }, + title: { + flex: 1, + fontWeight: '700', + } as any, + deptRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 5, + marginTop: spacing.xs + 2, + }, + deptText: { + letterSpacing: 0.3, + } as any, + detailsContainer: { + marginTop: spacing.s, + }, + divider: { + height: 1, + backgroundColor: colors.border, + marginBottom: spacing.s, + }, + removeButton: { + marginTop: spacing.m, + backgroundColor: colors.error, + borderRadius: 14, + paddingVertical: 12, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + gap: spacing.s, + }, + removeText: { + letterSpacing: 1, + } as any, + footerContainer: { + position: 'absolute', + bottom: 20, + left: spacing.m, + right: spacing.m, + }, + proceedButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 16, + borderRadius: 20, + gap: spacing.s, + ...shadows.strong, + }, + proceedText: { + letterSpacing: 1.5, + } as any, + countBadge: { + backgroundColor: '#fff', + borderRadius: 12, + minWidth: 24, + height: 24, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 6, + }, + }); diff --git a/src/Screens/Bidding/Component/BiddingCard.tsx b/src/Screens/Bidding/Component/BiddingCard.tsx index 967a0f5..6cabba3 100644 --- a/src/Screens/Bidding/Component/BiddingCard.tsx +++ b/src/Screens/Bidding/Component/BiddingCard.tsx @@ -1,9 +1,11 @@ import React, { memo, useCallback, useRef, useState } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, Platform, TextInput, Alert } from 'react-native'; import Animated, { FadeInUp, Layout, interpolate, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'; -import colors from '../../../Constant/Color'; +import colors_const from '../../../Constant/Color'; import AuctionItemDetail from '../../Auction/Modal/AuctionItemDetail'; import MainStyle from '../../../Styles/MainStyle'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; import { Loader } from '../../../Component/Index'; import { useSelector } from 'react-redux'; import axios from 'axios'; @@ -11,6 +13,8 @@ import Variables from '../../../Constant/Variable'; import { showErrorAlert, showSuccessAlert } from '../../../Constant/ShowDailog'; const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, SelectedAuction, allBoolean, setAllBoolean }: any) => { + const { colors } = useTheme(); + const styles = React.useMemo(() => makeStyles(colors), [colors]); const Token = useSelector((state: any) => state.token.token) const [showDetail, setShowDetail] = useState(false); const [value, setValue] = useState(`${item.bidAmount}`); @@ -100,7 +104,7 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec !showDetail ? {/* HEADER */} - {item.productName} + {item.productName} {/* auctionStartValueFigure */} - + Initial Amt : {item?.auctionStartValueFigure} - + - + {item.me ? 'LEADING' : 'LIVE'} - + - + ▼ - + {SelectedAuction?.auctionNumber} - + @@ -138,15 +142,15 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec {/* PRICE INFO */} - Current Bid - ₹{item?.bidAmount || item.auctionStartValueFigure} + Current Bid + ₹{item?.bidAmount || item.auctionStartValueFigure} - Increment - + Increment + +₹{item.bidVariationValue} - + @@ -154,7 +158,7 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec - Set Your Bid + Set Your Bid {!allBoolean.oneTimeBid && - − + − - ₹ - {bidValue} + ₹ + {bidValue} - + + + } { (allBoolean.oneTimeBid || allBoolean.rebid) && - ₹ + ₹ } @@ -194,7 +198,7 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid} > { - allState.isLoading ? : Confirm Bid + allState.isLoading ? : Confirm Bid } : null} @@ -209,11 +213,11 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec export default memo(BiddingCard); -const styles = StyleSheet.create({ +const makeStyles = (colors: any) => StyleSheet.create({ inputBox2: { flexDirection: 'row', alignItems: 'center', - backgroundColor: '#F1F5F9', + backgroundColor: colors.background, borderRadius: 12, height: 45, paddingHorizontal: 12, @@ -221,39 +225,23 @@ const styles = StyleSheet.create({ marginBottom: 20 }, currency2: { - fontSize: 16, - fontWeight: 'bold', - color: '#64748B', marginRight: 4, - }, + } as any, input: { flex: 1, fontSize: 18, fontWeight: '700', - color: '#0F172A', width: '100%' }, card: { - backgroundColor: '#FFF', borderRadius: 24, padding: 20, marginBottom: 16, borderWidth: 1, - borderColor: '#E2E8F0', overflow: 'hidden', - ...Platform.select({ - ios: { - shadowColor: '#000', - shadowOpacity: 0.05, - shadowRadius: 10, - }, - android: { elevation: 2 }, - }), }, leadingCard: { - borderColor: '#22C55E', borderWidth: 2, - backgroundColor: '#F0FDF4', }, cardHeader: { flexDirection: 'row', @@ -261,27 +249,24 @@ const styles = StyleSheet.create({ alignItems: 'flex-start', }, headerRight: { flexDirection: 'column', alignItems: 'center', gap: 8 }, - productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' }, - itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 }, + productName: {} as any, + itemIdText: { marginTop: 2 } as any, badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 }, - liveBadge: { backgroundColor: '#FEE2E2' }, - meBadge: { backgroundColor: '#BBF7D0' }, - badgeText: { fontSize: 10, fontWeight: '900' }, - liveText: { color: '#EF4444' }, - meText: { color: '#15803D' }, - arrow: { fontSize: 10, color: '#94A3B8' }, + liveBadge: { backgroundColor: `${colors.error}20` }, + meBadge: { backgroundColor: `${colors.success}20` }, + badgeText: { fontSize: 10 } as any, + liveText: { color: colors.error }, + meText: { color: colors.success }, + arrow: { fontSize: 10 }, priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 }, label: { - fontSize: 11, - color: '#94A3B8', textTransform: 'uppercase', - fontWeight: '600', - }, - mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' }, - incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' }, + } as any, + mainPrice: {} as any, + incrementText: {} as any, collapsibleContainer: { overflow: 'hidden' }, - divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 }, - bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 }, + divider: { height: 1, backgroundColor: colors.border, marginVertical: 10 }, + bidLabel: { marginBottom: 10 } as any, stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 }, stepBtn: { width: 45, @@ -291,19 +276,19 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, - disabledBtn: { backgroundColor: '#CBD5E1' }, - stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' }, + disabledBtn: { backgroundColor: colors.border }, + stepText: {} as any, inputBox: { flex: 1, height: 45, - backgroundColor: '#F1F5F9', + backgroundColor: colors.background, borderRadius: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, - currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 }, - bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' }, + currency: { marginRight: 4 } as any, + bidValueText: {} as any, submitBtn: { height: 50, backgroundColor: colors.primary, @@ -311,5 +296,5 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, - submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' }, + submitBtnText: {} as any, }); diff --git a/src/Screens/Bidding/Component/ConfirmBidModal.tsx b/src/Screens/Bidding/Component/ConfirmBidModal.tsx new file mode 100644 index 0000000..3f8686f --- /dev/null +++ b/src/Screens/Bidding/Component/ConfirmBidModal.tsx @@ -0,0 +1,133 @@ +import React from 'react'; +import { View, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native'; +import { ShieldCheck, X } from 'lucide-react-native'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; + +interface Props { + visible: boolean; + onClose: () => void; + onConfirm: () => void; + bidAmount: number; + isLoading?: boolean; +} + +const ConfirmBidModal = ({ visible, onClose, onConfirm, bidAmount, isLoading }: Props) => { + const { colors, shadows } = useTheme(); + + if (!visible) return null; + + return ( + + + + + + + + + + + + + Confirm Your Bid + + + Are you sure you want to place this bid? This action cannot be undone. + + + + + BID AMOUNT + + + ₹{bidAmount?.toLocaleString('en-IN') || 0} + + + + + + Cancel + + + + {isLoading ? ( + + ) : ( + Submit Bid + )} + + + + + ); +}; + +export default ConfirmBidModal; + +const styles = StyleSheet.create({ + overlay: { + ...StyleSheet.absoluteFillObject, + backgroundColor: 'rgba(0,0,0,0.7)', + justifyContent: 'center', + alignItems: 'center', + padding: 24, + zIndex: 9999, + elevation: 9999, + }, + modalCard: { + width: '100%', + borderRadius: 24, + padding: 24, + alignItems: 'center', + }, + closeBtn: { + position: 'absolute', + top: 16, + right: 16, + width: 32, + height: 32, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + }, + iconBox: { + width: 64, + height: 64, + borderRadius: 32, + alignItems: 'center', + justifyContent: 'center', + marginBottom: 16, + }, + title: { + marginBottom: 8, + }, + subtitle: { + marginBottom: 24, + paddingHorizontal: 10, + }, + amountBox: { + width: '100%', + paddingVertical: 16, + borderRadius: 16, + borderWidth: 1, + alignItems: 'center', + justifyContent: 'center', + marginBottom: 24, + gap: 4, + }, + btnRow: { + flexDirection: 'row', + gap: 12, + width: '100%', + }, + btn: { + flex: 1, + height: 50, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + }, + cancelBtn: {}, + confirmBtn: {}, +}); diff --git a/src/Screens/Bidding/Component/EmdComplete.tsx b/src/Screens/Bidding/Component/EmdComplete.tsx index 049c170..617bdff 100644 --- a/src/Screens/Bidding/Component/EmdComplete.tsx +++ b/src/Screens/Bidding/Component/EmdComplete.tsx @@ -1,105 +1,209 @@ import React from 'react'; -import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import { View, StyleSheet, TouchableOpacity } from 'react-native'; import LottieView from 'lottie-react-native'; -import Animated, { FadeInUp } from 'react-native-reanimated'; -import colors from '../../../Constant/Color'; +import Animated, { FadeInDown, FadeInUp, ZoomIn } from 'react-native-reanimated'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; +import { + ShieldCheck, + CheckCircle2, + FileText, + Gavel, + ArrowRight, +} from 'lucide-react-native'; + +const STEPS = [ + { icon: FileText, label: 'Documents Submitted', done: true }, + { icon: ShieldCheck, label: 'EMD Verified', done: true }, + { icon: Gavel, label: 'Ready to Bid', done: true }, +]; const EmdComplete = ({ onNext }: { onNext?: () => void }) => { - return ( - - + const { colors, spacing, shadows } = useTheme(); - + return ( + - - EMD Complete - - - Your submission was successful - + {/* ── Center content ── */} + - - - Next - - - - ); + {/* Success animation bubble */} + + + + + {/* Title */} + + + EMD Submitted! + + + Your Earnest Money Deposit has been{'\n'}successfully recorded. + + + + {/* ── Step Completion Chips ── */} + + {STEPS.map((step, index) => ( + + + + + + + {step.label} + + + + {index < STEPS.length - 1 && ( + + )} + + ))} + + + {/* ── Info badge ── */} + + + 🎉 You are now eligible to participate in the bidding process + + + + + {/* ── Bottom CTA ── */} + + + + Continue to Bidding + + + + + + + ); }; export default EmdComplete; -/* ---------------- Styles ---------------- */ +/* ─── Styles ─── */ const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#F8FAFC', - alignItems: 'center', - justifyContent: 'center', - }, + root: { + flex: 1, + alignItems: 'center', + justifyContent: 'space-between', + paddingVertical: 40, + }, - confetti: { - position: 'absolute', - top: 0, - width: '100%', - height: '100%', - }, + centerContent: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 24, + gap: 24, + width: '100%', + }, - success: { - width: 220, - height: 220, - marginTop: -100 - }, + /* Lottie bubble */ + lottieBubble: { + width: 160, + height: 160, + borderRadius: 80, + alignItems: 'center', + justifyContent: 'center', + }, + lottie: { + width: 130, + height: 130, + }, - title: { - marginTop: 16, - fontSize: 24, - fontWeight: '900', - color: '#0F172A', - letterSpacing: 0.5, - }, + /* Title */ + titleBlock: { + alignItems: 'center', + gap: 8, + }, + title: { + letterSpacing: 0.3, + } as any, + subtitle: { + lineHeight: 22, + opacity: 0.75, + } as any, - subText: { - marginTop: 8, - fontSize: 14, - color: '#475569', - }, + /* Steps */ + stepsCard: { + width: '100%', + borderRadius: 20, + paddingVertical: 16, + paddingHorizontal: 20, + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.06, + shadowRadius: 12, + elevation: 4, + }, + stepRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + }, + stepIconBox: { + width: 34, + height: 34, + borderRadius: 10, + alignItems: 'center', + justifyContent: 'center', + }, + stepLabel: { + flex: 1, + } as any, + stepConnector: { + width: 2, + height: 14, + marginLeft: 16, + borderRadius: 1, + marginVertical: 2, + }, - /* ---------- Floating Next Button ---------- */ - nextWrapper: { - position: 'absolute', - right: 20, - bottom: 30, - }, + /* Info badge */ + infoBadge: { + width: '100%', + borderRadius: 16, + borderWidth: 1.2, + paddingVertical: 12, + paddingHorizontal: 16, + }, + infoText: { + lineHeight: 20, + } as any, - nextBtn: { - backgroundColor: colors.primary, - paddingHorizontal: 26, - paddingVertical: 14, - borderRadius: 28, - - shadowColor: colors.primary, - shadowOffset: { width: 0, height: 8 }, - shadowOpacity: 0.35, - shadowRadius: 14, - elevation: 6, - }, - - nextText: { - color: '#FFFFFF', - fontSize: 15, - fontWeight: '800', - letterSpacing: 0.4, - }, + /* CTA */ + bottomArea: { + width: '100%', + paddingHorizontal: 24, + }, + ctaBtn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 8, + paddingVertical: 16, + borderRadius: 18, + }, + ctaText: { + letterSpacing: 0.5, + } as any, }); diff --git a/src/Screens/Bidding/Component/GoBackModal.tsx b/src/Screens/Bidding/Component/GoBackModal.tsx new file mode 100644 index 0000000..7c44668 --- /dev/null +++ b/src/Screens/Bidding/Component/GoBackModal.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { View, StyleSheet, TouchableOpacity } from 'react-native'; +import { LogOut, X } from 'lucide-react-native'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; + +interface Props { + visible: boolean; + onClose: () => void; + onConfirm: () => void; +} + +const GoBackModal = ({ visible, onClose, onConfirm }: Props) => { + const { colors, shadows } = useTheme(); + + if (!visible) return null; + + return ( + + + + + + + + + + + + + Leave Bidding? + + + Are you sure you want to go back to the Dashboard? You can always return to this panel later. + + + + + Stay + + + + Leave + + + + + ); +}; + +export default GoBackModal; + +const styles = StyleSheet.create({ + overlay: { + ...StyleSheet.absoluteFillObject, + backgroundColor: 'rgba(0,0,0,0.7)', + justifyContent: 'center', + alignItems: 'center', + padding: 24, + zIndex: 9999, + elevation: 9999, + }, + modalCard: { + width: '100%', + borderRadius: 24, + padding: 24, + alignItems: 'center', + }, + closeBtn: { + position: 'absolute', + top: 16, + right: 16, + width: 32, + height: 32, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + }, + iconBox: { + width: 64, + height: 64, + borderRadius: 32, + alignItems: 'center', + justifyContent: 'center', + marginBottom: 16, + }, + title: { + marginBottom: 8, + }, + subtitle: { + marginBottom: 24, + paddingHorizontal: 10, + }, + btnRow: { + flexDirection: 'row', + gap: 12, + width: '100%', + }, + btn: { + flex: 1, + height: 50, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + }, + cancelBtn: {}, + confirmBtn: {}, +}); diff --git a/src/Screens/Bidding/Component/ReverseBiddingCard.tsx b/src/Screens/Bidding/Component/ReverseBiddingCard.tsx index ee484ae..b8f7b8d 100644 --- a/src/Screens/Bidding/Component/ReverseBiddingCard.tsx +++ b/src/Screens/Bidding/Component/ReverseBiddingCard.tsx @@ -1,9 +1,11 @@ import React, { memo, useCallback, useRef, useState } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, Platform, TextInput } from 'react-native'; import Animated, { FadeInUp, Layout, interpolate, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'; -import colors from '../../../Constant/Color'; +import colors_const from '../../../Constant/Color'; import AuctionItemDetail from '../../Auction/Modal/AuctionItemDetail'; import MainStyle from '../../../Styles/MainStyle'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; import axios, { all } from 'axios'; import Variables from '../../../Constant/Variable'; import { useSelector } from 'react-redux'; @@ -11,6 +13,8 @@ import { showErrorAlert, showSuccessAlert } from '../../../Constant/ShowDailog'; import { Loader } from '../../../Component/Index'; const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, SelectedAuction, allBoolean, setAllBoolean }: any) => { + const { colors } = useTheme(); + const styles = React.useMemo(() => makeStyles(colors), [colors]); const Token = useSelector((state: any) => state.token.token) const [allState, setAllState] = useState({ isLoading: false }) const [showDetail, setShowDetail] = useState(false); @@ -108,9 +112,10 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid entering={FadeInUp.delay(100)} style={[ styles.card, + item.me ? styles.leadingCard : undefined, { - borderColor: item.me ? '#22C55E' : '#EF4444', - backgroundColor: item.me ? '#F0FDF4' : '#FDF0F0', + borderColor: item.me ? colors.success : colors.error, + backgroundColor: item.me ? `${colors.success}10` : `${colors.error}10`, }, ]} > @@ -121,10 +126,10 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid style={styles.cardHeader} > - {item.productName} - + {item.productName} + Initial Amt : ₹{item.auctionStartValue} - + @@ -135,38 +140,40 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid item.me ? styles.meBadge : styles.liveBadge, ]} > - {item.me ? 'LEADING' : 'LIVE'} - + - + ▼ - + {SelectedAuction?.auctionNumber} - + {/* PRICE INFO */} - Current Bid - ₹{item.bidAmount || item.auctionStartValue} + Current Bid + ₹{item.bidAmount || item.auctionStartValue} - Decrement - + Decrement + -₹{item.emdAmount} - + @@ -175,7 +182,7 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid - Set Your Bid + Set Your Bid {/* DECREASE */} @@ -187,25 +194,25 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid !canDecrease && styles.disabledBtn, ]} > - − + − } {/* VALUE */} {!allBoolean.oneTimeBid && - ₹ - {bidValue} + ₹ + {bidValue} } { (allBoolean.oneTimeBid || allBoolean.rebid) && - ₹ + ₹ } @@ -219,7 +226,7 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid !canIncrease && styles.disabledBtn, ]} > - + + + } @@ -228,7 +235,7 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid} > { - allState.isLoading ? : Confirm Bid + allState.isLoading ? : Confirm Bid } @@ -265,50 +272,34 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid export default ReverseBiddingCard; -const styles = StyleSheet.create({ +const makeStyles = (colors: any) => StyleSheet.create({ inputBox2: { flexDirection: 'row', alignItems: 'center', - backgroundColor: '#F1F5F9', + backgroundColor: colors.background, borderRadius: 12, height: 45, paddingHorizontal: 12, width: '100%' }, currency2: { - fontSize: 16, - fontWeight: 'bold', - color: '#64748B', marginRight: 4, - }, + } as any, input: { flex: 1, fontSize: 18, fontWeight: '700', - color: '#0F172A', width: '100%' }, card: { - backgroundColor: '#FFF', borderRadius: 24, padding: 20, marginBottom: 16, borderWidth: 1, - borderColor: '#E2E8F0', overflow: 'hidden', - ...Platform.select({ - ios: { - shadowColor: '#000', - shadowOpacity: 0.05, - shadowRadius: 10, - }, - android: { elevation: 2 }, - }), }, leadingCard: { - borderColor: '#22C55E', borderWidth: 2, - backgroundColor: '#F0FDF4', }, cardHeader: { flexDirection: 'row', @@ -316,27 +307,24 @@ const styles = StyleSheet.create({ alignItems: 'flex-start', }, headerRight: { flexDirection: 'column', alignItems: 'center', gap: 8 }, - productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' }, - itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 }, + productName: {} as any, + itemIdText: { marginTop: 2 } as any, badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 }, - liveBadge: { backgroundColor: '#FEE2E2' }, - meBadge: { backgroundColor: '#BBF7D0' }, - badgeText: { fontSize: 10, fontWeight: '900' }, - liveText: { color: '#EF4444' }, - meText: { color: '#15803D' }, - arrow: { fontSize: 10, color: '#94A3B8' }, + liveBadge: { backgroundColor: `${colors.error}20` }, + meBadge: { backgroundColor: `${colors.success}20` }, + badgeText: { fontSize: 10 } as any, + liveText: { color: colors.error }, + meText: { color: colors.success }, + arrow: { fontSize: 10 }, priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 }, label: { - fontSize: 11, - color: '#94A3B8', textTransform: 'uppercase', - fontWeight: '600', - }, - mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' }, - incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' }, + } as any, + mainPrice: {} as any, + incrementText: {} as any, collapsibleContainer: { overflow: 'hidden' }, - divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 }, - bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 }, + divider: { height: 1, backgroundColor: colors.border, marginVertical: 10 }, + bidLabel: { marginBottom: 10 } as any, stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 }, stepBtn: { width: 45, @@ -346,19 +334,19 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, - disabledBtn: { backgroundColor: '#CBD5E1' }, - stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' }, + disabledBtn: { backgroundColor: colors.border }, + stepText: {} as any, inputBox: { flex: 1, height: 45, - backgroundColor: '#F1F5F9', + backgroundColor: colors.background, borderRadius: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, - currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 }, - bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' }, + currency: { marginRight: 4 } as any, + bidValueText: {} as any, submitBtn: { height: 50, backgroundColor: colors.primary, @@ -366,5 +354,5 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, - submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' }, + submitBtnText: {} as any, }); diff --git a/src/Screens/Bidding/Component/TemplateFormModal.tsx b/src/Screens/Bidding/Component/TemplateFormModal.tsx index 882a04d..632ae46 100644 --- a/src/Screens/Bidding/Component/TemplateFormModal.tsx +++ b/src/Screens/Bidding/Component/TemplateFormModal.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react' -import { Modal, View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView } from 'react-native' +import React, { useState, useMemo } from 'react' +import { Modal, View, StyleSheet, TextInput, TouchableOpacity, ScrollView, Platform } from 'react-native' import { Formik } from 'formik' import * as Yup from 'yup' import { pick } from '@react-native-documents/picker' @@ -7,16 +7,20 @@ import { useSelector } from 'react-redux' import { apiCall } from '../../../Axios/Axios' import { TECHNICAL_DOCUMENT_SUBMIT, TECHNICAL_VALUE_SUBMIT } from '../../../Services/ApiCall/ApiUrls' import { Props } from '../Type/BidType' -import colors from '../../../Constant/Color' import { Loader } from '../../../Component/Index' import { useNavigation } from '@react-navigation/native' import NavigationString from '../../../Constant/NavigationString' +import { useTheme } from '../../../Hooks/useTheme' +import AppText from '../../../components/AppText' +import { Upload, FileText, X } from 'lucide-react-native' const TemplateFormModal: React.FC = ({ visible, template, setAllBoolean }) => { if (!template) return null const SelectedAuction = useSelector((state: any) => state.auction.auction) const Navigation: any = useNavigation() + const { colors, spacing, typography } = useTheme() + const styles = useMemo(() => makeStyles(colors, spacing, typography), [colors, spacing, typography]) const [loading, setLoading] = useState(false) const initialValues = template?.labels?.reduce((acc: any, label: any) => { acc[label.labelName] = '' @@ -65,10 +69,10 @@ const TemplateFormModal: React.FC = ({ visible, template, setAllBoolean } if (label.labelType === '3') { return ( - + {label.labelName} - {label.mandatory && *} - + {label.mandatory && *} + = ({ visible, template, setAllBoolean } pickFile(label.labelName, setFieldValue) } > - - {value?.name || 'Upload PDF'} - + {value?.name ? ( + + + + {value.name} + + + ) : ( + + + + Upload PDF + + + )} {showError && ( - {error} + {error} )} ) @@ -98,10 +109,10 @@ const TemplateFormModal: React.FC = ({ visible, template, setAllBoolean } return ( - + {label.labelName} - {label.mandatory && *} - + {label.mandatory && *} + = ({ visible, template, setAllBoolean } showError && styles.errorBorder, ]} placeholder={`Enter ${label.labelName}`} - placeholderTextColor="#9CA3AF" + placeholderTextColor={colors.textSecondary} value={value} onChangeText={(text) => setFieldValue(label.labelName, text) @@ -122,7 +133,7 @@ const TemplateFormModal: React.FC = ({ visible, template, setAllBoolean } /> {showError && ( - {error} + {error} )} ) @@ -210,26 +221,26 @@ const TemplateFormModal: React.FC = ({ visible, template, setAllBoolean } - Technical Documents - + Technical Documents + Fill the form, Click save when you're done. - + - ✕ + - Template + Template - + {template.templateName} - + = ({ visible, template, setAllBoolean } {loading ? ( ) : ( - + Submit - + )} @@ -287,129 +298,98 @@ const TemplateFormModal: React.FC = ({ visible, template, setAllBoolean } export default TemplateFormModal -const styles = StyleSheet.create({ - screen: { - flex: 1, - backgroundColor: '#F9FAFB', - }, - header: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 16, - paddingVertical: 14, - backgroundColor: '#FFFFFF', - borderBottomWidth: 1, - borderColor: '#E5E7EB', - }, - headerLeft: { - flex: 1, - }, - headerTitle: { - fontSize: 16, - fontWeight: '700', - color: '#111827', - }, - headerSubtitle: { - fontSize: 12, - color: '#6B7280', - marginTop: 2, - }, - closeBtn: { - padding: 6, - }, - closeText: { - fontSize: 18, - color: '#6B7280', - }, - content: { - paddingHorizontal: 16, - }, - card: { - backgroundColor: '#FFFFFF', - borderColor: '#E5E7EB', - paddingVertical: 20 - }, - templateHeading: { - fontSize: 18, - fontWeight: '700', - textAlign: 'center', - marginBottom: 16, - color: '#111827', - }, - - templateBox: { - borderWidth: 1, - borderColor: '#E5E7EB', - borderRadius: 10, - padding: 16, - }, - templateName: { - fontSize: 14, - fontWeight: '700', - color: '#111827', - marginBottom: 12, - }, - field: { - marginBottom: 14, - }, - label: { - fontSize: 13, - fontWeight: '600', - color: '#374151', - marginBottom: 6, - }, - required: { - color: '#EF4444', - }, - input: { - backgroundColor: '#FFFFFF', - borderWidth: 1, - borderColor: '#E5E7EB', - borderRadius: 8, - padding: 12, - fontSize: 14, - color: '#111827', - }, - filePicker: { - borderWidth: 1, - borderColor: '#E5E7EB', - borderRadius: 8, - padding: 12, - }, - fileText: { - color: '#6B7280', - fontSize: 14, - }, - fileSelected: { - color: '#111827', - fontWeight: '600', - }, - errorText: { - color: '#EF4444', - fontSize: 12, - marginTop: 4, - }, - errorBorder: { - borderColor: '#EF4444', - }, - submitRow: { - marginTop: 20, - alignItems: 'flex-end', - height: 50 - }, - submitBtn: { - backgroundColor: colors.primary, - paddingVertical: 10, - paddingHorizontal: 22, - borderRadius: 8, - minWidth: 120, - alignItems: 'center', - }, - submitText: { - color: '#FFFFFF', - fontWeight: '700', - fontSize: 14, - }, -}) - +const makeStyles = (colors: any, spacing: any, typography: any) => + StyleSheet.create({ + screen: { + flex: 1, + backgroundColor: colors.background, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 14, + backgroundColor: colors.surface, + borderBottomWidth: 1, + borderColor: colors.border, + }, + headerLeft: { + flex: 1, + }, + closeBtn: { + padding: 8, + backgroundColor: colors.overlay, + borderRadius: 10, + }, + content: { + paddingHorizontal: 16, + }, + card: { + backgroundColor: colors.surface, + borderRadius: 18, + marginTop: spacing.m, + padding: spacing.m, + ...Platform.select({ + ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.06, shadowRadius: 12 }, + android: { elevation: 3 }, + }), + }, + templateBox: { + borderWidth: 1, + borderColor: colors.border, + borderRadius: 14, + padding: 16, + }, + field: { + marginBottom: 14, + }, + label: { + letterSpacing: 0.5, + textTransform: 'uppercase', + marginBottom: 6, + } as any, + input: { + backgroundColor: colors.background, + borderWidth: 1.2, + borderColor: colors.border, + borderRadius: 12, + padding: 12, + fontSize: 14, + color: colors.text, + }, + filePicker: { + borderWidth: 1.2, + borderColor: colors.border, + backgroundColor: colors.background, + borderRadius: 12, + padding: 12, + }, + fileSelectedRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + }, + errorText: { + marginTop: 4, + } as any, + errorBorder: { + borderColor: colors.error, + }, + submitRow: { + marginTop: 20, + alignItems: 'flex-end', + height: 50, + }, + submitBtn: { + backgroundColor: colors.primary, + paddingVertical: 12, + paddingHorizontal: 24, + borderRadius: 14, + minWidth: 120, + alignItems: 'center', + }, + submitText: { + letterSpacing: 0.5, + } as any, + }) diff --git a/src/Screens/Bidding/Emd.tsx b/src/Screens/Bidding/Emd.tsx index e45b591..5ee0828 100644 --- a/src/Screens/Bidding/Emd.tsx +++ b/src/Screens/Bidding/Emd.tsx @@ -1,649 +1,647 @@ import React, { useEffect, useState } from 'react'; -import { View, Text, FlatList, TouchableOpacity, StyleSheet, TextInput, Alert } from 'react-native'; +import { + View, + Text, + FlatList, + TouchableOpacity, + StyleSheet, + TextInput, + Alert, + ActivityIndicator, +} from 'react-native'; import { pick } from '@react-native-documents/picker'; import Animated, { FadeInDown, FadeOutUp, Layout } from 'react-native-reanimated'; import BouncyCheckbox from 'react-native-bouncy-checkbox'; -import { InsideHeader, Loader, Skelton } from '../../Component/Index'; -import colors from '../../Constant/Color'; +import { InsideHeader, Skelton } from '../../Component/Index'; import EmdComplete from './Component/EmdComplete'; import NavigationString from '../../Constant/NavigationString'; import { useNavigation } from '@react-navigation/native'; import { useSelector } from 'react-redux'; import { useAuctionItem, useEmdCheck } from '../../Services/ApiCall/Hooks'; import { apiCall } from '../../Axios/Axios'; -import { AUCITON_SINGLE_EMD, AUCTION_ITEM_SUBMIT, CHECK_TECHNICAL_DOCUMENTAION, GET_AUCTION_DETAILS, GET_TECHNICAL_DOCUMENTAION } from '../../Services/ApiCall/ApiUrls'; +import { + AUCITON_SINGLE_EMD, + AUCTION_ITEM_SUBMIT, + CHECK_TECHNICAL_DOCUMENTAION, + GET_AUCTION_DETAILS, + GET_TECHNICAL_DOCUMENTAION, +} from '../../Services/ApiCall/ApiUrls'; import TemplateFormModal from './Component/TemplateFormModal'; import { SelectedItem } from './Type/BidType'; -import FontsFamily from '../../Constant/FontsFamily'; import { showSuccessAlert } from '../../Constant/ShowDailog'; - +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; +import { + FileText, + Upload, + CheckCircle2, + ShieldCheck, + Paperclip, + ChevronRight, + PackageCheck, + BadgeIndianRupee, +} from 'lucide-react-native'; const Emd = () => { - const Navigation: any = useNavigation() - const SelectedAuction = useSelector((state: any) => state.auction.auction) - const [selectedItems, setSelectedItems] = useState([]); - const [acceptedPolicy, setAcceptedPolicy] = useState(false); - const [allBoolean, setAllBoolean]: any = useState({ isSubmitEmdLoading: false, isEmdComplete: false, auctionItems: [], technicalDocumentStatus: false, selectTemplate: {}, technicalDocModalVisible: false, emdType: false, fileName: '', file: {} }) - const { mutate } = useEmdCheck() - const { mutate: AuctionItemMutate, isPending } = useAuctionItem() + const Navigation: any = useNavigation(); + const { colors, spacing, shadows, typography } = useTheme(); + const SelectedAuction = useSelector((state: any) => state.auction.auction); - const isSelected = (id: number) => selectedItems.some(i => i.auctionItemId === id); + const [selectedItems, setSelectedItems] = useState([]); + const [acceptedPolicy, setAcceptedPolicy] = useState(false); + const [isInitialLoading, setIsInitialLoading] = useState(true); + const [allBoolean, setAllBoolean]: any = useState({ + isSubmitEmdLoading: false, + isEmdComplete: false, + auctionItems: [], + technicalDocumentStatus: false, + selectTemplate: {}, + technicalDocModalVisible: false, + emdType: false, + fileName: '', + file: {}, + }); - const toggleSelect = (item: any, checked: boolean) => { - if (!checked) { - setSelectedItems(prev => - prev.filter(i => i.auctionItemId !== item.id), - ); - } else { - setSelectedItems(prev => [ - ...prev, - { auctionItemId: item.id, productImageName: '', file: null }, - ]); - } - }; + const { mutate } = useEmdCheck(); + const { mutate: AuctionItemMutate, isPending } = useAuctionItem(); - const updateSelectedItem = (id: number, data: Partial) => { - setSelectedItems(prev => - prev.map(i => (i.auctionItemId === id ? { ...i, ...data } : i)), - ); - }; + const isSelected = (id: number) => selectedItems.some(i => i.auctionItemId === id); - const pickFile = async (id: number) => { - try { - const res = await pick({ type: ['application/pdf'] }); - const file = res[0]; - updateSelectedItem(id, { - file: { - name: file.name ?? 'document.pdf', - uri: file.uri, - type: 'application/pdf', - }, - }); - } catch { - console.log('Pick cancelled'); - } - }; - - const pickFileSingle = async () => { - try { - const res = await pick({ type: ['application/pdf'] }); - const file = res[0]; - setAllBoolean((prev: any) => ({ ...prev, file: { name: file.name ?? 'document.pdf', uri: file.uri, type: 'application/pdf' } })) - } catch { - console.log('Pick cancelled'); - } - }; - /* ---------------- Render Item ---------------- */ - const renderItem = ({ item }: any) => { - const selected = isSelected(item.id); - const selectedData = selectedItems.find( - i => i.auctionItemId === item.id, - ); - - return ( - - - - {item.productName} - EMD ₹ {item.emdAmount} - - - toggleSelect(item, checked)} - /> - - - - Existing Doc:{' '} - - {item.itemDocumentName ?? 'Not Uploaded'} - - - - {selected && ( - - - updateSelectedItem(item.id, { productImageName: text }) - } - style={styles.input} - placeholderTextColor={colors.lightText} - /> - - pickFile(item.id)} - > - - {selectedData?.file ? 'Change PDF' : 'Upload PDF'} - - - - {selectedData?.file && ( - - {selectedData.file.name} - - )} - - )} - - ); - }; - - // Api calls - const getAuctionItem = async () => { - try { - setAllBoolean((prev: any) => ({ ...prev, isLoading: true })) - if (SelectedAuction.auctionPattern === 'Forward') { - const payload = { auctionNumber: SelectedAuction.auctionNumber, status: 'Forward' } - AuctionItemMutate(payload, { - onSuccess: (res) => { - if (res.statusCode == 200) { - setAllBoolean((prev: any) => ({ ...prev, auctionItems: Array.isArray(res?.data) ? res.data : [res.data] })) - } - }, - onError: (err) => { - - } - }) - - } else if (SelectedAuction.auctionPattern === 'Reverse') { - const payload = { auctionNumber: SelectedAuction.auctionNumber, status: 'Reverse' } - AuctionItemMutate(payload, { - onSuccess: (res) => { - if (res.statusCode == 200) { - setAllBoolean((prev: any) => ({ ...prev, auctionItems: Array.isArray(res?.data) ? res.data : [res.data] })) - } - }, - onError: (err) => { - - } - }) - - } - } catch (error) { - console.log('Auction fetch error', error) - } finally { - setAllBoolean((prev: any) => ({ ...prev, isLoading: false })) - } + const toggleSelect = (item: any, checked: boolean) => { + if (!checked) { + setSelectedItems(prev => prev.filter(i => i.auctionItemId !== item.id)); + } else { + setSelectedItems(prev => [ + ...prev, + { auctionItemId: item.id, productImageName: '', file: null }, + ]); } + }; - const getTemplateDocumentation = async () => { - try { - if (SelectedAuction.technicalDocReq) { - const res = await apiCall('get', CHECK_TECHNICAL_DOCUMENTAION, {}, { auctionId: SelectedAuction.auctionId }) + const updateSelectedItem = (id: number, data: Partial) => { + setSelectedItems(prev => + prev.map(i => (i.auctionItemId === id ? { ...i, ...data } : i)), + ); + }; - if (res.data) { - setAllBoolean((prev: any) => ({ ...prev, technicalDocumentStatus: true })) - } else { - const res = await apiCall('get', GET_TECHNICAL_DOCUMENTAION,) - const auctionIdToFind = String(SelectedAuction.auctionId); - const matchedObject = res.data.find((item: any) => - item.auctionIds - ?.split(',') - ?.map((id: any) => id.trim()) - ?.includes(auctionIdToFind) - ); - setAllBoolean((prev: any) => ({ ...prev, selectTemplate: matchedObject, technicalDocModalVisible: true })) - } - } - - - } catch (error) { - console.log('Auction fetch error', error) - } finally { - setAllBoolean((prev: any) => ({ ...prev, isLoading: false })) - } + const pickFile = async (id: number) => { + try { + const res = await pick({ type: ['application/pdf'] }); + const file = res[0]; + updateSelectedItem(id, { + file: { name: file.name ?? 'document.pdf', uri: file.uri, type: 'application/pdf' }, + }); + } catch { + console.log('Pick cancelled'); } - const getAuction = async () => { - const selectedRes = await apiCall('get', GET_AUCTION_DETAILS, {}, { id: SelectedAuction.auctionId }); - const emdType = String(selectedRes.data.emd) - .trim() - .replace(/\s+/g, ' ') - .toLowerCase(); + }; - - if (selectedRes.statusCode == 200 && emdType === 'auction wise') { - return true - } else { - return false - } + const pickFileSingle = async () => { + try { + const res = await pick({ type: ['application/pdf'] }); + const file = res[0]; + setAllBoolean((prev: any) => ({ + ...prev, + file: { name: file.name ?? 'document.pdf', uri: file.uri, type: 'application/pdf' }, + })); + } catch { + console.log('Pick cancelled'); } + }; - const AlreadyEmdCheck = async () => { - try { - mutate({ auctionNumber: SelectedAuction.auctionNumber }, { - onSuccess: (async (res) => { - if (res.statusCode == 200) { - if (res.message == 'No EMD Documents') { - const result = await getAuction() - if (result) { - setAllBoolean((prev: any) => ({ ...prev, emdType: true })) - return - } - setAllBoolean((prev: any) => ({ ...prev, isEmdComplete: false })) - getAuctionItem() - } else { - if (!SelectedAuction.technicalDocReq) { - setAllBoolean((prev: any) => ({ ...prev, isEmdComplete: true, technicalDocumentStatus: true })) - } else { - setAllBoolean((prev: any) => ({ ...prev, isEmdComplete: true })) - getTemplateDocumentation() - } - - } - } - }), - onError: (err) => { - - } - }) - } catch (error) { - console.log('EMD Check Error:', error) - } finally { - setAllBoolean((prev: any) => ({ ...prev, isLoading: false })) - } - } - - const handleSubmit = async () => { - setAllBoolean((prev: any) => ({ ...prev, isSubmitEmdLoading: true })) - if (!acceptedPolicy) { - Alert.alert('Policy Required', 'Please accept terms'); - return; - } - - if (selectedItems.length === 0) { - Alert.alert('Select Item', 'Please select at least one item'); - return; - } - - for (const item of selectedItems) { - if (!item.productImageName || !item.file) { - Alert.alert('Incomplete', 'Upload documents for all selected items'); - return; - } - } - try { - const formData = new FormData() - - formData.append('auctionId', String(SelectedAuction.auctionId)) - - selectedItems.forEach((item: any) => { - formData.append( - 'auctionItemId', - String(item.auctionItemId) - ) - - formData.append( - 'productImageName', - item.productImageName - ) - - formData.append('files', { - uri: item.file.uri, - name: item.file.name, - type: item.file.type || 'application/pdf', - } as any) - }) - - const res = await apiCall('post', AUCTION_ITEM_SUBMIT, formData, {}, 'multipart/form-data') - if (res.statusCode == 200) { - if (SelectedAuction.technicalDocReq) { - getTemplateDocumentation() - } else { - if (SelectedAuction.auctionPattern === 'Forward') { - Navigation.navigate(NavigationString.StartBidding) - } else { - Navigation.navigate(NavigationString.StartReverseBidding) - } - } - } - } catch (error) { - console.log('Submit error', error) - Alert.alert('Error', 'Failed to submit EMD') - } finally { - setAllBoolean((prev: any) => ({ ...prev, isSubmitEmdLoading: true })) - } - }; - - const EmdContinueHandle = async () => { - - if (allBoolean.technicalDocumentStatus) { - if (SelectedAuction.auctionPattern === 'Forward') { - Navigation.navigate(NavigationString.StartBidding) - } else { - Navigation.navigate(NavigationString.StartReverseBidding) - } - } else { - getTemplateDocumentation() - } - } - - - const singleEmdSubmit = async () => { - try { - setAllBoolean((prev: any) => ({ ...prev, isSubmitEmdLoading: true })) - - if (!acceptedPolicy) { - Alert.alert('Policy Required', 'Please accept terms'); - return; - } - const formData = new FormData(); - - formData.append('auctionId', SelectedAuction.auctionId); - formData.append('productImageName', allBoolean.fileName); - formData.append('files', allBoolean.file); - - const response = await apiCall( - 'post', - AUCITON_SINGLE_EMD, - formData, - {}, - 'multipart/form-data' - ); - - if (response?.statusCode === 200) { - showSuccessAlert(response.message || 'EMD submitted successfully'); - if (SelectedAuction.auctionPattern === 'Forward') { - Navigation.navigate(NavigationString.StartBidding) - } else { - Navigation.navigate(NavigationString.StartReverseBidding) - } - } - } catch (error: any) { - if (error?.response) { - console.log('Server error:', error.response.data); - } else if (error?.request) { - console.log('Network error:', error.request); - } else { - console.log('Error message:', error.message); - } - } finally { - setAllBoolean((prev: any) => ({ ...prev, isSubmitEmdLoading: false })) - } - }; - - useEffect(() => { - AlreadyEmdCheck() - }, []) + /* ── Render Item ── */ + const renderItem = ({ item }: any) => { + console.log(item, "item"); + const selected = isSelected(item.id); + const selectedData = selectedItems.find(i => i.auctionItemId === item.id); return ( - - - - {SelectedAuction.technicalDocReq && allBoolean.technicalDocModalVisible && - - } - - {isPending ? ( - - ) : allBoolean.isEmdComplete ? ( - - ) : allBoolean.emdType ? - - Document Name - { setAllBoolean((prev: any) => ({ ...prev, fileName: text })) }} - /> - - { pickFileSingle() }}> - {allBoolean?.file?.name ? allBoolean?.file?.name : 'Choose File'} - - - - setAcceptedPolicy(p => !p)} - text="I agree to Terms & Privacy Policy" - textStyle={styles.policyText} - /> - - - { - allBoolean.isSubmitEmdLoading ? : Submit EMD - } - - - - : <> - item.auctionId} - renderItem={renderItem} - contentContainerStyle={{ paddingBottom: 170 }} - /> - - - setAcceptedPolicy(p => !p)} - text="I agree to Terms & Privacy Policy" - textStyle={styles.policyText} - /> - - - { - allBoolean.isSubmitEmdLoading ? : Submit EMD - } - - - - - } + + {/* Card Header */} + + + + + + + {item.productName} + + + {/* */} + + EMD ₹ {item.emdAmount} + + + + + + + toggleSelect(item, checked)} + /> + + + {/* Existing doc */} + + + + Existing:{' '} + + {item.itemDocumentName ?? 'Not Uploaded'} + + + + + {/* Expanded Form */} + {selected && ( + + + + updateSelectedItem(item.id, { productImageName: text })} + style={[styles.textInput, { borderColor: colors.border, backgroundColor: colors.background, color: colors.text }]} + placeholderTextColor={colors.textSecondary} + /> + + pickFile(item.id)} + activeOpacity={0.85} + > + + + {selectedData?.file ? 'Change PDF' : 'Upload PDF'} + + + + {selectedData?.file && ( + + + + {selectedData.file.name} + + + )} + + )} + ); + }; + + /* ── API calls (unchanged logic) ── */ + const getAuctionItem = async () => { + try { + const payload = { auctionNumber: SelectedAuction.auctionNumber, status: SelectedAuction.auctionPattern }; + AuctionItemMutate(payload, { + onSuccess: res => { + if (res.statusCode === 200) { + setAllBoolean((prev: any) => ({ ...prev, auctionItems: Array.isArray(res?.data) ? res.data : [res.data] })); + } + }, + onError: () => { }, + }); + } catch (error) { + console.log('Auction fetch error', error); + } + }; + + const getTemplateDocumentation = async () => { + try { + if (SelectedAuction.technicalDocReq) { + const res = await apiCall('get', CHECK_TECHNICAL_DOCUMENTAION, {}, { auctionId: SelectedAuction.auctionId }); + if (res.data) { + setAllBoolean((prev: any) => ({ ...prev, technicalDocumentStatus: true })); + } else { + const res2 = await apiCall('get', GET_TECHNICAL_DOCUMENTAION); + const auctionIdToFind = String(SelectedAuction.auctionId); + const matchedObject = res2.data.find((item: any) => + item.auctionIds?.split(',')?.map((id: any) => id.trim())?.includes(auctionIdToFind), + ); + setAllBoolean((prev: any) => ({ ...prev, selectTemplate: matchedObject, technicalDocModalVisible: true })); + } + } + } catch (error) { + console.log('Auction fetch error', error); + } + }; + + const getAuction = async () => { + const selectedRes = await apiCall('get', GET_AUCTION_DETAILS, {}, { id: SelectedAuction.auctionId }); + const emdType = String(selectedRes.data.emd).trim().replace(/\s+/g, ' ').toLowerCase(); + return selectedRes.statusCode === 200 && emdType === 'auction wise'; + }; + + const AlreadyEmdCheck = async () => { + try { + setIsInitialLoading(true); + mutate( + { auctionNumber: SelectedAuction.auctionNumber }, + { + onSuccess: async res => { + try { + if (res.statusCode === 200) { + if (res.message === 'No EMD Documents') { + const result = await getAuction(); + if (result) { + setAllBoolean((prev: any) => ({ ...prev, emdType: true })); + return; + } + setAllBoolean((prev: any) => ({ ...prev, isEmdComplete: false })); + getAuctionItem(); + } else { + if (!SelectedAuction.technicalDocReq) { + setAllBoolean((prev: any) => ({ ...prev, isEmdComplete: true, technicalDocumentStatus: true })); + } else { + setAllBoolean((prev: any) => ({ ...prev, isEmdComplete: true })); + getTemplateDocumentation(); + } + } + } + } finally { + setIsInitialLoading(false); + } + }, + onError: () => { + setIsInitialLoading(false); + }, + }, + ); + } catch (error) { + console.log('EMD Check Error:', error); + setIsInitialLoading(false); + } + }; + + const handleSubmit = async () => { + setAllBoolean((prev: any) => ({ ...prev, isSubmitEmdLoading: true })); + if (!acceptedPolicy) { Alert.alert('Policy Required', 'Please accept terms'); return; } + if (selectedItems.length === 0) { Alert.alert('Select Item', 'Please select at least one item'); return; } + for (const item of selectedItems) { + if (!item.productImageName || !item.file) { + Alert.alert('Incomplete', 'Upload documents for all selected items'); + return; + } + } + try { + const formData = new FormData(); + formData.append('auctionId', String(SelectedAuction.auctionId)); + selectedItems.forEach((item: any) => { + formData.append('auctionItemId', String(item.auctionItemId)); + formData.append('productImageName', item.productImageName); + formData.append('files', { uri: item.file.uri, name: item.file.name, type: item.file.type || 'application/pdf' } as any); + }); + const res = await apiCall('post', AUCTION_ITEM_SUBMIT, formData, {}, 'multipart/form-data'); + if (res.statusCode === 200) { + if (SelectedAuction.technicalDocReq) { + getTemplateDocumentation(); + } else { + Navigation.navigate(SelectedAuction.auctionPattern === 'Forward' ? NavigationString.StartBidding : NavigationString.StartReverseBidding); + } + } + } catch (error) { + Alert.alert('Error', 'Failed to submit EMD'); + } finally { + setAllBoolean((prev: any) => ({ ...prev, isSubmitEmdLoading: false })); + } + }; + + const EmdContinueHandle = async () => { + if (allBoolean.technicalDocumentStatus) { + Navigation.navigate(SelectedAuction.auctionPattern === 'Forward' ? NavigationString.StartBidding : NavigationString.StartReverseBidding); + } else { + getTemplateDocumentation(); + } + }; + + const singleEmdSubmit = async () => { + try { + setAllBoolean((prev: any) => ({ ...prev, isSubmitEmdLoading: true })); + if (!acceptedPolicy) { Alert.alert('Policy Required', 'Please accept terms'); return; } + const formData = new FormData(); + formData.append('auctionId', SelectedAuction.auctionId); + formData.append('productImageName', allBoolean.fileName); + formData.append('files', allBoolean.file); + const response = await apiCall('post', AUCITON_SINGLE_EMD, formData, {}, 'multipart/form-data'); + if (response?.statusCode === 200) { + showSuccessAlert(response.message || 'EMD submitted successfully'); + Navigation.navigate(SelectedAuction.auctionPattern === 'Forward' ? NavigationString.StartBidding : NavigationString.StartReverseBidding); + } + } catch (error: any) { + console.log('Error:', error?.message || error); + } finally { + setAllBoolean((prev: any) => ({ ...prev, isSubmitEmdLoading: false })); + } + }; + + useEffect(() => { AlreadyEmdCheck(); }, []); + + const styles = makeStyles(colors, spacing, shadows, typography); + + /* ── Policy + Submit footer ── */ + const BottomBar = ({ onSubmit }: { onSubmit: () => void }) => ( + + + + setAcceptedPolicy(p => !p)} + /> + + + I agree to{' '} + + Terms & Privacy Policy + + + + + + {allBoolean.isSubmitEmdLoading ? ( + + ) : ( + <> + + + Submit EMD + + + )} + + + ); + + return ( + + + + {SelectedAuction.technicalDocReq && allBoolean.technicalDocModalVisible && ( + + )} + + {(isPending || isInitialLoading) ? ( + + ) : allBoolean.isEmdComplete ? ( + + ) : allBoolean.emdType ? ( + /* ── Single EMD (auction-wise) ── */ + + + {/* Icon banner */} + + + + + Auction-wise EMD + + + Upload a single document for this auction + + + + + {/* Document name */} + + DOCUMENT NAME + + setAllBoolean((prev: any) => ({ ...prev, fileName: text }))} + /> + + {/* File picker */} + + + + {allBoolean?.file?.name ? 'Change PDF' : 'Choose PDF'} + + + + {allBoolean?.file?.name ? ( + + + + {allBoolean.file.name} + + + ) : null} + + + + + ) : ( + /* ── Item-wise EMD ── */ + + String(item.id)} + renderItem={renderItem} + contentContainerStyle={styles.listContent} + showsVerticalScrollIndicator={false} + /> + + + )} + + ); }; export default Emd; -const styles = StyleSheet.create({ - container: { - flex: 1, - // paddingHorizontal: 10, - backgroundColor: colors.white +/* ─── Styles factory ─── */ +const makeStyles = (colors: any, spacing: any, shadows: any, typography?: any) => + StyleSheet.create({ + root: { + flex: 1, + backgroundColor: colors.background, + }, + listContent: { + padding: spacing.m, + paddingBottom: 160, }, - container2: { - paddingHorizontal: 10, - paddingVertical: 10, - backgroundColor: colors.white, - elevation: 2, - borderRadius: 10, - marginTop: 20, - marginHorizontal: 10 - }, - button: { - backgroundColor: colors.primary, - padding: 12, - borderRadius: 6, - alignItems: 'center', - }, - buttonText: { - color: '#fff', - fontWeight: '600', - }, + /* ── Item card ── */ card: { - backgroundColor: '#FFFFFF', - marginHorizontal: 5, - marginTop: 16, - padding: 18, - borderRadius: 8, - elevation: 5, - overflow: 'hidden' + borderRadius: 18, + marginBottom: spacing.m, + padding: spacing.m, + borderWidth: 1, + borderColor: colors.border, + ...shadows.light, }, - - cardSelected: { - backgroundColor: colors.primaryHighlight, - borderWidth: 1.5, - borderColor: colors.primary, - }, - cardHeader: { - width: '100%', - flexDirection: 'row', - justifyContent: 'space-between', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + gap: spacing.s, }, - - title: { - fontSize: 17, - fontWeight: '800', - color: '#0F172A', + cardLeft: { + flexDirection: 'row', + alignItems: 'center', + flex: 1, + gap: spacing.s, }, - - subText: { - fontSize: 13, - color: '#64748B', - marginTop: 6, + itemIconBox: { + width: 40, + height: 40, + borderRadius: 12, + alignItems: 'center', + justifyContent: 'center', }, - - meta: { - marginTop: 10, - fontSize: 13, - color: '#475569', + itemTitle: { + fontFamily: typography?.fonts?.bold, + marginBottom: 3, + } as any, + emdRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 3, }, - - bold: { - fontWeight: '700', - color: '#020617', + emdAmount: { + fontFamily: typography?.fonts?.bold, + } as any, + existingDocRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 5, + marginTop: spacing.s, + paddingHorizontal: spacing.s + 2, + paddingVertical: 6, + borderRadius: 8, }, - - form: { - marginTop: 16, - paddingTop: 16, - borderTopWidth: 1, - borderColor: '#E2E8F0', + expandedForm: { + marginTop: spacing.s, }, - - input: { - borderWidth: 1, - borderColor: '#CBD5E1', - borderRadius: 14, - padding: 12, - backgroundColor: '#FFFFFF', - fontSize: 14, - color: colors.black + separator: { + height: StyleSheet.hairlineWidth, + marginBottom: spacing.m, }, - input2: { - borderWidth: 1, - borderColor: '#CBD5E1', - borderRadius: 14, - padding: 12, - backgroundColor: '#FFFFFF', - fontSize: 14, - color: colors.black, - marginBottom: 20 + textInput: { + borderWidth: 1.2, + borderRadius: 14, + padding: 12, + fontSize: 14, + marginBottom: spacing.s, }, - uploadBtn: { - marginTop: 12, - backgroundColor: colors.primary, - paddingVertical: 13, - borderRadius: 14, - alignItems: 'center', - shadowColor: colors.primaryHighlight, - shadowOffset: { width: 0, height: 6 }, - shadowOpacity: 0.3, - shadowRadius: 12, - elevation: 4, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 7, + paddingVertical: 13, + borderRadius: 14, }, - uploadText: { - color: '#FFFFFF', - fontWeight: '800', - fontSize: 14, + fontFamily: typography?.fonts?.bold, + } as any, + fileNameRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + marginTop: spacing.s, + paddingHorizontal: spacing.s + 4, + paddingVertical: 8, + borderRadius: 10, }, - fileName: { - marginTop: 8, - fontSize: 12, - color: '#475569', + /* ── Single EMD card ── */ + singleEmdCard: { + margin: spacing.m, + backgroundColor: colors.surface, + borderRadius: 20, + padding: spacing.m, + ...shadows.light, }, - - bottom: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - padding: 16, - backgroundColor: '#FFFFFF', - borderTopWidth: 1, - borderColor: '#E2E8F0', - - shadowColor: '#000', - shadowOffset: { width: 0, height: -6 }, - shadowOpacity: 0.08, - shadowRadius: 14, - elevation: 12, + singleEmdBanner: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.m, + borderRadius: 14, + padding: spacing.m, + marginBottom: spacing.m, }, + fieldLabel: { + fontFamily: typography?.fonts?.bold, + letterSpacing: 1, + marginBottom: spacing.xs + 2, + } as any, + /* ── Bottom bar ── */ + bottomBar: { + height: 160, + padding: spacing.m, + paddingBottom: spacing.m + 60, + backgroundColor: colors.surface, + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: colors.border, + ...shadows.large, + }, + policyRow: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.s, + marginBottom: spacing.m, + }, policyText: { - fontSize: 13, - color: '#334155', - }, - + flex: 1, + lineHeight: 20, + fontSize: 13, + fontWeight: '500', + color: colors.text, + } as any, submitBtn: { - marginTop: 14, - backgroundColor: colors.primary, - paddingVertical: 15, - borderRadius: 16, - alignItems: 'center', - shadowColor: colors.primaryHighlight, - shadowOffset: { width: 0, height: 8 }, - shadowOpacity: 0.32, - shadowRadius: 16, - elevation: 6, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 8, + paddingVertical: 15, + borderRadius: 16, }, - - disabled: { - backgroundColor: colors.lightText + disabledBtn: { + opacity: 0.6, }, - submitText: { - color: '#FFFFFF', - fontWeight: '800', - fontSize: 15, - }, -}); + fontSize: 14, + fontWeight: '800', + color: '#fff', + letterSpacing: 0.5, + textTransform: 'uppercase', + } as any, + }); diff --git a/src/Screens/Bidding/StartBidding.tsx b/src/Screens/Bidding/StartBidding.tsx index 50730a7..80fda10 100644 --- a/src/Screens/Bidding/StartBidding.tsx +++ b/src/Screens/Bidding/StartBidding.tsx @@ -1,52 +1,74 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { View, FlatList, StyleSheet, Platform, Alert, BackHandler, Text } from 'react-native'; +import { + View, + FlatList, + StyleSheet, + Alert, + BackHandler, +} from 'react-native'; import LottieView from 'lottie-react-native'; +import Animated, { FadeInDown } from 'react-native-reanimated'; import { InsideHeader } from '../../Component/Index'; -import colors from '../../Constant/Color'; import { useWebSocketService } from '../../socket/Socket'; import Variables from '../../Constant/Variable'; import { useSelector } from 'react-redux'; import { apiCall } from '../../Axios/Axios'; -import { AUCTION_ONE_TIME_BID, FORWARD_AUCTION_ITEM, GET_AUCTION_DETAILS, REVERSE_AUCTION_ITEM, SELECTED_AUCTION_ITEM } from '../../Services/ApiCall/ApiUrls'; +import { + AUCTION_ONE_TIME_BID, + FORWARD_AUCTION_ITEM, + GET_AUCTION_DETAILS, + REVERSE_AUCTION_ITEM, + SELECTED_AUCTION_ITEM, +} from '../../Services/ApiCall/ApiUrls'; import BiddingCard from './Component/BiddingCard'; +import ConfirmBidModal from './Component/ConfirmBidModal'; +import GoBackModal from './Component/GoBackModal'; import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native'; import NavigationString from '../../Constant/NavigationString'; import CountdownTimer from '../../Component/Counter/CountdownTimer'; -import FontsFamily from '../../Constant/FontsFamily'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; +import { Gavel, Building2, Clock4 } from 'lucide-react-native'; const StartBidding = () => { + const { colors, spacing, shadows } = useTheme(); const SelectedAuction = useSelector((state: any) => state.auction.auction); - const loginUser = useSelector((state: any) => state.token.token) - const Navigation: any = useNavigation() + const loginUser = useSelector((state: any) => state.token.token); + const Navigation: any = useNavigation(); + const [items, setItems] = useState([]); const [bidValues, setBidValues] = useState>({}); - const [allBoolean, setAllBoolean] = useState({ showSuccess: false, showDialog: false, bidPayload: {}, oneTimeBid: false, rebid: false, reload: false }) + const [allBoolean, setAllBoolean] = useState({ + showSuccess: false, + showDialog: false, + showBackDialog: false, + bidPayload: {} as any, + oneTimeBid: false, + rebid: false, + reload: false, + }); + const { subscribe, createRoom, placeBid } = useWebSocketService( Variables.webSocketUrl, () => console.log('Connected!'), - (error) => console.log('WebSocket Error:', error), + (error: any) => console.log('WebSocket Error:', error), loginUser.token, ); const getAuction = async () => { const selectedRes = await apiCall('get', GET_AUCTION_DETAILS, {}, { id: SelectedAuction.auctionId }); - if (selectedRes.statusCode == 200 && selectedRes.data.contractorId.length) { - setAllBoolean((prev: any) => ({ ...prev, oneTimeBid: true })) + if (selectedRes.statusCode === 200 && selectedRes.data.contractorId.length) { + setAllBoolean(prev => ({ ...prev, oneTimeBid: true })); } - } + }; + const getOneTimeBidValue = async () => { try { const selectedRes = await apiCall('get', AUCTION_ONE_TIME_BID, {}, { auctionId: SelectedAuction.auctionId }); - if (selectedRes.statusCode == 200) { - return selectedRes.data - } else { - return [] - } - } catch (error) { + return selectedRes.statusCode === 200 ? selectedRes.data : []; + } catch { return []; } + }; - } - - } const getAllAcutionItems = async () => { try { const payload = { auctionNumber: SelectedAuction.auctionNumber }; @@ -58,87 +80,40 @@ const StartBidding = () => { } const selectedRes = await apiCall('get', SELECTED_AUCTION_ITEM, {}, payload); + const uniqueArray: number[] = Array.from(new Set(selectedRes?.data || [])); + const filteredItems = auctionItem.filter(item => uniqueArray.includes(item.id)); - const uniqueArray: number[] = Array.from( - new Set(selectedRes?.data || []) - ); - - const filteredItems = auctionItem.filter(item => - uniqueArray.includes(item.id) - ); - - if (SelectedAuction.auctionCategories == 'Live') { + if (SelectedAuction.auctionCategories === 'Live') { const result = await createRoom(SelectedAuction.auctionNumber); - - subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, data => { + subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, (data: any) => { console.log('🔥 BID UPDATE:', data); }); const bidAmountMap2 = new Map(); - (result || []).forEach((r: any) => { - bidAmountMap2.set(r.auctionItemId, { - bidAmount: r.bidAmount, - contractorId: r.contractorId, - }); + bidAmountMap2.set(r.auctionItemId, { bidAmount: r.bidAmount, contractorId: r.contractorId }); }); - // Check One Time bid enable butoon - if (SelectedAuction.auctionCategories != 'Live') { - const myBid = result.find( - (bid: any) => bid.contractorId === loginUser.contractorId - ); - - if (myBid) { - setAllBoolean((prev: any) => ({ ...prev, oneTimeBid: true })) - } - } - - const bidAmountMap = new Map(); - - (result || []).forEach((r: any) => { - bidAmountMap.set(r.auctionItemId, r.bidAmount); - }); - const updatedFilteredItems = filteredItems.map(item => { const bidData = bidAmountMap2.get(item.id); return { ...item, bidAmount: bidData?.bidAmount ?? item.auctionStartValueFigure, bidAvilable: bidData?.bidAmount ?? 0, - me: bidData?.contractorId == loginUser.contractorId ? true : false + me: bidData?.contractorId === loginUser.contractorId, }; }); - const initialBids: Record = {}; - updatedFilteredItems.forEach(item => { - initialBids[item.id] = item.bidAmount; - }); + updatedFilteredItems.forEach(item => { initialBids[item.id] = item.bidAmount; }); setBidValues(initialBids); setItems(updatedFilteredItems); } else { - let result = [] - if (SelectedAuction.rebidAllowed) { - setAllBoolean((prev: any) => ({ ...prev, rebid: true })) - } - - if (SelectedAuction.auctionCategories != 'Live') { - result = await getOneTimeBidValue() - } + if (SelectedAuction.rebidAllowed) setAllBoolean(prev => ({ ...prev, rebid: true })); + let result: any[] = []; + if (SelectedAuction.auctionCategories !== 'Live') result = await getOneTimeBidValue(); const bidMap = new Map(); - - result.forEach((bid: any) => { - bidMap.set(bid.auctionItemId, bid); - }); + result.forEach((bid: any) => bidMap.set(bid.auctionItemId, bid)); const mergedData = auctionItem.map((item: any) => { const bid = bidMap.get(item.id); - - if (!bid) { - return { - ...item, - bidAmount: item.auctionStartValueFigure, - contractorId: item.contractorId, - }; - } - + if (!bid) return { ...item, bidAmount: item.auctionStartValueFigure, contractorId: item.contractorId }; return { ...item, bidAmount: bid.bidAmount, @@ -150,56 +125,36 @@ const StartBidding = () => { }; }); const initialBids: Record = {}; - mergedData.forEach(item => { - initialBids[item.id] = item.bidAmount; - }); - setBidValues(initialBids); - setItems(mergedData) + mergedData.forEach(item => { initialBids[item.id] = item.bidAmount; }); setBidValues(initialBids); + setItems(mergedData); } } catch (error) { console.error('Error fetching auction items:', error); } }; - subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, async (message) => { - - const result = message[message.length - 1] - - const updatedBidItem = (prev: any[]) => + subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, async (message: any) => { + const result = message[message.length - 1]; + setItems((prev: any[]) => prev.map(item => item.id === result.auctionItemId - ? { ...item, bidAmount: result.bidAmount, me: result.contractorId == loginUser.contractorId ? true : false } - : item - ); - - setItems(updatedBidItem); - - setBidValues(prev => ({ - ...prev, - [result.auctionItemId]: result.bidAmount, - })); - try { - - } catch (err) { - console.log('JSON parse error:', err); - } + ? { ...item, bidAmount: result.bidAmount, me: result.contractorId === loginUser.contractorId } + : item, + ), + ); + setBidValues(prev => ({ ...prev, [result.auctionItemId]: result.bidAmount })); }); - useEffect(() => { - if (SelectedAuction.auctionCategories == 'One-time') { - getAuction() - } + if (SelectedAuction.auctionCategories === 'One-time') getAuction(); getAllAcutionItems(); - subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, (data) => { }); + subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, () => {}); }, [allBoolean.reload]); useEffect(() => { const initial: Record = {}; - items.forEach((item: any) => { - initial[item.id] = item.bidAmount + item.bidVariationValue; - }); + items.forEach((item: any) => { initial[item.id] = item.bidAmount + item.bidVariationValue; }); setBidValues(initial); }, []); @@ -216,51 +171,14 @@ const StartBidding = () => { fullName: loginUser.fullName, roomId: SelectedAuction.auctionNumber, }; - setAllBoolean((prev: any) => ({ ...prev, bidPayload: payload, })); - Alert.alert( - 'Confirm Bid', - `Are you sure you want to place a bid of ₹${payload.bidAmount}?`, - [ - { - text: 'Cancel', - style: 'cancel', - }, - { - text: 'Submit', - onPress: () => { - - placeBidSubmit(payload); - }, - }, - ], - { cancelable: true } - ); + setAllBoolean(prev => ({ ...prev, bidPayload: payload, showDialog: true })); }; const placeBidSubmit = async (data: any) => { + setAllBoolean(prev => ({ ...prev, showDialog: false })); try { const result: any = await placeBid(data); - - if (result?.status && result.data) { - const updatedBidItem = (prev: any[]) => - prev.map(item => - item.id === result.data.auctionItemId - ? { ...item, bidAmount: result.data.bidAmount, me: result.data?.contractorId == loginUser.contractorId ? true : false } - : item - ); - - // setItems(updatedBidItem); - - - // setBidValues((prev: any) => ({ - // ...prev, - // [result.data.auctionItemId]: { - // ...prev[result.data.auctionItemId], - // bidAmount: result.data.bidAmount, - // }, - // })); - getAllAcutionItems() - } + if (result?.status && result.data) getAllAcutionItems(); } catch (error) { console.error('Error placing bid:', error); } @@ -269,55 +187,75 @@ const StartBidding = () => { useFocusEffect( useCallback(() => { const onBackPress = () => { - Alert.alert( - 'Confirm', - 'Do you want to go back to Dashboard?', - [ - { text: 'Cancel', style: 'cancel' }, - { - text: 'Yes', - onPress: () => { - Navigation.dispatch( - CommonActions.reset({ - index: 0, - routes: [{ name: NavigationString.Home }], - }) - ); - }, - }, - ], - { cancelable: true } - ); - - return true; // block default back action + setAllBoolean(prev => ({ ...prev, showBackDialog: true })); + return true; }; - - const subscription = BackHandler.addEventListener( - 'hardwareBackPress', - onBackPress - ); - - return () => subscription.remove(); - }, [Navigation]) + const sub = BackHandler.addEventListener('hardwareBackPress', onBackPress); + return () => sub.remove(); + }, [Navigation]), ); - return ( - - - - - {SelectedAuction.deptName} - + const styles = makeStyles(colors, spacing, shadows); - + const biddingEndDate = + SelectedAuction?.auctionStatus === 'Amended' + ? SelectedAuction?.amendedkeyDates?.auctionBidding?.endDateTime + : SelectedAuction?.keyDates?.auctionBidding?.endDateTime; + + return ( + + + + {/* ── Top Info Band ── */} + + {/* Department row */} + + + + + + {SelectedAuction.deptName ?? 'Auction Department'} + + + + {/* Timer card */} + + + + + BIDDING ENDS IN + + - + {/* Auction chip */} + + + + + {SelectedAuction.auctionNumber} + + + + + + {SelectedAuction.auctionCategories ?? 'Auction'} + + + + + + {/* ── Items List ── */} item.id.toString()} @@ -327,15 +265,17 @@ const StartBidding = () => { bidValue={bidValues[item.id] || 0} onIncrease={() => handleUpdate(item.id, item.bidVariationValue)} onDecrease={() => handleUpdate(item.id, -item.bidVariationValue)} - onPlaceBid={() => { submitBid(item) }} + onPlaceBid={() => submitBid(item)} SelectedAuction={SelectedAuction} allBoolean={allBoolean} setAllBoolean={setAllBoolean} /> )} - contentContainerStyle={{ padding: 16, paddingBottom: 300 }} + contentContainerStyle={styles.listContent} + showsVerticalScrollIndicator={false} /> + {/* ── Success overlay ── */} {allBoolean.showSuccess && ( { /> )} + + setAllBoolean(prev => ({ ...prev, showDialog: false }))} + onConfirm={() => placeBidSubmit(allBoolean.bidPayload)} + bidAmount={allBoolean.bidPayload?.bidAmount} + /> + + setAllBoolean(prev => ({ ...prev, showBackDialog: false }))} + onConfirm={() => { + setAllBoolean(prev => ({ ...prev, showBackDialog: false })); + Navigation.dispatch( + CommonActions.reset({ + index: 0, + routes: [{ name: NavigationString.Home }], + }) + ); + }} + /> ); }; export default StartBidding; -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: colors.white }, - container2: { - paddingHorizontal: 16, - paddingTop: 12, - paddingBottom: 8, - }, - title: { - fontSize: 18, - fontFamily: FontsFamily.poppinsSemiBold, - color: colors.black, - marginBottom: 6, - // textAlign:'center' - }, - timerWrapper: { - alignItems: 'center', - }, - card: { - backgroundColor: '#FFF', - borderRadius: 24, - padding: 20, - marginBottom: 16, - borderWidth: 1, - borderColor: '#E2E8F0', - overflow: 'hidden', - ...Platform.select({ ios: { shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 10 }, android: { elevation: 2 } }), - }, - leadingCard: { borderColor: '#22C55E', borderWidth: 2, backgroundColor: '#F0FDF4' }, - cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start' }, - headerRight: { flexDirection: 'row', alignItems: 'center', gap: 8 }, - productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' }, - itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 }, - badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 }, - liveBadge: { backgroundColor: '#FEE2E2' }, - meBadge: { backgroundColor: '#BBF7D0' }, - badgeText: { fontSize: 10, fontWeight: '900' }, - liveText: { color: '#EF4444' }, - meText: { color: '#15803D' }, - arrow: { fontSize: 10, color: '#94A3B8' }, - priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 }, - label: { fontSize: 11, color: '#94A3B8', textTransform: 'uppercase', fontWeight: '600' }, - mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' }, - incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' }, - collapsibleContainer: { overflow: 'hidden' }, - divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 }, - bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 }, - stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 }, - stepBtn: { width: 45, height: 45, backgroundColor: colors.primary, borderRadius: 12, justifyContent: 'center', alignItems: 'center' }, - disabledBtn: { backgroundColor: '#CBD5E1' }, - stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' }, - inputBox: { flex: 1, height: 45, backgroundColor: '#F1F5F9', borderRadius: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }, - currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 }, - bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' }, - submitBtn: { height: 50, backgroundColor: colors.primary, borderRadius: 15, justifyContent: 'center', alignItems: 'center' }, - submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' }, - overlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(255,255,255,0.7)', justifyContent: 'center', alignItems: 'center', zIndex: 99 }, -}); \ No newline at end of file +/* ─── Styles factory ─── */ +const makeStyles = (colors: any, spacing: any, shadows: any) => + StyleSheet.create({ + root: { + flex: 1, + backgroundColor: colors.background, + }, + + /* Info band */ + infoBand: { + backgroundColor: colors.surface, + paddingHorizontal: spacing.m, + paddingVertical: spacing.m, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, + gap: spacing.s, + ...shadows.light, + }, + deptRow: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.s, + }, + deptIcon: { + width: 30, + height: 30, + borderRadius: 9, + alignItems: 'center', + justifyContent: 'center', + }, + deptName: { + flex: 1, + } as any, + + /* Timer */ + timerCard: { + borderRadius: 16, + borderWidth: 1.2, + paddingVertical: spacing.m, + paddingHorizontal: spacing.m, + alignItems: 'center', + gap: 4, + }, + timerLabelRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + timerLabel: { + letterSpacing: 1.2, + } as any, + + /* Chips */ + chipRow: { + flexDirection: 'row', + gap: spacing.s, + }, + chip: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + paddingHorizontal: spacing.s + 4, + paddingVertical: 5, + borderRadius: 20, + }, + chipText: { + letterSpacing: 0.3, + } as any, + liveDot: { + width: 6, + height: 6, + borderRadius: 3, + }, + + listContent: { + padding: spacing.m, + paddingBottom: 120, + }, + + overlay: { + ...StyleSheet.absoluteFillObject, + backgroundColor: colors.overlay, + justifyContent: 'center', + alignItems: 'center', + zIndex: 99, + }, + }); diff --git a/src/Screens/Bidding/StartReverseBidding.tsx b/src/Screens/Bidding/StartReverseBidding.tsx index 0c6023e..e14bdc6 100644 --- a/src/Screens/Bidding/StartReverseBidding.tsx +++ b/src/Screens/Bidding/StartReverseBidding.tsx @@ -11,16 +11,21 @@ import { AUCTION_ONE_TIME_BID, GET_AUCTION_DETAILS, REVERSE_AUCTION_ITEM, SELECT import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native'; import NavigationString from '../../Constant/NavigationString'; import CountdownTimer from '../../Component/Counter/CountdownTimer'; -import FontsFamily from '../../Constant/FontsFamily'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; import ReverseBiddingCard from './Component/ReverseBiddingCard'; +import ConfirmBidModal from './Component/ConfirmBidModal'; +import GoBackModal from './Component/GoBackModal'; const StartReverseBidding = () => { + const { colors } = useTheme(); + const styles = React.useMemo(() => makeStyles(colors), [colors]); const SelectedAuction = useSelector((state: any) => state.auction.auction); const loginUser = useSelector((state: any) => state.token.token) const Navigation: any = useNavigation() const [items, setItems] = useState([]); const [bidValues, setBidValues] = useState>({}); - const [allBoolean, setAllBoolean] = useState({ showSuccess: false, showDialog: false, bidPayload: {}, oneTimeBid: false, rebid: false, reload: false }) + const [allBoolean, setAllBoolean] = useState({ showSuccess: false, showDialog: false, showBackDialog: false, bidPayload: {} as any, oneTimeBid: false, rebid: false, reload: false }) const { subscribe, createRoom, placeBid } = useWebSocketService( Variables.webSocketUrl, () => console.log('Connected!'), @@ -216,27 +221,11 @@ const StartReverseBidding = () => { fullName: loginUser.fullName, roomId: SelectedAuction.auctionNumber, }; - setAllBoolean((prev: any) => ({ ...prev, bidPayload: payload, })); - Alert.alert( - 'Confirm Bid', - `Are you sure you want to place a bid of ₹${payload.bidAmount}?`, - [ - { - text: 'Cancel', - style: 'cancel', - }, - { - text: 'Submit', - onPress: () => { - placeBidSubmit(payload); - }, - }, - ], - { cancelable: true } - ); + setAllBoolean((prev: any) => ({ ...prev, bidPayload: payload, showDialog: true })); }; const placeBidSubmit = async (data: any) => { + setAllBoolean((prev: any) => ({ ...prev, showDialog: false })); try { const result: any = await placeBid(data); @@ -258,26 +247,7 @@ const StartReverseBidding = () => { useFocusEffect( useCallback(() => { const onBackPress = () => { - Alert.alert( - 'Confirm', - 'Do you want to go back to Dashboard?', - [ - { text: 'Cancel', style: 'cancel' }, - { - text: 'Yes', - onPress: () => { - Navigation.dispatch( - CommonActions.reset({ - index: 0, - routes: [{ name: NavigationString.Home }], - }) - ); - }, - }, - ], - { cancelable: true } - ); - + setAllBoolean((prev: any) => ({ ...prev, showBackDialog: true })); return true; // block default back action }; @@ -294,9 +264,9 @@ const StartReverseBidding = () => { - + {SelectedAuction.deptName} - + { /> )} + + setAllBoolean(prev => ({ ...prev, showDialog: false }))} + onConfirm={() => placeBidSubmit(allBoolean.bidPayload)} + bidAmount={allBoolean.bidPayload?.bidAmount} + /> + + setAllBoolean(prev => ({ ...prev, showBackDialog: false }))} + onConfirm={() => { + setAllBoolean(prev => ({ ...prev, showBackDialog: false })); + Navigation.dispatch( + CommonActions.reset({ + index: 0, + routes: [{ name: NavigationString.Home }], + }) + ); + }} + /> ); }; export default StartReverseBidding; -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: colors.white }, +const makeStyles = (colors: any) => StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.background }, container2: { paddingHorizontal: 16, paddingTop: 12, paddingBottom: 8, }, title: { - fontSize: 18, - fontFamily: FontsFamily.poppinsSemiBold, - color: colors.black, marginBottom: 6, - // textAlign:'center' - }, + } as any, timerWrapper: { alignItems: 'center', }, - card: { - backgroundColor: '#FFF', - borderRadius: 24, - padding: 20, - marginBottom: 16, - borderWidth: 1, - borderColor: '#E2E8F0', - overflow: 'hidden', - ...Platform.select({ ios: { shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 10 }, android: { elevation: 2 } }), - }, - leadingCard: { borderColor: '#22C55E', borderWidth: 2, backgroundColor: '#F0FDF4' }, - cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start' }, - headerRight: { flexDirection: 'row', alignItems: 'center', gap: 8 }, - productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' }, - itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 }, - badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 }, - liveBadge: { backgroundColor: '#FEE2E2' }, - meBadge: { backgroundColor: '#BBF7D0' }, - badgeText: { fontSize: 10, fontWeight: '900' }, - liveText: { color: '#EF4444' }, - meText: { color: '#15803D' }, - arrow: { fontSize: 10, color: '#94A3B8' }, - priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 }, - label: { fontSize: 11, color: '#94A3B8', textTransform: 'uppercase', fontWeight: '600' }, - mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' }, - incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' }, - collapsibleContainer: { overflow: 'hidden' }, - divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 }, - bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 }, - stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 }, - stepBtn: { width: 45, height: 45, backgroundColor: colors.primary, borderRadius: 12, justifyContent: 'center', alignItems: 'center' }, - disabledBtn: { backgroundColor: '#CBD5E1' }, - stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' }, - inputBox: { flex: 1, height: 45, backgroundColor: '#F1F5F9', borderRadius: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }, - currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 }, - bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' }, - submitBtn: { height: 50, backgroundColor: colors.primary, borderRadius: 15, justifyContent: 'center', alignItems: 'center' }, - submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' }, - overlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(255,255,255,0.7)', justifyContent: 'center', alignItems: 'center', zIndex: 99 }, + overlay: { ...StyleSheet.absoluteFillObject, backgroundColor: colors.overlay, justifyContent: 'center', alignItems: 'center', zIndex: 99 }, }); \ No newline at end of file diff --git a/src/Screens/Home/Home.tsx b/src/Screens/Home/Home.tsx index 29d9965..d1dc58f 100644 --- a/src/Screens/Home/Home.tsx +++ b/src/Screens/Home/Home.tsx @@ -1,308 +1,191 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { View, Text, StyleSheet, Dimensions, FlatList, ActivityIndicator, Alert } from 'react-native'; -import Animated, { useSharedValue, useAnimatedStyle, withDelay, withTiming } from 'react-native-reanimated'; -import { Layers, Clock, ShoppingCart, X, FileText } from 'lucide-react-native'; -import { Header } from '../../Component/Index'; -import colors from '../../Constant/Color'; +import { View, StyleSheet, Dimensions, FlatList, ActivityIndicator, Alert, ScrollView, ImageBackground, TouchableOpacity, Platform } from 'react-native'; +import Animated, { FadeInDown, FadeInUp } from 'react-native-reanimated'; +import { Layers, Clock, ShoppingCart, X, FileText, ArrowUpRight, Menu, Radio } from 'lucide-react-native'; +import { useTheme } from '../../Hooks/useTheme'; import { useDashboard, useESetuAdharVarification, useTenderDashboard } from '../../Services/ApiCall/Hooks'; import { useSelector } from 'react-redux'; -import ConfirmDialog from '../../Constant/Dialog/ConfirmDialog'; -import { generateTransactionId } from '../../Helper/uniqueId'; +import { getToken } from '../../Redux/Slices/Token'; import { useFocusEffect, useNavigation } from '@react-navigation/native'; import NavigationString from '../../Constant/NavigationString'; +import ImagePath from '../../Constant/ImagePath'; +import AppText from '../../components/AppText'; +import SidebarModal from '../../components/SidebarModal'; + +// Header is now integrated or minimal +// import { Header } from '../../Component/Index'; const { width } = Dimensions.get('window'); -const CARD_WIDTH = (width - 48) / 2; -const StatCard = ({ item, index, isLoading }: any) => { - const translateY = useSharedValue(30); - const opacity = useSharedValue(0); - useEffect(() => { - translateY.value = withDelay(index * 80, withTiming(0, { duration: 400 })); - opacity.value = withDelay(index * 80, withTiming(1, { duration: 400 })); - }, []); - - const animatedStyle = useAnimatedStyle(() => ({ - transform: [{ translateY: translateY.value }], - opacity: opacity.value, - })); - - const Icon = item.icon; - - return ( - - - - {item.title} - - - - - - {item.value} - - - - - {isLoading ? ( - - ) : ( - {item.value} - )} - - {item.desc} - - ); -}; const Home = () => { - const { data, isLoading } = useDashboard(); - const { data: tenderData, isLoading: tenderLoading } = useTenderDashboard(); + const { colors, spacing, typography, shadows } = useTheme(); + + const [stats, setStats] = useState({}); + const [isMenuVisible, setIsMenuVisible] = useState(false); + + const navigation = useNavigation(); + const userData = useSelector(getToken); + const userType = userData?.userType; // 'tender' | 'auction' + + // Hooks for data + const { data: dashboardData, refetch: refetchDashboard, isPending: isLoadingDashboard } = useDashboard(); + const { data: tenderData, refetch: refetchTender, isPending: isLoadingTender } = useTenderDashboard(); + const { mutate: verifyAadhaar } = useESetuAdharVarification(); + + useFocusEffect( + useCallback(() => { + if (userType === 'auction') refetchDashboard(); + else refetchTender(); + }, [userType]) + ); - const Navigation: any = useNavigation(); - const user = useSelector((state: any) => state.token?.token); - const { mutateAsync: eSetuAdharVarification, isPending } = useESetuAdharVarification(); - const dashboard = data?.data || {}; - const [showDialog, setShowDialog] = useState(false); - const [isNavigating, setIsNavigating] = useState(false); - const [DASHBOARD_CARDS,setDASHBOARD_CARDS]=useState([]) -useFocusEffect( - useCallback(() => { - setIsNavigating(false); - - return () => { - // cleanup if needed - }; - }, []) -); useEffect(() => { - if (user?.esetuVerified === 'Not Verified') { - setShowDialog(true); + if (userType === 'auction' && dashboardData) { + console.log(dashboardData.data, "lkkllklklklklklklklklk") + setStats(dashboardData.data || {}); + } else if (userType === 'tender' && tenderData) { + console.log(tenderData.data, "bonds") + setStats(tenderData.data || {}); } - }, []); - - const handleProceedVerification = async () => { - try { - setIsNavigating(true); - - const payload = { - contId: user.id, - transactionId: generateTransactionId(), - udf1: "23.77", - udf2: "77.34", - udf3: "string", - udf4: "string", - }; + }, [dashboardData, tenderData, userType]); - const res = await eSetuAdharVarification(payload); + const renderGlassCard = (title: string, value: string | number, icon: React.ReactNode, index: number) => ( + + + + {icon} + + + + {value || '0'} + {title} + + ); - setShowDialog(false); - - Navigation.navigate(NavigationString.KycWebViewScreen, { res }); - - } catch (err) { - Alert.alert('Error', 'Failed to initiate verification. Please try again.'); - setIsNavigating(false); - } - }; - - - - - -useEffect(()=>{ -if (user?.userType === "auction") { - setDASHBOARD_CARDS([ - { - title: 'Total Auctions', - value: dashboard?.myAuctionCount ?? 0, - desc: 'Auctions purchased by you', - icon: Layers, - color: '#2563EB', - }, - { - title: 'Live Auctions', - value: dashboard?.liveAuctionCount ?? 0, - desc: 'Currently live', - icon: Clock, - color: '#0284C7', - }, - { - title: 'Participated Auctions', - value: dashboard?.currentBiddingCount ?? 0, - desc: 'Participated in auction', - icon: ShoppingCart, - color: '#7C3AED', - }, - { - title: 'Cancelled Auctions', - value: dashboard?.cancelledAuctionCount ?? 0, - desc: 'Cancelled by department', - icon: X, - color: '#DC2626', - }, - ]); -} else { - setDASHBOARD_CARDS([ - { - title: 'Total Tenders', - value: tenderData?.data?.[0]?.totalTender ?? 0, - desc: 'Tenders assigned', - icon: FileText, - color: '#16A34A', - }, - { - title: 'Live Tenders', - value: tenderData?.data?.[0]?.liveTender ?? 0, - desc: 'Currently live', - icon: Clock, - color: '#059669', - }, - { - title: 'Participated Tenders', - value: tenderData?.data?.[0]?.totalParticipate ?? 0, - desc: 'Participated in tender', - icon: ShoppingCart, - color: '#0EA5E9', - }, - { - title: 'Cancelled Tenders', - value: tenderData?.data?.[0]?.cancelledTender ?? 0, - desc: 'Cancelled by authority', - icon: X, - color: '#EF4444', - }, - ]); -} -},[tenderData,dashboard]) + const StatGrid = () => ( + + {userType === 'auction' ? ( + <> + {renderGlassCard('Live Auctions', stats?.liveAuctionCount, , 0)} + {renderGlassCard('My Purchased Auctions', stats?.myAuctionCount, , 1)} + {renderGlassCard('Cancelled Auctions', stats?.cancelledAuctionCount, , 2)} + {renderGlassCard('Participated Auctions', stats?.currentBiddingCount, , 3)} + + ) : ( + <> + {renderGlassCard('Total Purchased', stats[0]?.totalPurchased, , 0)} + {renderGlassCard('Participated', stats[0]?.totalParticipate, , 1)} + {renderGlassCard('Total Tenders', stats[0]?.totalTender, , 2)} + + )} + + ); return ( - -
+ - !isPending && !isNavigating && setShowDialog(false)} - loading={isPending || isNavigating} - /> + - {isNavigating && ( - - - Loading verification... + {/* Large Typography Header */} + + + Hello, {userData?.firstName || 'User'} + + Find Your{'\n'}Perfect {userType === 'auction' ? 'Deal' : 'Tender'} + + + setIsMenuVisible(true)}> + + + + + setIsMenuVisible(false)} /> + + {/* Floating/Glass Stats */} + + Overview + {isLoadingDashboard || isLoadingTender ? ( + + ) : ( + + )} - )} - index.toString()} - numColumns={2} - columnWrapperStyle={styles.row} - contentContainerStyle={styles.list} - renderItem={({ item, index }) => { - return ( - - ) - }} - showsVerticalScrollIndicator={false} - /> - + {/* Placeholder for Recent Activity list with same glass style */} + + Recent Activity + + No recent activity + + + + + ); }; export default Home; const styles = StyleSheet.create({ - screen: { + container: { flex: 1, - backgroundColor: colors.white, }, - list: { - padding: 16, - paddingBottom: 24, + scrollContent: { + paddingTop: 60, + paddingHorizontal: 20, + paddingBottom: 100, // Space for floating tab bar }, - row: { + headerContainer: { + marginBottom: 40, + flexDirection: 'row', justifyContent: 'space-between', - marginBottom: 16, + alignItems: 'flex-start' }, - card: { - width: CARD_WIDTH, - backgroundColor: '#FFFFFF', - borderRadius: 16, + sectionContainer: { + marginBottom: 32, + }, + gridContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + }, + glassCard: { + width: (width - 48) / 2, // 2 column grid padding: 16, + borderRadius: 24, borderWidth: 1, - borderColor: '#E5E7EB', - shadowColor: '#000', - shadowOpacity: 0.05, - shadowRadius: 8, - elevation: 3, + marginBottom: 16, + // Glassmorphism creates a subtle depth without heavy shadows }, - header: { + cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', + marginBottom: 12, }, - title: { - fontSize: 13, - fontWeight: '600', - color: '#374151', - flex: 1, - paddingRight: 8, - }, - badge: { - flexDirection: 'row', + iconContainer: { + width: 40, + height: 40, + borderRadius: 12, alignItems: 'center', - gap: 6, - borderWidth: 1, - borderRadius: 20, - paddingHorizontal: 8, - paddingVertical: 4, - }, - badgeText: { - fontSize: 12, - fontWeight: '700', - }, - value: { - fontSize: 28, - fontWeight: '800', - color: '#111827', - marginTop: 5, - }, - desc: { - fontSize: 12, - color: '#6B7280', - marginTop: 6, - }, - navigationOverlay: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: 'rgba(255, 255, 255, 0.9)', justifyContent: 'center', - alignItems: 'center', - zIndex: 999, }, - loadingText: { - marginTop: 12, - fontSize: 16, + cardValue: { + fontSize: 28, fontWeight: '600', - color: '#374151', + marginBottom: 4, + }, + cardLabel: { + fontSize: 14, + fontWeight: '500', }, }); diff --git a/src/Screens/Home/KycWebViewScreen.tsx b/src/Screens/Home/KycWebViewScreen.tsx index 40face6..41b75c8 100644 --- a/src/Screens/Home/KycWebViewScreen.tsx +++ b/src/Screens/Home/KycWebViewScreen.tsx @@ -32,13 +32,17 @@ export const KycWebViewScreen = ({ route, navigation }: any) => { }; }, []); - const navigateToLogin = () => { - navigation.dispatch( - CommonActions.reset({ - index: 0, - routes: [{ name: 'Login' }], // Adjust to your login screen route name - }) - ); + const handleNavigationBack = () => { + if (data.returnRoute) { + navigation.replace(data.returnRoute, data.returnParams); + } else { + navigation.dispatch( + CommonActions.reset({ + index: 0, + routes: [{ name: 'Login' }], + }) + ); + } }; const startSuccessCountdown = () => { @@ -49,7 +53,7 @@ export const KycWebViewScreen = ({ route, navigation }: any) => { const countdownInterval = setInterval(() => { remainingSeconds -= 1; setCountdown(remainingSeconds); - + if (remainingSeconds <= 0) { clearInterval(countdownInterval); } @@ -57,7 +61,7 @@ export const KycWebViewScreen = ({ route, navigation }: any) => { successTimeoutRef.current = setTimeout(() => { clearInterval(countdownInterval); - navigateToLogin(); + handleNavigationBack(); }, SUCCESS_DELAY_MS); }; @@ -77,7 +81,7 @@ export const KycWebViewScreen = ({ route, navigation }: any) => { const handleNavigationStateChange = (navState: any) => { if ( - navState.title && + navState.title && navState.title.includes('E-KYC SETU - Verification Success') && !hasCompletedRef.current ) { @@ -126,7 +130,17 @@ export const KycWebViewScreen = ({ route, navigation }: any) => { onLoadStart={handleLoadStart} onLoadEnd={handleLoadEnd} onError={handleError} - onNavigationStateChange={handleNavigationStateChange} + + onNavigationStateChange={(navState) => { + console.log("Current URL:", navState.url); + handleNavigationStateChange(navState); + }} + + onShouldStartLoadWithRequest={(request) => { + console.log("Request URL:", request.url); + return true; + }} + renderLoading={() => } /> )} @@ -144,9 +158,9 @@ export const KycWebViewScreen = ({ route, navigation }: any) => { Redirecting in {countdown} seconds... - diff --git a/src/Screens/Login/ForgotPassword.tsx b/src/Screens/Login/ForgotPassword.tsx index 8c2adaa..d87d4c0 100644 --- a/src/Screens/Login/ForgotPassword.tsx +++ b/src/Screens/Login/ForgotPassword.tsx @@ -17,6 +17,7 @@ import { useNavigation } from '@react-navigation/native'; import colors from '../../Constant/Color'; import { showToast } from '../../Constant/ShowDailog'; import NavigationString from '../../Constant/NavigationString'; +import { BASE_URL } from '../../Services/ApiCall/ApiUrls'; interface ForgotModalProps { visible: boolean; @@ -30,7 +31,7 @@ const ForgotModal: React.FC = ({ visible, onClose }) => { - const Navigation = useNavigation(); + const Navigation = useNavigation(); const [email, setEmail] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); @@ -58,7 +59,7 @@ const ForgotModal: React.FC = ({ try { // Make API call to send OTP for forgot password const response = await fetch( - `https://procurelinc.in/EProcurementSB/eproc/send-otp-forgot-password?email=${encodeURIComponent(email)}`, + `${BASE_URL}eproc/send-otp-forgot-password?email=${encodeURIComponent(email)}`, { method: 'POST', headers: { @@ -71,8 +72,8 @@ const ForgotModal: React.FC = ({ if (response.ok) { showToast('OTP has been sent to your email address. Please check your inbox.') onClose(); - Navigation.navigate(NavigationString.ResetPassword, { email }); - + Navigation.navigate(NavigationString.ResetPassword, { email }); + } else { const errorData = await response.text(); console.error('API Error:', errorData); @@ -101,13 +102,13 @@ const ForgotModal: React.FC = ({ > - Reset Password - + Enter your email address and we'll send you an OTP to reset your password. @@ -134,7 +135,7 @@ const ForgotModal: React.FC = ({ > Cancel - + { - const Navigation: any = useNavigation(); - const Dispatch = useDispatch(); - const [passwordVisible, setPasswordVisible] = React.useState(false); + const { colors, spacing, shadows, isDarkMode } = useTheme(); + const navigation = useNavigation(); + const dispatch = useDispatch(); + + const [passwordVisible, setPasswordVisible] = useState(false); const [activeType, setActiveType] = useState<'tender' | 'auction'>('tender'); - const [loginLoading, setLoginLoading] = useState(false); - const [auctionLoading, setAuctionLoading] = useState(false); - const { mutate: loginMutate } = useLogin(); - const { mutate: auctionMutate } = useAuctionLogin(); - // const { data } = useGetDesignation(); - const { mutate: registrationStateMutate } = useRagistrationState(); - const [forgotPassword, setForgotPassword] = useState(false) + const [showForm, setShowForm] = useState(false); - // const { data: MasterData } = usefetchMasterData() + // Animation Shared Values for Reveal + const formOpacity = useSharedValue(0); + const headerTranslateY = useSharedValue(0); + const bgTransition = useSharedValue(0); - const handleGetData = async () => { - registrationStateMutate(undefined, { - onSuccess: (res: any) => { - - if (res?.statusCode === 200) { - const { registrationStatus, contOtherId, contOrganization } = res.data; - const params = { otherId: contOtherId, orgType: contOrganization }; - - - switch (registrationStatus) { - case null: - case undefined: - - Navigation.replace(NavigationString.Registration); - break; - case 'Pending': - - // handleRoute(params); - break; - case 'Complete': - case 'Completed': - Navigation.dispatch( - CommonActions.reset({ - index: 0, - routes: [{ name: NavigationString.Home }], - }), - ); - break; - default: - { - Navigation.replace(NavigationString.Registration); - } - break; - } - return; - } - - if (res?.statusCode === 404) { - console.warn('Registration API returned 404'); - Navigation.replace('Registration'); - return; - } - - Navigation.replace('Registration'); - }, - onError: (error: any) => { - console.error('Registration status error catch :', error); - - if (error?.response?.status === 500) { - Alert.alert('Server error', 'Something went wrong. Please try again later.'); - return; - } - - if (error?.request) { - Alert.alert('Network Error', 'Please check your internet connection.'); - return; - } - - Alert.alert('Error', error?.message || 'Something went wrong'); - Navigation.replace('Registration'); - }, - }); + const handleShowForm = () => { + setShowForm(true); + formOpacity.value = withTiming(1, { duration: 800 }); + headerTranslateY.value = withTiming(-height * 0.1, { duration: 800 }); + bgTransition.value = withTiming(1, { duration: 1200 }); }; + // Animation Shared Values for Background Blobs + const blob1Pos = useSharedValue(0); + const blob2Pos = useSharedValue(0); + const blob3Pos = useSharedValue(0); + + useEffect(() => { + // Animate Blobs + blob1Pos.value = withRepeat( + withTiming(1, { duration: 8000, easing: Easing.inOut(Easing.sin) }), + -1, + true + ); + blob2Pos.value = withRepeat( + withTiming(1, { duration: 10000, easing: Easing.inOut(Easing.quad) }), + -1, + true + ); + blob3Pos.value = withRepeat( + withTiming(1, { duration: 12000, easing: Easing.inOut(Easing.cubic) }), + -1, + true + ); + }, []); + + const blob1Style = useAnimatedStyle(() => ({ + transform: [ + { translateX: interpolate(blob1Pos.value, [0, 1], [0, 50]) }, + { translateY: interpolate(blob1Pos.value, [0, 1], [0, 30]) }, + { scale: interpolate(blob1Pos.value, [0, 1], [1, 1.2]) }, + ], + })); + + const blob2Style = useAnimatedStyle(() => ({ + transform: [ + { translateX: interpolate(blob2Pos.value, [0, 1], [0, -40]) }, + { translateY: interpolate(blob2Pos.value, [0, 1], [0, -50]) }, + { scale: interpolate(blob2Pos.value, [0, 1], [1, 1.1]) }, + ], + })); + + const blob3Style = useAnimatedStyle(() => ({ + transform: [ + { translateX: interpolate(blob3Pos.value, [0, 1], [0, 30]) }, + { translateY: interpolate(blob3Pos.value, [0, 1], [0, 40]) }, + { scale: interpolate(blob3Pos.value, [0, 1], [1, 1.3]) }, + ], + })); + + const animatedHeaderStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: headerTranslateY.value }], + })); + + const animatedFormStyle = useAnimatedStyle(() => ({ + opacity: formOpacity.value, + transform: [{ translateY: interpolate(formOpacity.value, [0, 1], [20, 0]) }], + })); - const HandleLogin = async (values: any) => { - try { - if (activeType === 'tender') { - setLoginLoading(true); - loginMutate( - { password: values.password, emailAdd: values.email }, - { - onSuccess: res => { - console.log("res",res ) - if (res.statusCode == 200) { - Dispatch(setToken({ ...res.data, token: res.token, userType: activeType })); - handleGetData(); - } else { - setLoginLoading(false); - showErrorAlert(res.message) - } + // Primary brand color + const PRIMARY = '#e8a44c'; + const ACCENT_1 = '#fbbf24'; // Amber-400 + const ACCENT_2 = '#f59e0b'; // Amber-500 - }, - onError: err => { - setLoginLoading(false); - showErrorAlert(err.message); - }, - }, - ); - } else { - setAuctionLoading(true); - auctionMutate( - { password: values.password, emailAdd: values.email }, - { - onSuccess: res => { - if (res.statusCode == 200) { - Dispatch(setToken({ ...res.data, token: res.token, userType: activeType })); - Navigation.dispatch( - CommonActions.reset({ - index: 0, - routes: [{ name: NavigationString.Home }], - }), - ); - } + // Mutations + const tenderLoginMutation = useCreate(TENDER_LOGIN, { + onSuccess: (res) => handleSuccess(res, 'tender'), + onError: (err) => showErrorAlert(err.message || 'Login failed') + }); - }, - onError: err => { - showErrorAlert(err.message); - setLoginLoading(false); - }, - }, - ); - } - } catch (error) { - - } finally { - setAuctionLoading(false); + const auctionLoginMutation = useCreate(LOG_IN_URL, { + onSuccess: (res) => handleSuccess(res, 'auction'), + onError: (err) => showErrorAlert(err.message || 'Login failed') + }); + + const handleSuccess = (res: any, type: string) => { + if (res.statusCode === 200) { + console.log("Response login : ", res) + dispatch(setToken({ ...res.data, token: res.token, userType: type })); + navigation.dispatch( + CommonActions.reset({ + index: 0, + routes: [{ name: NavigationString.Home }], + }), + ); + } else { + showErrorAlert(res.message); } - }; - const isLoading = activeType === 'tender' ? loginLoading : auctionLoading; + const handleLogin = (values: any) => { + const payload = { emailAdd: values.email, password: values.password, device: "mobileApp" }; + if (activeType === 'tender') tenderLoginMutation.mutate(payload); + else auctionLoginMutation.mutate(payload); + }; + + const isLoading = activeType === 'tender' ? tenderLoginMutation.isPending : auctionLoginMutation.isPending; + + // Minimal Input Component (No Card Borders) + const MinimalInput = ({ placeholder, value, onChangeText, onBlur, icon, secureTextEntry, rightIcon, onRightIconPress }: any) => { + const focus = useSharedValue(0); + const { colors: themeColors } = useTheme(); + + const animatedStyle = useAnimatedStyle(() => { + const borderBottomColor = interpolateColor(focus.value, [0, 1], ['rgba(0,0,0,0.1)', themeColors.primary]); + const borderBottomWidth = interpolate(focus.value, [0, 1], [1, 2]); + return { + borderBottomColor, + borderBottomWidth, + }; + }); + + return ( + + {icon} + { + focus.value = withTiming(0); + onBlur(e); + }} + onFocus={() => { + focus.value = withTiming(1); + }} + secureTextEntry={secureTextEntry} + cursorColor={themeColors.primary} + /> + {rightIcon && ( + + {rightIcon} + + )} + + ); + }; + return ( - - - - - - + + + + {!showForm && + - - + ProcureLinc - - + } + {!showForm && ( + - - - setActiveType('tender')} - disabled={isLoading} + + + - Tender - - setActiveType('auction')} - disabled={isLoading} - > - Auction - - - - - Welcome Back - Enter your details to access your account - - { - HandleLogin(values); - }} - > - {({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => ( - - Email Address - - - - - {errors.email && touched.email && {errors.email}} - - Password - - - - setPasswordVisible(!passwordVisible)}> - {passwordVisible ? : } - - - {errors.password && touched.password && {errors.password}} - - setForgotPassword(true)} > - Forgot Password? - { - ReactNativeHapticFeedback.trigger('impactLight', hapticOptions); - handleSubmit(); - }} - style={styles.buttonShadow} - disabled={isLoading} + activeOpacity={0.9} + onPress={handleShowForm} + style={styles.revealButton} > - {isLoading ? ( - - ) : ( - <> - Sign In - - - )} + Get Started + + + + )} - - - Don't have an account? - Navigation.navigate('Signup')}> - Sign up - - - + {/* FORM SECTION WITH GRADIENT GLASS CARD - AT BOTTOM */} + {showForm && ( + + {/* Glass Blur Layer */} + + + {/* Gradient Background Layer */} + + + {/* Gradient Border Overlay */} + + - )} - - + + + + {/* TOGGLE */} + + {['tender', 'auction'].map((type) => ( + setActiveType(type as 'tender' | 'auction')} + style={[ + styles.toggleButton, + activeType === type && [styles.toggleButtonActive] + ]} + > + {activeType === type && ( + + + + )} + + {type.toUpperCase()} + + + ))} + + + + {({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => ( + + } + /> + {touched.email && errors.email && {errors.email}} + + } + secureTextEntry={!passwordVisible} + rightIcon={passwordVisible ? : } + onRightIconPress={() => setPasswordVisible(!passwordVisible)} + + /> + {touched.password && errors.password && {errors.password}} + + + Forgot Password? + + + handleSubmit()} + style={[styles.loginBtnContainer, { backgroundColor: colors.primary }]} + > + + + {isLoading ? 'Wait...' : 'SIGN IN'} + + {!isLoading && } + + + + )} + + + + )} + + + {/* Footer */} + + New User? + navigation.navigate(NavigationString.Signup)}> + Register Now + + + - {forgotPassword && setForgotPassword(false)}/>} - + ); }; +export default Login; + const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.white, + backgroundColor: '#ffffff', }, - header: { - height: height * 0.35, - }, - gradient: { + keyboardView: { flex: 1, - justifyContent: 'center', - alignItems: 'center', - borderBottomLeftRadius: 0, + }, + scrollContent: { + flexGrow: 1, + paddingHorizontal: 20, + paddingVertical: 40, + justifyContent: 'space-between', + }, + mainContent: { + flex: 1, + width: '100%', + justifyContent: 'flex-end', // Push content to bottom + }, + revealButtonWrapper: { + width: '100%', + justifyContent: 'flex-end', + }, + + // BACKGROUND + backgroundContainer: { + ...StyleSheet.absoluteFillObject, + overflow: 'hidden', + }, + blob: { + position: 'absolute', + borderRadius: 999, + opacity: 0.4, + }, + blob1: { + width: width * 0.8, + height: width * 0.8, + backgroundColor: '#e8a44c', + top: -width * 0.2, + left: -width * 0.2, + }, + blob2: { + width: width * 0.7, + height: width * 0.7, + backgroundColor: '#ddd6fe', + top: height * 0.1, + right: -width * 0.3, + opacity: 0.4, + }, + blob3: { + width: width * 0.9, + height: width * 0.9, + backgroundColor: '#e0f2fe', + bottom: -width * 0.2, + left: width * 0.1, + opacity: 0.3, + }, + + // HEADER + headerContentWrapper: { + width: '100%', + marginTop: height * 0.05, // Subtle top padding + marginBottom: 20, + borderRadius: 24, + overflow: 'hidden', + }, + headerContent: { + width: '100%', + padding: 20, + alignItems: 'center', // Ensure logo is centered + }, + headerTitle: { + fontSize: 28, + fontWeight: '900', + letterSpacing: -1, + textShadowColor: 'rgba(0, 0, 0, 0.1)', + textShadowOffset: { width: 0, height: 1 }, + textShadowRadius: 4, + fontFamily: FontsFamily.poppinsBold }, logo: { - height: 200, - width: 200, - }, - formContainer: { - flex: 1, - backgroundColor: colors.white, - marginTop: -40, - borderTopLeftRadius: 35, - borderTopRightRadius: 35, - paddingHorizontal: 28, - paddingTop: 10, + width: 100, // Larger logo + height: 100, + marginTop: 20, + alignSelf: 'center', shadowColor: '#000', - shadowOffset: { width: 0, height: -10 }, + shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 10, - elevation: 20, + elevation: 5, }, - sheetHandle: { - width: 40, - height: 5, - backgroundColor: '#E0E0E0', - borderRadius: 10, - alignSelf: 'center', + brandTitle: { + fontSize: height > 700 ? 42 : 36, + fontWeight: '900', + color: '#0f172a', + textAlign: 'center', // Centering for branding + letterSpacing: -1.5, + marginBottom: 4, + textShadowColor: 'rgba(0, 0, 0, 0.1)', + textShadowOffset: { width: 0, height: 1 }, + textShadowRadius: 4, + }, + brandTagline: { + fontSize: 18, + fontWeight: '600', + color: '#334155', marginBottom: 16, + letterSpacing: -0.5, + textAlign: 'center', }, - toggleWrapper: { - alignItems: 'center', + welcomeText: { + fontSize: 15, + color: '#64748b', + textAlign: 'center', + lineHeight: 24, + maxWidth: '90%', + alignSelf: 'center', + }, + + // GLASS CARD - Updated for Gradient + glassCard: { + borderRadius: 30, + overflow: 'hidden', marginBottom: 20, + width: '100%', + // Soft Shadow + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 10, + }, + shadowOpacity: 0.05, + shadowRadius: 20, + elevation: 5, }, + glassBorderContainer: { + ...StyleSheet.absoluteFillObject, + borderRadius: 30, + overflow: 'hidden', + }, + gradientBorder: { + ...StyleSheet.absoluteFillObject, + opacity: 0.5, + + borderColor: 'rgba(255,255,255,0.5)' // Fallback + }, + cardInnerContent: { + padding: 24, + paddingTop: 30, + }, + + // TOGGLE toggleContainer: { flexDirection: 'row', - backgroundColor: '#F1F3F5', - borderRadius: 20, - padding: 4, - width: width * 0.5, + marginBottom: 24, + borderBottomWidth: 1, + borderBottomColor: 'rgba(226, 232, 240, 0.6)', + paddingBottom: 8, }, toggleButton: { - flex: 1, + marginRight: 24, paddingVertical: 8, - borderRadius: 16, - alignItems: 'center', - justifyContent: 'center', - }, - toggleButtonActive: { - backgroundColor: colors.primary, + paddingHorizontal: 16, + borderRadius: 20, }, + toggleButtonActive: {}, toggleText: { fontSize: 14, fontWeight: '600', - color: '#7C7C7C', + letterSpacing: 0.5, }, toggleTextActive: { - color: colors.white, - }, - title: { - fontSize: 28, + color: '#0f172a', fontWeight: '800', - color: '#1A1A1A', - letterSpacing: -0.5, }, - subtitle: { - fontSize: 15, - color: '#7C7C7C', - marginTop: 6, - marginBottom: 30, - }, - label: { - fontSize: 14, - fontWeight: '600', - color: '#444', - marginBottom: 8, - marginLeft: 4, + + // FORM INPUTS + formContainer: { + width: '100%', }, inputWrapper: { flexDirection: 'row', alignItems: 'center', - backgroundColor: '#F8F9FB', - borderRadius: 16, - borderWidth: 1.5, - borderColor: '#F0F0F0', - paddingHorizontal: 16, - height: 58, + height: 56, + marginBottom: 4, }, - inputError: { - borderColor: '#FF5252', - backgroundColor: '#FFF8F8', - }, - input: { - flex: 1, - marginLeft: 12, - fontSize: 16, - color: '#1A1A1A', - fontWeight: '500', - }, - errorText: { - color: '#FF5252', - fontSize: 12, - marginTop: 5, - marginLeft: 12, - fontWeight: '500', - }, - forgotPass: { - alignSelf: 'flex-end', - marginTop: 12, - marginBottom: 24, - }, - forgotPassText: { - color: colors.primary, - fontWeight: '600', - fontSize: 14, - }, - buttonShadow: { - shadowColor: colors.primary, - shadowOffset: { width: 0, height: 8 }, - shadowOpacity: 0.3, - shadowRadius: 12, - elevation: 8, - }, - button: { - height: 58, - borderRadius: 18, - flexDirection: 'row', - justifyContent: 'center', + inputIcon: { + marginRight: 16, + width: 24, alignItems: 'center', }, - buttonText: { - color: colors.white, + textInput: { + flex: 1, + fontSize: 16, + color: '#0f172a', // This might need theme injection in the component + fontWeight: '500', + height: '100%', + }, + inputRightIcon: { + padding: 8, + }, + errorText: { + fontSize: 12, + color: '#ef4444', + marginLeft: 40, + marginBottom: 8, + }, + forgotPass: { + alignSelf: 'flex-start', + marginTop: 8, + marginBottom: 32, + marginLeft: 40, + }, + forgotText: { + fontSize: 14, + fontWeight: '600', + color: '#64748b', + }, + + // BUTTON + loginBtnContainer: { + shadowColor: '#e8a44c', + shadowOffset: { width: 0, height: 8 }, + shadowOpacity: 0.3, + shadowRadius: 16, + elevation: 8, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 15, + borderRadius: 28, + }, + loginBtn: { + height: 56, + borderRadius: 28, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + loginBtnText: { + fontSize: 16, + fontWeight: '800', + color: '#FFFFFF', + letterSpacing: 1.5, + textTransform: 'uppercase', + }, + arrowIcon: { + marginLeft: 12, + }, + + // FOOTER + footer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginBottom: 10, + }, + footerText: { + fontSize: 14, + color: '#64748b', + }, + signUpText: { + fontSize: 14, + fontWeight: '800', + }, + + // REVEAL BUTTON + revealButtonContainer: { + marginTop: 'auto', // Push to bottom + marginBottom: 30, + width: '100%', + paddingHorizontal: 10, + }, + + revealButton: { + width: '100%', + height: 64, + borderRadius: 32, + overflow: 'hidden', + shadowColor: '#e8a44c', + shadowOffset: { width: 0, height: 10 }, + shadowOpacity: 0.3, + shadowRadius: 20, + elevation: 10, + }, + revealButtonGradient: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 24, + }, + revealButtonText: { fontSize: 18, - fontWeight: '700', + fontWeight: '800', + color: '#FFF', + letterSpacing: 1, + marginRight: 12, + }, + topRightBranding: { + position: 'absolute', + top: 0, + right: 0, + alignItems: 'flex-end', + paddingRight: 10, + }, + logoSmall: { + width: 100, + height: 70, + }, + introTextContainer: { + marginBottom: 20, + paddingHorizontal: 10, + }, + introMainText: { + fontSize: 24, + color: '#FFFFFF', + fontWeight: '800', + fontFamily: FontsFamily.poppinsBold, + textShadowColor: 'rgba(0, 0, 0, 0.3)', + textShadowOffset: { width: 0, height: 2 }, + textShadowRadius: 6, + }, + introSubText: { + fontSize: 14, + color: 'rgba(255, 255, 255, 0.8)', + fontWeight: '500', + marginTop: 4, + fontFamily: FontsFamily.poppinsMedium, }, }); -export default Login; diff --git a/src/Screens/Login/ResetPassword.tsx b/src/Screens/Login/ResetPassword.tsx index 2bffc94..2579e10 100644 --- a/src/Screens/Login/ResetPassword.tsx +++ b/src/Screens/Login/ResetPassword.tsx @@ -263,7 +263,7 @@ const ResetPassword: React.FC = ({ route, navigation }) => { try { const res = await axios.post( - `https://procurelinc.in/EProcurementSB/eproc/send-otp-forgot-password?email=${encodeURIComponent(email)}`, + `${BASE_URL}eproc/send-otp-forgot-password?email=${encodeURIComponent(email)}`, null, { headers: { @@ -432,7 +432,7 @@ const ResetPassword: React.FC = ({ route, navigation }) => { )} - {!isOtpVerified&& 0 && styles.disabledResendButton]} onPress={handleResendOtp} disabled={resendLoading || resendCooldown > 0} diff --git a/src/Screens/Profile/ProfileTab.tsx b/src/Screens/Profile/ProfileTab.tsx index f5276a0..f9202f8 100644 --- a/src/Screens/Profile/ProfileTab.tsx +++ b/src/Screens/Profile/ProfileTab.tsx @@ -1,7 +1,6 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState, useCallback } from 'react'; import { View, - Text, ScrollView, StyleSheet, TouchableOpacity, @@ -15,8 +14,11 @@ import { TextInput, KeyboardAvoidingView, Alert, + StatusBar, + Image, } from 'react-native'; -import { LinearGradient } from 'react-native-linear-gradient'; +import LinearGradient from 'react-native-linear-gradient'; +import Animated, { FadeInDown, FadeInUp, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'; import { AlertCircle, UserCircle, @@ -51,15 +53,18 @@ import { Edit, X, Save, + ChevronRight, + LogOut } from 'lucide-react-native'; import { useFocusEffect, useNavigation } from '@react-navigation/native'; - import { useContractorProfile, useUpdateUserProfile, useESetuAdharVarification } from '../../Services/ApiCall/Hooks'; -import colors from '../../Constant/Color'; import { useSelector } from 'react-redux'; import ConfirmDialog from '../../Constant/Dialog/ConfirmDialog'; import { generateTransactionId } from '../../Helper/uniqueId'; import NavigationString from '../../Constant/NavigationString'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; +import ImagePath from '../../Constant/ImagePath'; const { width } = Dimensions.get('window'); @@ -165,11 +170,14 @@ const hasSectionData = (values: any[]): boolean => { // Main Component const ProfileTab: React.FC = () => { + const { colors, spacing, shadows, isDarkMode } = useTheme(); + const styles = useMemo(() => createStyles(colors), [colors]); const { data, refetch, isLoading, error } = useContractorProfile(); const user = useSelector((state: any) => state.token?.token); + console.log("User: ", user) const Navigation: any = useNavigation(); const { mutateAsync: eSetuAdharVarification, isPending } = useESetuAdharVarification(); - + const [editModalVisible, setEditModalVisible] = useState(false); const [showDialog, setShowDialog] = useState(false); const [isNavigating, setIsNavigating] = useState(false); @@ -182,19 +190,19 @@ const ProfileTab: React.FC = () => { const displayMobile = isAuctionUser ? user?.mobileNo : data?.mobileNo; const handleAadhaarPress = () => { - if (user?.esetuVerified === 'Not Verified') { + if (user?.esetuVerified === 'Not Verified' || user?.esetuVerified === null) { setShowDialog(true); } }; - useFocusEffect(()=>{ - setIsNavigating(false) - }) + useFocusEffect(() => { + setIsNavigating(false) + }) const handleProceedVerification = async () => { try { setIsNavigating(true); - + const payload = { contId: user.id, transactionId: generateTransactionId(), @@ -202,15 +210,15 @@ const ProfileTab: React.FC = () => { udf2: "77.34", udf3: "string", udf4: "string", - responseUrl: "https://dev.procurelinc.in/" + responseUrl: "http://dev.192.168.6.194:8090/" }; const res = await eSetuAdharVarification(payload); - + setShowDialog(false); - + Navigation.navigate(NavigationString.KycWebViewScreen, { res }); - + } catch (err) { Alert.alert('Error', 'Failed to initiate verification. Please try again.'); setIsNavigating(false); @@ -221,7 +229,7 @@ const ProfileTab: React.FC = () => { return ( - Loading profile... + Loading profile... ); } @@ -230,9 +238,9 @@ const ProfileTab: React.FC = () => { return ( - {error} - - Retry + {error instanceof Error ? error.message : String(error)} + refetch()}> + Retry ); @@ -243,7 +251,7 @@ const ProfileTab: React.FC = () => { return ( <> { /> } showsVerticalScrollIndicator={false} - removeClippedSubviews={true} - maxToRenderPerBatch={10} - updateCellsBatchingPeriod={50} - initialNumToRender={8} - windowSize={10} > {/* Header with Gradient */} @@ -274,10 +277,10 @@ const ProfileTab: React.FC = () => { showEdit={!isAuctionUser} onEdit={() => setEditModalVisible(true)} > - { data.agentWebsite, data.agentRegno ]) && ( - - - - - - - - )} + + + + + + + + )} {/* Address Information - Only show if has data */} {hasSectionData([ @@ -312,13 +315,13 @@ const ProfileTab: React.FC = () => { data.agentState, data.agentPincode ]) && ( - - - - - - - )} + + + + + + + )} {/* Tax & Legal - Only show if has data */} {hasSectionData([ @@ -328,14 +331,14 @@ const ProfileTab: React.FC = () => { data.gistnNo, data.comAadharNo ]) && ( - - - - - - - - )} + + + + + + + + )} {/* Registration Status - Only show if has data */} {hasSectionData([ @@ -344,18 +347,18 @@ const ProfileTab: React.FC = () => { data.msmeStatus, data.expiryDate ]) && ( - - - - - - - )} + + + + + + + )} @@ -401,6 +404,8 @@ const EditProfileModal: React.FC = ({ data, onSuccess, }) => { + const { colors } = useTheme(); + const styles = useMemo(() => createStyles(colors), [colors]); const [formData, setFormData] = useState({ firstName: data.firstName || '', middleName: data.middleName || '', @@ -484,83 +489,83 @@ const EditProfileModal: React.FC = ({ style={styles.modalOverlay} behavior={Platform.OS === 'ios' ? 'padding' : 'height'} > - + - Edit Personal Information + Edit Personal Information - + - First Name * + First Name * setFormData({ ...formData, firstName: text })} placeholder="Enter first name" - placeholderTextColor={colors.grayText} + placeholderTextColor={colors.textSecondary} /> {errors.firstName && ( - {errors.firstName} + {errors.firstName} )} - Middle Name + Middle Name setFormData({ ...formData, middleName: text })} placeholder="Enter middle name" - placeholderTextColor={colors.grayText} + placeholderTextColor={colors.textSecondary} /> - Last Name * + Last Name * setFormData({ ...formData, lastName: text })} placeholder="Enter last name" - placeholderTextColor={colors.grayText} + placeholderTextColor={colors.textSecondary} /> {errors.lastName && ( - {errors.lastName} + {errors.lastName} )} - Email * + Email * setFormData({ ...formData, emailId: text })} placeholder="Enter email" - placeholderTextColor={colors.grayText} + placeholderTextColor={colors.textSecondary} keyboardType="email-address" autoCapitalize="none" /> {errors.emailId && ( - {errors.emailId} + {errors.emailId} )} - Mobile Number * + Mobile Number * setFormData({ ...formData, mobileNo: text })} placeholder="Enter mobile number" - placeholderTextColor={colors.grayText} + placeholderTextColor={colors.textSecondary} keyboardType="phone-pad" maxLength={10} /> {errors.mobileNo && ( - {errors.mobileNo} + {errors.mobileNo} )} @@ -571,7 +576,7 @@ const EditProfileModal: React.FC = ({ onPress={onClose} disabled={updateProfileMutation.isPending} > - Cancel + Cancel = ({ disabled={updateProfileMutation.isPending} > {updateProfileMutation.isPending ? ( - + ) : ( <> - - Save + + Save )} @@ -597,6 +602,9 @@ const EditProfileModal: React.FC = ({ // Profile Header Component - OPTIMIZED const ProfileHeader: React.FC = React.memo(({ data, displayName }) => { + const { colors } = useTheme(); + const styles = useMemo(() => createStyles(colors), [colors]); + const getInitials = (name: string): string => { if (!name || typeof name !== 'string' || name.trim().length === 0) { const fallbackName = `${data.firstName || ''} ${data.lastName || ''}`.trim(); @@ -630,46 +638,44 @@ const ProfileHeader: React.FC = React.memo(({ data, displayN return ( - - {getInitials(displayName)} + {getInitials(displayName)} - + {displayName || 'Unknown User'} - + - - {data.role} + + {data.role} - + ); }); ProfileHeader.displayName = 'ProfileHeader'; -// Quick Stats Component - OPTIMIZED WITH TOUCHABLE AADHAAR +// Quick Stats Component - Glass Cards const QuickStats: React.FC = React.memo(({ data, onAadhaarPress }) => { const user = useSelector((state: any) => state.token?.token); - + const { colors } = useTheme(); + const stats: StatItem[] = useMemo( () => [ { Icon: CheckCircle, label: 'Aadhaar', - value: user?.esetuVerified, + value: user?.esetuVerified || 'Pending', color: user?.esetuVerified === 'Not Verified' ? colors.error : colors.success, onPress: onAadhaarPress, }, @@ -677,169 +683,103 @@ const QuickStats: React.FC = React.memo(({ data, onAadhaarPress Icon: ShieldCheck, label: '2FA', value: data.twoFactorAuthEnabled ? 'Enabled' : 'Disabled', - color: data.twoFactorAuthEnabled ? colors.success : colors.grayText, - }, - { - Icon: FileText, - label: 'Registration', - value: data.registrationStatus, - color: colors.info, + color: data.twoFactorAuthEnabled ? colors.success : colors.textSecondary, }, + // ... ], - [data.twoFactorAuthEnabled, data.registrationStatus, user?.esetuVerified, onAadhaarPress] + [data.twoFactorAuthEnabled, user?.esetuVerified, onAadhaarPress, colors] ); return ( - - {stats.map((stat) => { - const CardContent = ( - - - - {stat.value} - - {stat.label} - + + {stats.map((stat, index) => { + const content = ( + + + + + {stat.value} + {stat.label} + ); - if (stat.onPress) { - return ( - - {CardContent} - - ); - } - - return {CardContent}; + return stat.onPress ? ( + {content} + ) : ( + {content} + ); })} ); }); -QuickStats.displayName = 'QuickStats'; - -// Info Section Component - OPTIMIZED +// Info Section Component - Glass Card Container const InfoSection: React.FC = React.memo( - ({ title, IconComponent, children, onEdit, showEdit = false }) => { + ({ title, IconComponent, children, onEdit, showEdit = false, delay = 0 }) => { + const { colors } = useTheme(); + return ( - - - - - + + + + + - {title} + {title} {showEdit && onEdit && ( - - + + )} - {children} - + {children} + ); } ); -InfoSection.displayName = 'InfoSection'; - -// Info Row Component - OPTIMIZED +// Info Row Component const InfoRow: React.FC = React.memo( ({ label, value, IconComponent, valueColor }) => { - if (!isValidValue(value)) { - return null; - } + const { colors } = useTheme(); + + if (!isValidValue(value)) return null; return ( - - - - - - - - {label} - - {value} - - - + + + + {label} + {value} ); } ); -InfoRow.displayName = 'InfoRow'; - // Styles -interface Styles { - container: ViewStyle; - contentContainer: ViewStyle; - centerContainer: ViewStyle; - loadingText: TextStyle; - errorText: TextStyle; - retryButton: ViewStyle; - retryButtonText: TextStyle; - headerGradient: ViewStyle; - avatarContainer: ViewStyle; - avatar: ViewStyle; - avatarText: TextStyle; - statusBadge: ViewStyle; - statusDot: ViewStyle; - headerName: TextStyle; - roleBadge: ViewStyle; - roleText: TextStyle; - statsContainer: ViewStyle; - statCard: ViewStyle; - statValue: TextStyle; - statLabel: TextStyle; - section: ViewStyle; - sectionHeader: ViewStyle; - sectionHeaderLeft: ViewStyle; - sectionIconContainer: ViewStyle; - sectionTitle: TextStyle; - sectionContent: ViewStyle; - infoRow: ViewStyle; - infoRowTouchable: ViewStyle; - infoRowContent: ViewStyle; - infoIconContainer: ViewStyle; - infoTextContainer: ViewStyle; - infoLabel: TextStyle; - infoValue: TextStyle; - editButton: ViewStyle; - modalOverlay: ViewStyle; - modalContent: ViewStyle; - modalHeader: ViewStyle; - modalTitle: TextStyle; - closeButton: ViewStyle; - formContainer: ViewStyle; - inputGroup: ViewStyle; - inputLabel: TextStyle; - input: TextStyle; - inputError: TextStyle; - errorText: TextStyle; - modalFooter: ViewStyle; - button: ViewStyle; - cancelButton: ViewStyle; - saveButton: ViewStyle; - cancelButtonText: TextStyle; - saveButtonText: TextStyle; - navigationOverlay: ViewStyle; -} - -const styles = StyleSheet.create({ +const createStyles = (colors: any) => StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.lightGray, + paddingHorizontal: 20 }, contentContainer: { paddingBottom: 20, @@ -848,16 +788,16 @@ const styles = StyleSheet.create({ flex: 1, justifyContent: 'center', alignItems: 'center', - backgroundColor: colors.white, + backgroundColor: colors.background, padding: 20, }, loadingText: { marginTop: 16, fontSize: 16, - color: colors.grayText, + color: colors.textSecondary, fontWeight: '500', }, - errorText: { + screenErrorText: { marginTop: 16, fontSize: 16, color: colors.error, @@ -886,13 +826,6 @@ const styles = StyleSheet.create({ paddingTop: Platform.OS === 'ios' ? 60 : 40, paddingBottom: 30, alignItems: 'center', - borderBottomLeftRadius: 32, - borderBottomRightRadius: 32, - elevation: 8, - shadowColor: colors.shadow, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.3, - shadowRadius: 8, }, avatarContainer: { position: 'relative', @@ -902,11 +835,11 @@ const styles = StyleSheet.create({ width: 100, height: 100, borderRadius: 50, - backgroundColor: colors.white, + backgroundColor: colors.card, justifyContent: 'center', alignItems: 'center', borderWidth: 4, - borderColor: colors.white, + borderColor: colors.card, elevation: 4, shadowColor: colors.shadow, shadowOffset: { width: 0, height: 2 }, @@ -926,7 +859,7 @@ const styles = StyleSheet.create({ height: 24, borderRadius: 12, borderWidth: 3, - borderColor: colors.white, + borderColor: colors.card, // Updated justifyContent: 'center', alignItems: 'center', }, @@ -939,7 +872,7 @@ const styles = StyleSheet.create({ headerName: { fontSize: 26, fontWeight: '700', - color: colors.white, + color: colors.text, // Updated marginBottom: 8, textAlign: 'center', paddingHorizontal: 20, @@ -947,14 +880,14 @@ const styles = StyleSheet.create({ roleBadge: { flexDirection: 'row', alignItems: 'center', - backgroundColor: 'rgba(255, 255, 255, 0.25)', + backgroundColor: colors.primary + '20', // Updated opacity approach paddingHorizontal: 16, paddingVertical: 6, borderRadius: 20, gap: 6, }, roleText: { - color: colors.white, + color: colors.primary, // Updated fontSize: 14, fontWeight: '600', }, @@ -968,7 +901,7 @@ const styles = StyleSheet.create({ }, statCard: { flex: 1, - backgroundColor: colors.white, + backgroundColor: colors.card, borderRadius: 16, padding: 16, alignItems: 'center', @@ -981,20 +914,20 @@ const styles = StyleSheet.create({ statValue: { fontSize: 14, fontWeight: '700', - color: colors.darkText, + color: colors.text, marginTop: 8, textAlign: 'center', }, statLabel: { fontSize: 12, - color: colors.grayText, + color: colors.textSecondary, marginTop: 4, }, section: { marginHorizontal: 16, marginTop: 24, - backgroundColor: colors.white, + backgroundColor: colors.card, borderRadius: 20, padding: 20, elevation: 2, @@ -1021,7 +954,7 @@ const styles = StyleSheet.create({ width: 40, height: 40, borderRadius: 12, - backgroundColor: colors.primaryHighlight, + backgroundColor: colors.primary + '10', // Updated justifyContent: 'center', alignItems: 'center', marginRight: 12, @@ -1029,7 +962,7 @@ const styles = StyleSheet.create({ sectionTitle: { fontSize: 18, fontWeight: '700', - color: colors.darkText, + color: colors.text, flex: 1, }, sectionContent: { @@ -1038,7 +971,7 @@ const styles = StyleSheet.create({ editButton: { padding: 8, borderRadius: 8, - backgroundColor: colors.primaryHighlight, + backgroundColor: colors.primary + '10', // Updated }, infoRow: { @@ -1047,7 +980,7 @@ const styles = StyleSheet.create({ }, infoRowTouchable: { padding: 14, - backgroundColor: colors.lightestGray, + backgroundColor: colors.background, // Updated borderRadius: 12, }, infoRowContent: { @@ -1058,7 +991,7 @@ const styles = StyleSheet.create({ width: 32, height: 32, borderRadius: 8, - backgroundColor: colors.white, + backgroundColor: colors.card, // Updated justifyContent: 'center', alignItems: 'center', marginRight: 12, @@ -1070,13 +1003,13 @@ const styles = StyleSheet.create({ }, infoLabel: { fontSize: 13, - color: colors.grayText, + color: colors.textSecondary, fontWeight: '500', lineHeight: 18, }, infoValue: { fontSize: 15, - color: colors.darkText, + color: colors.text, fontWeight: '600', lineHeight: 21, }, @@ -1087,11 +1020,11 @@ const styles = StyleSheet.create({ justifyContent: 'flex-end', }, modalContent: { - backgroundColor: colors.white, borderTopLeftRadius: 24, borderTopRightRadius: 24, maxHeight: '90%', paddingTop: 20, + borderRadius: 30 }, modalHeader: { flexDirection: 'row', @@ -1105,7 +1038,7 @@ const styles = StyleSheet.create({ modalTitle: { fontSize: 20, fontWeight: '700', - color: colors.darkText, + color: colors.text, }, closeButton: { padding: 4, @@ -1120,7 +1053,7 @@ const styles = StyleSheet.create({ inputLabel: { fontSize: 14, fontWeight: '600', - color: colors.darkText, + color: colors.text, marginBottom: 8, }, input: { @@ -1129,8 +1062,8 @@ const styles = StyleSheet.create({ borderRadius: 12, padding: 14, fontSize: 15, - color: colors.darkText, - backgroundColor: colors.white, + color: colors.text, + backgroundColor: colors.card, // Updated }, inputError: { borderColor: colors.error, @@ -1157,7 +1090,7 @@ const styles = StyleSheet.create({ gap: 8, }, cancelButton: { - backgroundColor: colors.lightestGray, + backgroundColor: colors.background, // Updated }, saveButton: { backgroundColor: colors.primary, @@ -1165,7 +1098,7 @@ const styles = StyleSheet.create({ cancelButtonText: { fontSize: 16, fontWeight: '600', - color: colors.darkText, + color: colors.text, }, saveButtonText: { fontSize: 16, diff --git a/src/Screens/Ragistration/PartnerShip.tsx b/src/Screens/Ragistration/PartnerShip.tsx index 99ae19d..689a4c4 100644 --- a/src/Screens/Ragistration/PartnerShip.tsx +++ b/src/Screens/Ragistration/PartnerShip.tsx @@ -3,23 +3,22 @@ import { SafeAreaView, ScrollView, StyleSheet, - Text, TouchableOpacity, View, Platform, } from 'react-native'; -import React, { use, useState } from 'react'; -import { useNavigation, useRoute, NavigationProp, RouteProp } from '@react-navigation/native'; -import colors from '../../Constant/Color'; +import React, { useMemo, useState } from 'react'; +import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton'; -import MyInput from '../../Component/MyInput'; +import MyInput from '../../components/MyInput'; import { showToast } from '../../Constant/ShowDailog'; -import { PARTNER_API_ENDPOINT } from '../../Services/ApiCall/ApiUrls'; import { usePartnerShipSubmit } from '../../Services/ApiCall/Hooks'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Navigation } from 'lucide-react-native'; import { NavigationString } from '../../Constant/AllImports'; - +import Animated, { FadeInUp } from 'react-native-reanimated'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; +import { Trash2, UserPlus } from 'lucide-react-native'; type Partner = { partnerName: string; @@ -32,11 +31,12 @@ type Navigation = { replace: (route: string) => void; }; type PartnershipParams = { - otherId: string | number + otherId: string | number; }; + const Partnership = () => { const route = useRoute>(); - + const { colors, spacing, typography, shadows } = useTheme(); const navigation = useNavigation() as Navigation; const [partners, setPartners] = useState([ { @@ -49,9 +49,11 @@ const Partnership = () => { ]); const [focusedField, setFocusedField] = useState(''); - const [loading, setLoading] = useState(false) - const { mutateAsync:partnerShipSubmit } = usePartnerShipSubmit(); - const insets = useSafeAreaInsets(); + const [loading, setLoading] = useState(false); + const { mutateAsync: partnerShipSubmit } = usePartnerShipSubmit(); + const insets = useSafeAreaInsets(); + const styles = useMemo(() => makeStyles(colors, spacing, shadows, typography), [colors, spacing, shadows, typography]); + const handleChange = (index: number, name: keyof Partner, value: string) => { const updated = [...partners]; updated[index][name] = value; @@ -90,9 +92,8 @@ const Partnership = () => { }; const handleSubmit = async () => { - setLoading(true) + setLoading(true); try { - // Convert percentage strings to numbers and validate const payload = partners.map((p, i) => { const percentage = Number(p.percentage); @@ -105,13 +106,12 @@ const Partnership = () => { ptnrAadharNo: p.aadharNumber, ptnrMobNo: p.mobileNumber, ptnrName: p.partnerName, - ptnrPop: percentage, // Ensure this is a number + ptnrPop: percentage, ptnrPANNo: p.partnerPan, ptnrNo: i, }; }); - // Validate total percentage equals 100% const totalPercentage = payload.reduce((sum, p) => sum + p.ptnrPop, 0); if (totalPercentage !== 100) { showToast(`Total percentage must equal 100% (currently ${totalPercentage}%)`); @@ -123,153 +123,179 @@ const Partnership = () => { if (response?.statusCode === 200) { showToast(response.message || 'Submitted successfully!'); navigation.replace(NavigationString.Home); - } } catch (error: any) { console.error('❌ Submission failed:', error.message); showToast(error.message || 'Submission failed. Please check partner details.'); - }finally{ - setLoading(false) + } finally { + setLoading(false); } }; return ( - - Partnership Details + + + + Partnership Details + + + Add all partners and their share percentages + + + - + {partners.map((partner, index) => ( - - Partner {index + 1} + + + + Partner {index + 1} + + {partners.length > 1 && ( + handleRemove(index)} activeOpacity={0.7}> + + + )} + handleChange(index, 'partnerName', text)} onFocus={() => setFocusedField(`partnerName-${index}`)} onBlur={() => setFocusedField('')} focused={focusedField === `partnerName-${index}`} + containerStyle={{ marginBottom: spacing.s }} /> handleChange(index, 'percentage', text)} onFocus={() => setFocusedField(`percentage-${index}`)} onBlur={() => setFocusedField('')} focused={focusedField === `percentage-${index}`} keyboardType="numeric" - maxlength={3} + maxLength={3} + containerStyle={{ marginBottom: spacing.s }} /> handleChange(index, 'partnerPan', text)} onFocus={() => setFocusedField(`partnerPan-${index}`)} onBlur={() => setFocusedField('')} focused={focusedField === `partnerPan-${index}`} autoCapitalize="characters" - maxlength={10} - + maxLength={10} + containerStyle={{ marginBottom: spacing.s }} /> handleChange(index, 'aadharNumber', text)} onFocus={() => setFocusedField(`aadharNumber-${index}`)} onBlur={() => setFocusedField('')} focused={focusedField === `aadharNumber-${index}`} keyboardType="numeric" - maxlength={12} + maxLength={12} + containerStyle={{ marginBottom: spacing.s }} /> handleChange(index, 'mobileNumber', text)} onFocus={() => setFocusedField(`mobileNumber-${index}`)} onBlur={() => setFocusedField('')} focused={focusedField === `mobileNumber-${index}`} keyboardType="phone-pad" - maxlength={10} + maxLength={10} /> - - {partners.length > 1 && ( - handleRemove(index)}> - Delete Partner - - )} - + ))} - - + Add Partner + + + + Add Partner + - + + + ); }; export default Partnership; -const styles = StyleSheet.create({ - heading: { - fontSize: 18, - fontWeight: 'bold', - marginBottom: 10, - marginTop: 10, - textAlign: 'center', - - }, - partnerTitle: { - fontWeight: '600', - fontSize: 16, - marginBottom: 10, +const makeStyles = (colors: any, spacing: any, shadows: any, typography: any) => StyleSheet.create({ + header: { + paddingHorizontal: spacing.m, + paddingBottom: spacing.m, + borderBottomWidth: 1, + borderBottomColor: colors.border, + backgroundColor: colors.surface, }, scrollContainer: { - // backgroundColor: color.background, flexGrow: 1, + padding: spacing.m, + paddingBottom: 30, + }, + card: { + backgroundColor: colors.surface, + borderRadius: 14, + padding: 14, + marginBottom: spacing.m, + }, + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: spacing.s, + paddingBottom: spacing.xs, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, }, addButton: { backgroundColor: colors.primary, - padding: 10, - marginTop: 10, - borderRadius: 10, + flexDirection: 'row', alignItems: 'center', - width: 140, + justifyContent: 'center', + paddingVertical: 12, + paddingHorizontal: 20, + borderRadius: 12, alignSelf: 'flex-end', }, - addText: { - color: 'white', - fontWeight: '600', - }, - deleteButton: { - backgroundColor: '#FF5C5C', - padding: 10, - marginTop: 10, - borderRadius: 10, - alignItems: 'center', - width: 140, - alignSelf: 'flex-end', - }, - deleteText: { - color: 'white', - fontWeight: '600', + footer: { + padding: spacing.m, + borderTopWidth: 1, + borderTopColor: colors.border, + backgroundColor: colors.surface, }, }); diff --git a/src/Screens/Ragistration/Ragistration.tsx b/src/Screens/Ragistration/Ragistration.tsx index 8714847..61a47f5 100644 --- a/src/Screens/Ragistration/Ragistration.tsx +++ b/src/Screens/Ragistration/Ragistration.tsx @@ -1,24 +1,16 @@ -import React, { useEffect, useState } from 'react'; -import { View, Text, TouchableOpacity, StyleSheet, Platform, KeyboardAvoidingView, SafeAreaView, ScrollView, BackHandler, ToastAndroid, Button, Alert } from 'react-native'; -// import AntDesign from 'react-native-vector-icons/AntDesign'; +import React, { useEffect, useMemo, useState } from 'react'; +import { View, TouchableOpacity, StyleSheet, Platform, KeyboardAvoidingView, ScrollView, BackHandler, ToastAndroid, Alert } from 'react-native'; import { ArrowLeft } from 'lucide-react-native'; -import { NavigationProp, RouteProp, useNavigation, useRoute } from '@react-navigation/native'; +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import AsyncStorage from '@react-native-async-storage/async-storage'; -// import { sendAadhaarOtp, verifyAadhaarOtp, verifyGstNumber, verifyPanHandlerRes } from '../../utils/helper/ragisterationApiCalls'; - -// import { getAllMsMeOrg, getCityDropdown, getClassDropdown, getMsmeDropdown, getOrgDropdown, getSocialDropdown, getStateDropdown } from '../../utils/helper/dropdownApis'; import { ApiResponse, DocumentsType, FormDataType, OrgType, UserData } from './types/types'; import { pick } from '@react-native-documents/picker'; -// import { REJ_STAGE_TWO } from '../../utils/services/apiUrl'; -// import LoaderModel from '../../component/LoaderModal'; - import axios from 'axios'; import StageOne from './ragistrationStages/StageOne'; import StageTwo from './ragistrationStages/StageTwo'; import StageZero from './ragistrationStages/StageZero'; import StageThree from './ragistrationStages/StageThree'; import StageFour from './ragistrationStages/StageFour'; -import colors from '../../Constant/Color'; import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton'; import StepProgressBar from '../../Component/StepProgressBar'; import { formatDateInput } from '../../Helper/formatDateInput'; @@ -26,6 +18,9 @@ import { showToast } from '../../Constant/ShowDailog'; import { useCityDropdown, useContractorStageTwoSubmit, usefetchMasterData, useSendAadhaarOtp, useVerifyAadhaarOtp, useVerifyGstNumber, useVerifyPan } from '../../Services/ApiCall/Hooks'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSelector } from 'react-redux'; +import Animated, { FadeInUp, FadeInDown } from 'react-native-reanimated'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; type RouteParams = { userData?: { @@ -38,11 +33,12 @@ type RouteParams = { }; const Registration: React.FC = () => { + const { colors, spacing, typography, shadows } = useTheme(); const navigation = useNavigation(); const { params } = useRoute, string>>(); const userData = params as RouteParams; const [stage, setStage] = useState(0); - const insets = useSafeAreaInsets(); + const insets = useSafeAreaInsets(); const [focusedField, setFocusedField] = useState(null); const [selectedOrg, setSelectedOrg] = useState(0); const [documents, setDocuments] = useState({ @@ -102,58 +98,52 @@ const Registration: React.FC = () => { const { mutateAsync: verifyGstNumber } = useVerifyGstNumber(); const { mutateAsync: contractorStageTwoSubmit } = useContractorStageTwoSubmit(); const { data: masterData } = usefetchMasterData(); -useEffect(() => { - if (!masterData?.data) return; - const contractorClasses = masterData.data.contractorClasses || []; + + const styles = useMemo(() => makeStyles(colors, spacing, shadows, typography), [colors, spacing, shadows, typography]); + + useEffect(() => { + if (!masterData?.data) return; + const contractorClasses = masterData.data.contractorClasses || []; const msmeOrgs = masterData.data.msmeOrgs || []; - const mappedmsmeOrgs = msmeOrgs.map((item:any)=> ({ - label: item.comName, - value: item.msmeId, - })); - const orgs = masterData.data.organizationTypes || []; - const mappedcontractorClasses = contractorClasses.map((item:any)=> ({ - label: item.contClassName || '', - value: item.contClassId, - })); - const mapped = orgs.map((item:any)=> ({ - label: item.organizationType, - value: item.orgId, - })); - const socialCategories = masterData.data.socialCategories || []; - const mappedsocialCategories = socialCategories.map((item:any)=> ({ - label: item.categoryName, - value: item.socialCategoryId, - })); - const stateList = masterData.data.states || []; - const mappedStates = stateList.map((item:any)=> ({ - label: item.stateName, - value: item.stateId, - })); - setAllMsmeOrgs(mappedmsmeOrgs); - setAllSocialCategories(mappedsocialCategories); - setContractorClassItems(mappedcontractorClasses); - setAllOrganizations(mapped); - setstates(mappedStates); -}, [masterData?.data]); + const mappedmsmeOrgs = msmeOrgs.map((item: any) => ({ + label: item.comName, + value: item.msmeId, + })); + const orgs = masterData.data.organizationTypes || []; + const mappedcontractorClasses = contractorClasses.map((item: any) => ({ + label: item.contClassName || '', + value: item.contClassId, + })); + const mapped = orgs.map((item: any) => ({ + label: item.organizationType, + value: item.orgId, + })); + const socialCategories = masterData.data.socialCategories || []; + const mappedsocialCategories = socialCategories.map((item: any) => ({ + label: item.categoryName, + value: item.socialCategoryId, + })); + const stateList = masterData.data.states || []; + const mappedStates = stateList.map((item: any) => ({ + label: item.stateName, + value: item.stateId, + })); + setAllMsmeOrgs(mappedmsmeOrgs); + setAllSocialCategories(mappedsocialCategories); + setContractorClassItems(mappedcontractorClasses); + setAllOrganizations(mapped); + setstates(mappedStates); + }, [masterData?.data]); const { mutate: cityDropdownMutate } = useCityDropdown(); - const { data: MasterData}=usefetchMasterData() - const user = useSelector((state: any) => state.token.token); + const { data: MasterData } = usefetchMasterData(); + const user = useSelector((state: any) => state.token.token); const steps = ['', '', '', '', '']; - const dropdownStyle = { - borderWidth: 1, - borderColor: '#e0e0e0', - borderRadius: 12, - paddingHorizontal: 14, - height: 50, - backgroundColor: '#fff', - marginBottom: 10, - } + useEffect(() => { - const fetchUserData = async (user:any) => { + const fetchUserData = async (user: any) => { try { - if (user) { const parsedUser = user; setFormData(prev => ({ @@ -180,25 +170,11 @@ useEffect(() => { gstin = gstin.toUpperCase(); const panRegex = /^[A-Z]{5}[0-9]{4}[A-Z]$/; - const gstinRegex = /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/; if (!panRegex.test(pan)) { return { valid: false, message: "Invalid PAN format. It should be like 'ABCDE1234F'." }; } - // if (!gstinRegex.test(gstin)) { - // return { valid: false, message: "Invalid GSTIN format. It should be like '22ABCDE1234F1Z5'." }; - // } - - // const gstinPAN = gstin.slice(2, 12); // Extract PAN part from GSTIN - - // if (pan !== gstinPAN) { - // return { - // valid: false, - // message: "PAN does not match the PAN embedded in GSTIN.", - // }; - // } - return { valid: true, message: 'PAN and GSTIN are correctly linked.' }; }; @@ -207,7 +183,7 @@ useEffect(() => { }; const verifyPanHandler = async () => { - setPanVerifying(true); // Start loading + setPanVerifying(true); const payload = { dob: formData?.dateOfIncorp.trim(), name: formData?.orgPanName.trim(), @@ -220,7 +196,7 @@ useEffect(() => { } else { setPanVerified(false); } - setPanVerifying(false); // Stop loading + setPanVerifying(false); }; const sendAdharOTPHandler = async (text: string) => { @@ -300,7 +276,7 @@ useEffect(() => { const handleSubmit = async () => { setLoading(true); - + const dtoData = { agentAddress: formData.address, agentCity: formData.city, @@ -320,7 +296,7 @@ useEffect(() => { orgPAN: formData.orgPanNumber, comAadharNo: formData.aadhaarNumber, gistnNo: formData.gstnNo, - stateName: MasterData?.data?.states?.find((state:any)=>state.stateId==formData.state)?.stateName || '', + stateName: MasterData?.data?.states?.find((state: any) => state.stateId == formData.state)?.stateName || '', }; try { @@ -333,13 +309,12 @@ useEffect(() => { fileData.append('pancardfile', documents.panCard as any); fileData.append('regfile', documents.registrationCertificate as any); - const res = await contractorStageTwoSubmit(fileData); - - if (res.status === 200) { + if (res.statusCode === 200) { if (res) { + showToast('Form submitted successfully!'); - + const orgType = selectedOrg; const params = { otherId: res?.data?.contOtherId, orgType: orgType }; if ([4, 5, 6].includes(orgType)) { @@ -357,7 +332,6 @@ useEffect(() => { } } catch (error: any) { if (axios.isAxiosError(error)) { - } else { console.error('Unexpected error', error); } @@ -369,24 +343,20 @@ useEffect(() => { }; - const fetchCity = async (stateId: number) => { cityDropdownMutate(stateId, { - onSuccess: (data) => { - setCities(data); - }, - onError: (error) => { - setCities([]); - console.error('Error in city fetch:', error); - } + onSuccess: (data) => { + setCities(data); + }, + onError: (error) => { + setCities([]); + console.error('Error in city fetch:', error); + } }); - }; - console.log('formdata : ', formData); - useEffect(() => { if (formData?.state) { fetchCity(Number(formData.state)); @@ -502,15 +472,31 @@ useEffect(() => { } }; + // ── Stage Titles ── + const stageTitles = [ + 'Personal & Contact', + 'Registration Details', + 'KYC Verification', + 'Address Details', + 'Upload Documents', + ]; + return ( - - + + - - + + {/* ═══ Back Button ═══ */} + { if (stage > 0) { @@ -518,12 +504,39 @@ useEffect(() => { } }} style={styles.backBtn} + activeOpacity={0.7} > - {/* Replaced AntDesign with Lucide ArrowLeft */} - + - + + Registration + + + + {/* ═══ Progress Bar ═══ */} + + + {/* ═══ Stage Title ═══ + + + {stageTitles[stage]} + + + Step {stage + 1} of {steps.length} + + */} + + {/* ═══ Stage Content ═══ */} + {stage === 0 && ( { allMsmeOrgs={allMsmeOrgs} allSocialCategories={allSocialCategories} allMSME={allMSME} - dropdownStyle={dropdownStyle} + dropdownStyle={{}} setSelectedOrg={setSelectedOrg} selectedOrg={selectedOrg} /> @@ -594,8 +607,9 @@ useEffect(() => { states={states} cities={cities} fetchCity={() => { - fetchCity(Number(formData.state))}} - dropdownStyle={dropdownStyle} + fetchCity(Number(formData.state)); + }} + dropdownStyle={{}} styles={styles} /> )} @@ -609,10 +623,14 @@ useEffect(() => { selectedOrg={selectedOrg} /> )} - + + + {/* ═══ Next / Submit Button ═══ */} + { if (validateStage(stage)) { switch (stage) { @@ -657,104 +675,105 @@ useEffect(() => { } }} /> - - + + + ); }; export default Registration; -const styles = StyleSheet.create({ +// ── Styles ──────────────────────────────────────────────────────────── +const makeStyles = (colors: any, spacing: any, shadows: any, typography: any) => StyleSheet.create({ scrollContainer: { flexGrow: 1, - paddingHorizontal:14 + paddingHorizontal: spacing.m, }, + headerRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingVertical: spacing.s, + }, + backBtn: { + width: 36, + height: 36, + borderRadius: 10, + backgroundColor: colors.surface, + justifyContent: 'center', + alignItems: 'center', + borderWidth: 1, + borderColor: colors.border, + }, + stageTitleContainer: { + marginBottom: spacing.m, + paddingHorizontal: spacing.xs, + }, + stageTitle: { + fontWeight: '800', + marginBottom: 2, + }, + stageCard: { + backgroundColor: colors.surface, + borderRadius: 16, + padding: spacing.m, + }, + // These styles will be passed to child stages that still use `styles` prop label: { - fontSize: 15, - fontWeight: '600', - marginVertical: 10, - color: colors.black, + fontSize: typography.size.s, + fontWeight: '700', + color: colors.primary, + marginBottom: spacing.s, + letterSpacing: 0.3, + textTransform: 'uppercase', }, row: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 10, }, - backBtn: { - padding: 5, - width: 50, - marginBottom: 5, - }, sectionTitle: { - fontSize: 24, + fontSize: typography.size.l, fontWeight: '700', - color: colors.black, - marginBottom: 20, - marginTop: 10, + color: colors.text, + marginBottom: spacing.m, textAlign: 'center', }, uploadGrid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', + gap: spacing.s, }, uploadCard: { width: '48%', - backgroundColor: '#fdfdfd', - borderRadius: 20, - padding: 20, + backgroundColor: colors.surface, + borderRadius: 16, + padding: spacing.m, alignItems: 'center', - marginBottom: 20, - shadowColor: '#000', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.08, - shadowRadius: 6, - elevation: 4, borderWidth: 1, - borderColor: '#ddd', + borderColor: colors.border, }, iconCircle: { - width: 50, - height: 50, - borderRadius: 25, + width: 46, + height: 46, + borderRadius: 23, backgroundColor: colors.primary, justifyContent: 'center', alignItems: 'center', - marginBottom: 10, + marginBottom: spacing.s, }, cardTitle: { - fontSize: 15, + fontSize: typography.size.s, fontWeight: '600', - color: colors.black, + color: colors.text, marginBottom: 4, textAlign: 'center', }, cardHint: { - fontSize: 12, - color: '#888', + fontSize: typography.size.xs, + color: colors.textSecondary, textAlign: 'center', }, - dropdown: { - height: 55, - borderColor: '#e0e0e0', - borderWidth: 1, - borderRadius: 12, - paddingHorizontal: 14, - marginTop: 8, - marginBottom: 14, - backgroundColor: '#fff', - }, - selectedTextStyle: { - fontSize: 14, - color: colors.black, - }, - placeholderStyle: { - fontSize: 14, - color: colors.black, - }, - itemTextStyle: { - fontSize: 14, - color: colors.black, - }, }); diff --git a/src/Screens/Ragistration/StageThree.tsx b/src/Screens/Ragistration/StageThree.tsx index 36719e8..56db2a6 100644 --- a/src/Screens/Ragistration/StageThree.tsx +++ b/src/Screens/Ragistration/StageThree.tsx @@ -3,24 +3,21 @@ import { SafeAreaView, ScrollView, StyleSheet, - Text, TouchableOpacity, View, Platform, } from 'react-native'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; - - - -import colors from '../../Constant/Color'; import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton'; -import MyInput from '../../Component/MyInput'; +import MyInput from '../../components/MyInput'; import { showToast } from '../../Constant/ShowDailog'; -import { STAGETHREE } from '../../Services/ApiCall/ApiUrls'; import { useContractorStageThreeSubmit } from '../../Services/ApiCall/Hooks'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; - +import Animated, { FadeInUp } from 'react-native-reanimated'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; +import { Trash2, Building2 } from 'lucide-react-native'; type Company = { director: string; @@ -32,13 +29,16 @@ type Company = { type StageThreeRouteParams = { orgType: string; - otherId: string | number + otherId: string | number; }; const StageThreeScreen = () => { const route = useRoute>(); const navigation = useNavigation(); - const insets = useSafeAreaInsets() + const { colors, spacing, typography, shadows } = useTheme(); + const insets = useSafeAreaInsets(); + const styles = useMemo(() => makeStyles(colors, spacing, shadows, typography), [colors, spacing, shadows, typography]); + const [company, setCompany] = useState([ { director: '', @@ -52,7 +52,7 @@ const StageThreeScreen = () => { const [focusedField, setFocusedField] = useState(null); const [loading, setLoading] = useState(false); const { mutateAsync: stageThreeSubmit } = useContractorStageThreeSubmit(); - + const handleChange = (index: number, field: keyof Company, value: string) => { const updated = [...company]; updated[index][field] = value; @@ -72,7 +72,6 @@ const StageThreeScreen = () => { ]); }; - const handleSubmit = async () => { setLoading(true); try { @@ -119,140 +118,179 @@ const StageThreeScreen = () => { ); }; + const orgLabel = + route.params?.orgType == '4' + ? 'Public' + : route.params?.orgType == '5' + ? 'Private' + : route.params?.orgType == '6' + ? 'LLP' + : route.params?.orgType; + return ( - - Stage Three For {route.params?.orgType == '4' ? 'Public' : route.params?.orgType == '5' ? 'Private' : route.params?.orgType == '6' ? 'Limited Liability Partnership (LLP)' : route.params?.orgType} Organization + + + + {orgLabel} Organization + + + Add directors / shareholders for your organization + + + - + {company.map((partner, index) => ( - - Company {index + 1} + + + + Director {index + 1} + + {company.length > 1 && ( + handleRemove(index)} activeOpacity={0.7}> + + + )} + handleChange(index, 'director', text)} + onChangeText={(text: any) => handleChange(index, 'director', text)} onFocus={() => setFocusedField(`director-${index}`)} onBlur={() => setFocusedField(null)} focused={focusedField === `director-${index}`} + containerStyle={{ marginBottom: spacing.s }} /> handleChange(index, 'DINNumber', text)} + onChangeText={(text: any) => handleChange(index, 'DINNumber', text)} onFocus={() => setFocusedField(`DINNumber-${index}`)} onBlur={() => setFocusedField(null)} focused={focusedField === `DINNumber-${index}`} keyboardType="numeric" - maxlength={8} + maxLength={8} + containerStyle={{ marginBottom: spacing.s }} /> handleChange(index, 'panNumber', text)} + onChangeText={(text: any) => handleChange(index, 'panNumber', text)} onFocus={() => setFocusedField(`panNumber-${index}`)} onBlur={() => setFocusedField(null)} focused={focusedField === `panNumber-${index}`} - maxlength={12} + maxLength={12} autoCapitalize="characters" + containerStyle={{ marginBottom: spacing.s }} /> handleChange(index, 'aadharNumber', text)} + onChangeText={(text: any) => handleChange(index, 'aadharNumber', text)} onFocus={() => setFocusedField(`aadharNumber-${index}`)} onBlur={() => setFocusedField(null)} focused={focusedField === `aadharNumber-${index}`} keyboardType="numeric" - maxlength={12} + maxLength={12} + containerStyle={{ marginBottom: spacing.s }} /> handleChange(index, 'mobileNumber', text)} + onChangeText={(text: any) => handleChange(index, 'mobileNumber', text)} onFocus={() => setFocusedField(`mobileNumber-${index}`)} onBlur={() => setFocusedField(null)} focused={focusedField === `mobileNumber-${index}`} keyboardType="phone-pad" - maxlength={10} + maxLength={10} /> - - {company.length > 1 && ( - handleRemove(index)}> - Delete Company - - )} - + ))} - - + Add Company + + + + Add Director + - + + + + ); }; export default StageThreeScreen; -const styles = StyleSheet.create({ - heading: { - fontSize: 18, - fontWeight: 'bold', - marginBottom: 10, - marginTop: 10, - textAlign: 'center', - }, - partnerTitle: { - fontWeight: '600', - fontSize: 16, - marginBottom: 10, +const makeStyles = (colors: any, spacing: any, shadows: any, typography: any) => StyleSheet.create({ + header: { + paddingHorizontal: spacing.m, + paddingBottom: spacing.m, + borderBottomWidth: 1, + borderBottomColor: colors.border, + backgroundColor: colors.surface, }, scrollContainer: { - flexGrow: 1, + padding: spacing.m, + paddingBottom: 30, + }, + card: { + backgroundColor: colors.surface, + borderRadius: 14, + padding: 14, + marginBottom: spacing.m, + }, + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: spacing.s, + paddingBottom: spacing.xs, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, }, addButton: { backgroundColor: colors.primary, - padding: 10, - marginTop: 10, - borderRadius: 10, + flexDirection: 'row', alignItems: 'center', - width: 140, + justifyContent: 'center', + paddingVertical: 12, + paddingHorizontal: 20, + borderRadius: 12, alignSelf: 'flex-end', }, - addText: { - color: 'white', - fontWeight: '600', - }, - deleteButton: { - backgroundColor: '#FF5C5C', - padding: 10, - marginTop: 10, - borderRadius: 10, - alignItems: 'center', - width: 140, - alignSelf: 'flex-end', - }, - deleteText: { - color: 'white', - fontWeight: '600', + footer: { + padding: spacing.m, + borderTopWidth: 1, + borderTopColor: colors.border, + backgroundColor: colors.surface, }, }); diff --git a/src/Screens/Ragistration/ragistrationStages/StageFour.tsx b/src/Screens/Ragistration/ragistrationStages/StageFour.tsx index ea28097..f1265c4 100644 --- a/src/Screens/Ragistration/ragistrationStages/StageFour.tsx +++ b/src/Screens/Ragistration/ragistrationStages/StageFour.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { View, Text, TouchableOpacity } from 'react-native'; +import { View, TouchableOpacity } from 'react-native'; import { IdCard, FileText, @@ -7,7 +7,12 @@ import { ShieldCheck, Building2, ScrollText, -} from 'lucide-react-native'; // lucide icons + CheckCircle, + Upload, +} from 'lucide-react-native'; +import Animated, { FadeInUp } from 'react-native-reanimated'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; interface DocumentItem { key: string; @@ -30,13 +35,15 @@ const StageFour: React.FC = ({ formData, selectedOrg, }) => { + const { colors, spacing, shadows } = useTheme(); + const documentItems: DocumentItem[] = [ { key: 'panCard', label: 'PAN Card', icon: 'pan' }, { key: 'aadharCard', label: 'Aadhar Card', icon: 'aadhar' }, { key: 'gstnCertificate', label: 'GSTN Certificate', icon: 'gst' }, - { key: 'constractorClass', label: 'Contractor Class File', icon: 'contractor' }, + { key: 'constractorClass', label: 'Contractor Class', icon: 'contractor' }, { key: 'msmefile', label: 'MSME Organization', icon: 'msme' }, - { key: 'registrationCertificate', label: 'Registration Certificate', icon: 'registration' }, + { key: 'registrationCertificate', label: 'Registration Cert.', icon: 'registration' }, ]; const shouldRenderItem = (itemKey: string): boolean => { @@ -52,54 +59,84 @@ const StageFour: React.FC = ({ return false; } - return true; // pan + aadhar + rest (unless filtered above) + return true; }; const renderIcon = (iconType: DocumentItem['icon']) => { - const commonProps = { size: 26, color: '#fff', strokeWidth: 2 }; + const commonProps = { size: 24, color: colors.buttonText, strokeWidth: 2 }; switch (iconType) { case 'pan': - return ; // PAN → ID card feel [web:19] + return ; case 'aadhar': - return ; // Same icon, different label + return ; case 'gst': - return ; // File with badge [web:20] + return ; case 'contractor': - return ; // Safety / verification feel + return ; case 'msme': - return ; // Organization / company + return ; case 'registration': - return ; // Certificate / document scroll + return ; default: return ; } }; + let animIndex = 0; + return ( <> - Upload Required Documents + + Upload Documents + - + {documentItems.map(item => { if (!shouldRenderItem(item.key)) return null; + const isUploaded = !!documents[item.key]; + const currentIndex = animIndex++; return ( - handleDocumentPick(item.key)} + entering={FadeInUp.delay(80 * currentIndex).springify()} + style={{ width: '50%', padding: spacing.xs }} > - {renderIcon(item.icon)} + handleDocumentPick(item.key)} + activeOpacity={0.7} + > + + {isUploaded ? ( + + ) : ( + renderIcon(item.icon) + )} + - {item.label} + + {item.label} + - - {documents[item.key] - ? `${documents[item.key]?.name} ✅` - : 'Tap to upload PDF'} - - + + {isUploaded + ? `${documents[item.key]?.name?.slice(0, 18)}...` + : 'Tap to upload PDF'} + + + ); })} diff --git a/src/Screens/Ragistration/ragistrationStages/StageOne.tsx b/src/Screens/Ragistration/ragistrationStages/StageOne.tsx index e7fd6b7..1477b2b 100644 --- a/src/Screens/Ragistration/ragistrationStages/StageOne.tsx +++ b/src/Screens/Ragistration/ragistrationStages/StageOne.tsx @@ -1,11 +1,11 @@ import React, { useState } from 'react'; -import { Text, View, StyleSheet } from 'react-native'; - -import DatePicker from 'react-native-date-picker' -import MyInput from '../../../Component/MyInput'; +import { View } from 'react-native'; +import DatePicker from 'react-native-date-picker'; +import MyInput from '../../../components/MyInput'; import MyDropdown from '../../../Component/Dropdown/MyDropdown'; -import colors from '../../../Constant/Color'; import { DropdownOption } from '../types/types'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; interface Props { formData: { @@ -14,7 +14,7 @@ interface Props { orgType: string; registrationDetail: string | number; socialCategory: string; - expiryDate: string + expiryDate: string; }; handleChange: (key: string, value: string) => void; focusedField: string | null; @@ -26,7 +26,7 @@ interface Props { allMSME: DropdownOption[]; dropdownStyle: object; setSelectedOrg: (val: number) => void; - selectedOrg: string | number + selectedOrg: string | number; } const StageOne: React.FC = ({ @@ -40,23 +40,24 @@ const StageOne: React.FC = ({ allSocialCategories, dropdownStyle, setSelectedOrg, - selectedOrg + selectedOrg, }) => { - console.log("masterdata",allOrganizations,allMsmeOrgs,allSocialCategories,contractorClassItems,selectedOrg !== 8) - - const [selectedAllMSME, setSelectedAllMSME] = useState() - const [open, setOpen] = useState(false) - const [date, setDate] = useState(new Date()) + const { colors, spacing } = useTheme(); + const [selectedAllMSME, setSelectedAllMSME] = useState(); + const [open, setOpen] = useState(false); + const [date, setDate] = useState(new Date()); const handleDateConfirm = (selectedDate: Date) => { setOpen(false); setDate(selectedDate); - handleChange('expiryDate', selectedDate.toISOString().split('T')[0]); // Format as YYYY-MM-DD + handleChange('expiryDate', selectedDate.toISOString().split('T')[0]); }; + return ( - Registration & Type Details - + + Registration & Type Details + = ({ setSelectedOrg(item.value); handleChange('orgType', item.value); }} - style={dropdownStyle} onFocus={() => setFocusedField('orgType')} onBlur={() => setFocusedField(null)} focusedField={focusedField === 'orgType'} /> - { - selectedOrg !== 8 && + + {selectedOrg !== 8 && ( handleChange('registrationNumber', text)} onFocus={() => setFocusedField('registrationNumber')} onBlur={() => setFocusedField(null)} focused={focusedField === 'registrationNumber'} - autoCapitalize='characters' /> - } - {selectedOrg && + autoCapitalize="characters" + containerStyle={{ marginBottom: spacing.s }} + /> + )} + + {selectedOrg && ( = ({ valueField="value" placeholder="Select Class" value={selectedOrg == 8 ? contractorClassItems[0]?.value : formData.contractorClass} - style={dropdownStyle} disable={selectedOrg == 8 ? true : false} onChange={item => { handleChange('contractorClass', item.value); @@ -102,9 +104,10 @@ const StageOne: React.FC = ({ onFocus={() => setFocusedField('contractorClass')} onBlur={() => setFocusedField(null)} focusedField={focusedField === 'contractorClass'} - />} + /> + )} - {selectedOrg !== 8 && + {selectedOrg !== 8 && ( = ({ placeholder="Select Registration Type" value={formData.registrationDetail} onChange={item => { - setSelectedAllMSME(item.value) - console.log('first', item) - handleChange('registrationDetail', item.value) + setSelectedAllMSME(item.value); + handleChange('registrationDetail', item.value); }} - style={dropdownStyle} onFocus={() => setFocusedField('registrationDetail')} onBlur={() => setFocusedField(null)} focusedField={focusedField === 'registrationDetail'} - />} - {/* { - setSelectedAllMSME(item.value) - handleChange('allMsme', item.value) - }} - style={dropdownStyle} + /> + )} - onFocus={() => setFocusedField('allMsme')} - onBlur={() => setFocusedField(null)} - focusedField={focusedField === 'allMsme'} - /> */} = ({ placeholder="Select Social Category" value={formData.socialCategory} onChange={item => handleChange('socialCategory', item.value)} - style={dropdownStyle} - onFocus={() => setFocusedField('socialCategory')} onBlur={() => setFocusedField(null)} focusedField={focusedField === 'socialCategory'} /> - { - selectedAllMSME !== 3 && selectedOrg !== 8 && - <> + {selectedAllMSME !== 3 && selectedOrg !== 8 && ( + <> handleChange('expiryDate', text)} onFocus={() => setFocusedField('expiryDate')} onBlur={() => setFocusedField(null)} focused={focusedField === 'expiryDate'} - rightIcon='wallet' editable={true} rightIconPress={() => setOpen(true)} /> @@ -175,22 +157,12 @@ const StageOne: React.FC = ({ date={date} onConfirm={handleDateConfirm} onCancel={() => setOpen(false)} - mode="date" // Ensure you're using date mode + mode="date" /> - - } + )} ); }; -const styles = StyleSheet.create({ - label: { - fontSize: 16, - fontWeight: '600', - color: colors.black, - marginBottom: 10, - }, -}); - export default StageOne; diff --git a/src/Screens/Ragistration/ragistrationStages/StageThree.tsx b/src/Screens/Ragistration/ragistrationStages/StageThree.tsx index f94a2a0..78d9613 100644 --- a/src/Screens/Ragistration/ragistrationStages/StageThree.tsx +++ b/src/Screens/Ragistration/ragistrationStages/StageThree.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { Text } from 'react-native'; +import { View } from 'react-native'; import { DropdownOption } from '../types/types'; -import MyInput from '../../../Component/MyInput'; +import MyInput from '../../../components/MyInput'; import MyDropdown from '../../../Component/Dropdown/MyDropdown'; - - +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; interface AddressDetailsSectionProps { formData: { @@ -21,7 +21,7 @@ interface AddressDetailsSectionProps { cities: DropdownOption[]; fetchCity: (stateValue: number) => void; dropdownStyle?: object; - styles?: any; // Optional styles prop for custom styling + styles?: any; } const StageThree: React.FC = ({ @@ -32,54 +32,51 @@ const StageThree: React.FC = ({ states, cities, fetchCity, - styles + styles, }) => { + const { colors, spacing } = useTheme(); - const dropdownStyle = { - borderWidth: 1, - borderColor: '#e0e0e0', - borderRadius: 12, - paddingHorizontal: 14, - height: 50, - backgroundColor: '#fff', - marginBottom: 10, - }; return ( <> - Address Details + + Address Details + handleChange('address', text)} onFocus={() => setFocusedField('address')} onBlur={() => setFocusedField(null)} focused={focusedField === 'address'} + containerStyle={{ marginBottom: spacing.s }} /> handleChange('agentContact2', text)} onFocus={() => setFocusedField('agentContact2')} onBlur={() => setFocusedField(null)} focused={focusedField === 'agentContact2'} keyboardType="numeric" + containerStyle={{ marginBottom: spacing.s }} /> handleChange('pinCode', text)} onFocus={() => setFocusedField('pinCode')} onBlur={() => setFocusedField(null)} focused={focusedField === 'pinCode'} + containerStyle={{ marginBottom: spacing.s }} /> = ({ handleChange('city', ''); fetchCity(item.value); }} - style={dropdownStyle} onFocus={() => setFocusedField('state')} onBlur={() => setFocusedField(null)} focusedField={focusedField === 'state'} @@ -108,7 +104,6 @@ const StageThree: React.FC = ({ placeholder="Select City" value={formData.city} onChange={item => handleChange('city', item.value)} - style={dropdownStyle} onFocus={() => setFocusedField('city')} onBlur={() => setFocusedField(null)} focusedField={focusedField === 'city'} diff --git a/src/Screens/Ragistration/ragistrationStages/StageTwo.tsx b/src/Screens/Ragistration/ragistrationStages/StageTwo.tsx index 3536972..56dbd09 100644 --- a/src/Screens/Ragistration/ragistrationStages/StageTwo.tsx +++ b/src/Screens/Ragistration/ragistrationStages/StageTwo.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { - Text, TouchableOpacity, ActivityIndicator, View, + Alert, } from 'react-native'; -import MyInput from '../../../Component/MyInput'; - -// Adjust the path to your custom input +import MyInput from '../../../components/MyInput'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; interface Props { formData: { @@ -21,12 +21,10 @@ interface Props { handleChange: (key: string, value: string) => void; focusedField: string | null; setFocusedField: (field: string | null | any) => void; - // PAN panVerified: boolean; setPanVerified: (value: boolean) => void; verifyPanHandler: () => void; panVerifying: boolean; - // Aadhaar aadhaarVerified: boolean; setAadhaarVerified: (value: boolean) => void; sendAdharOTPHandler: (aadhaarNumber: string) => void; @@ -34,13 +32,12 @@ interface Props { setadharotpsend: (value: boolean) => void; verifyAdharOtpHandler: () => void; aadhaarVerifying: boolean; - // GST verifyGSTHandler: () => void; gstverifying: boolean; color: any; styles: any; formatDateInput: (text: string) => string; - selectedOrg:string|number + selectedOrg: string | number; } const StageTwo: React.FC = ({ @@ -64,61 +61,79 @@ const StageTwo: React.FC = ({ color, styles, formatDateInput, - selectedOrg + selectedOrg, }) => { + const { colors, spacing, shadows } = useTheme(); + + const verifyButtonStyle = { + backgroundColor: colors.primary, + paddingVertical: 10, + paddingHorizontal: 16, + marginTop: spacing.s, + borderRadius: 10, + alignItems: 'center' as const, + alignSelf: 'flex-end' as const, + minWidth: 120, + }; + return ( <> - {/* ---------- PAN Details ---------- */} - PAN Details + {/* ── PAN Details ── */} + + PAN Details + { setPanVerified(false); handleChange('orgPanNumber', text); }} - isVarify={panVerified} + isVerify={panVerified} onFocus={() => setFocusedField('pan')} onBlur={() => setFocusedField(null)} focused={focusedField === 'pan'} editable={!panVerified} autoCapitalize="characters" - maxlength={10} + maxLength={10} + containerStyle={{ marginBottom: spacing.s }} /> { const formatted = formatDateInput(text); setPanVerified(false); handleChange('dateOfIncorp', formatted); }} - isVarify={panVerified} + isVerify={panVerified} onFocus={() => setFocusedField('dob')} onBlur={() => setFocusedField(null)} focused={focusedField === 'dob'} editable={!panVerified} - keyboardType='numeric' + keyboardType="numeric" + containerStyle={{ marginBottom: spacing.s }} /> { setPanVerified(false); handleChange('orgPanName', text); }} - isVarify={panVerified} + isVerify={panVerified} onFocus={() => setFocusedField('panName')} onBlur={() => setFocusedField(null)} focused={focusedField === 'panName'} editable={!panVerified} autoCapitalize="characters" + containerStyle={{ marginBottom: spacing.s }} /> {formData.orgPanNumber && @@ -126,139 +141,187 @@ const StageTwo: React.FC = ({ formData.orgPanName && !panVerified && ( - - {panVerifying ? 'Verifying' : 'Verify PAN'} - + + {panVerifying ? 'Verifying...' : 'Verify PAN'} + )} - {/* ---------- Aadhaar Details ---------- */} - Aadhaar Details - - { - handleChange('aadhaarNumber', text); - setAadhaarVerified(false); - setadharotpsend(false); - handleChange('aadhaarOtp', ''); - }} - isVarify={aadhaarVerified} - onFocus={() => setFocusedField('adhar')} - onBlur={() => setFocusedField(null)} - focused={focusedField === 'adhar'} - editable={!aadhaarVerified} - /> - - {formData.aadhaarNumber.length === 12 && !adharotpsend && ( + {panVerified && ( sendAdharOTPHandler(formData.aadhaarNumber)} - disabled={aadhaarVerifying} + onPress={() => { + Alert.alert( + 'Change PAN', + 'Are you sure you want to change your PAN details? You will need to re-verify.', + [ + { text: 'Cancel', style: 'cancel' }, + { + text: 'Change', + style: 'destructive', + onPress: () => { + setPanVerified(false); + handleChange('orgPanNumber', ''); + handleChange('dateOfIncorp', ''); + handleChange('orgPanName', ''); + }, + }, + ], + ); + }} + activeOpacity={0.8} > - {aadhaarVerifying ? ( - - ) : ( - Send OTP - )} + + Change PAN + )} - {adharotpsend && !aadhaarVerified && ( - <> - handleChange('aadhaarOtp', text)} - onFocus={() => setFocusedField('adharOtp')} - onBlur={() => setFocusedField(null)} - focused={focusedField === 'adharOtp'} - /> + {/* ── Aadhaar Details ── */} + + + Aadhaar Details + + + { + handleChange('aadhaarNumber', text); + setAadhaarVerified(false); + setadharotpsend(false); + handleChange('aadhaarOtp', ''); + }} + isVerify={aadhaarVerified} + onFocus={() => setFocusedField('adhar')} + onBlur={() => setFocusedField(null)} + focused={focusedField === 'adhar'} + editable={!aadhaarVerified} + containerStyle={{ marginBottom: spacing.s }} + /> + + {formData.aadhaarNumber.length === 12 && !adharotpsend && ( + sendAdharOTPHandler(formData.aadhaarNumber)} + disabled={aadhaarVerifying} + activeOpacity={0.8} + > + {aadhaarVerifying ? ( + + ) : ( + + Send OTP + + )} + + )} + + {adharotpsend && !aadhaarVerified && ( + <> + handleChange('aadhaarOtp', text)} + onFocus={() => setFocusedField('adharOtp')} + onBlur={() => setFocusedField(null)} + focused={focusedField === 'adharOtp'} + containerStyle={{ marginBottom: spacing.s }} + /> + + + {aadhaarVerifying ? 'Verifying...' : 'Verify OTP'} + + + + )} + + {aadhaarVerified && ( { + Alert.alert( + 'Change Aadhaar', + 'Are you sure you want to change your Aadhaar details? You will need to re-verify.', + [ + { text: 'Cancel', style: 'cancel' }, + { + text: 'Change', + style: 'destructive', + onPress: () => { + setAadhaarVerified(false); + setadharotpsend(false); + handleChange('aadhaarNumber', ''); + handleChange('aadhaarOtp', ''); + }, + }, + ], + ); + }} + activeOpacity={0.8} > - - {aadhaarVerifying ? 'Verifying...' : 'Verify'} - + + Change Aadhaar + - + )} + + + {/* ── GST Section ── */} + {selectedOrg !== 11 && ( + + handleChange('gstnNo', text)} + onFocus={() => setFocusedField('gstNumber')} + onBlur={() => setFocusedField(null)} + focused={focusedField === 'gstNumber'} + autoCapitalize="characters" + /> + )} - - {/* ---------- GST Section ---------- */} - - {selectedOrg !== 11 && - <> - handleChange('gstnNo', text)} - onFocus={() => setFocusedField('gstNumber')} - onBlur={() => setFocusedField(null)} - focused={focusedField === 'gstNumber'} - autoCapitalize="characters" - /> - - {/* {formData?.gstnNo?.length === 15 && ( - - - {gstverifying ? 'Verifying...' : 'Verify GST'} - - - )} */} - } ); }; export default StageTwo; - - diff --git a/src/Screens/Ragistration/ragistrationStages/StageZero.tsx b/src/Screens/Ragistration/ragistrationStages/StageZero.tsx index ff73963..25cba07 100644 --- a/src/Screens/Ragistration/ragistrationStages/StageZero.tsx +++ b/src/Screens/Ragistration/ragistrationStages/StageZero.tsx @@ -1,12 +1,12 @@ -import React, { useEffect } from 'react'; -import { Text, View, StyleSheet } from 'react-native'; -import MyInput from '../../../Component/MyInput'; - +import React from 'react'; +import { View } from 'react-native'; +import MyInput from '../../../components/MyInput'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; interface Props { formData: { - agentContact2: string; organizationName: string; officialWebsite: string; @@ -14,7 +14,7 @@ interface Props { email: string; firstName: string; middleName: string; - lastName: string + lastName: string; }; handleChange: (key: string, value: string) => void; focusedField: string | null; @@ -29,52 +29,55 @@ const StageZero: React.FC = ({ setFocusedField, styles, }) => { + const { colors, spacing } = useTheme(); return ( <> - Personal & Contact Details - {/* */} + + Personal & Contact Details + + handleChange('firstName', text)} onFocus={() => setFocusedField('firstname')} onBlur={() => setFocusedField(null)} focused={focusedField === 'firstname'} editable={false} + containerStyle={{ marginBottom: spacing.s }} /> - - { - formData.middleName ? - - handleChange('middleName', text)} - onFocus={() => setFocusedField('middleName')} - onBlur={() => setFocusedField(null)} - focused={focusedField === 'middleName'} - editable={false} - /> - : null} + {formData.middleName ? ( + handleChange('middleName', text)} + onFocus={() => setFocusedField('middleName')} + onBlur={() => setFocusedField(null)} + focused={focusedField === 'middleName'} + editable={false} + containerStyle={{ marginBottom: spacing.s }} + /> + ) : null} handleChange('lastName', text)} onFocus={() => setFocusedField('lastName')} onBlur={() => setFocusedField(null)} focused={focusedField === 'lastName'} editable={false} + containerStyle={{ marginBottom: spacing.s }} /> handleChange('email', text)} @@ -82,11 +85,12 @@ const StageZero: React.FC = ({ onBlur={() => setFocusedField(null)} focused={focusedField === 'email'} editable={false} + containerStyle={{ marginBottom: spacing.s }} /> handleChange('mobileNumber', text)} @@ -94,22 +98,24 @@ const StageZero: React.FC = ({ onBlur={() => setFocusedField(null)} focused={focusedField === 'mobileNumber'} editable={false} + containerStyle={{ marginBottom: spacing.s }} /> handleChange('organizationName', text)} onFocus={() => setFocusedField('organizationName')} onBlur={() => setFocusedField(null)} focused={focusedField === 'organizationName'} autoCapitalize="none" + containerStyle={{ marginBottom: spacing.s }} /> handleChange('officialWebsite', text)} onFocus={() => setFocusedField('officialWebsite')} diff --git a/src/Screens/SignUp/SignUp.tsx b/src/Screens/SignUp/SignUp.tsx index 82f2d7a..6c00b0d 100644 --- a/src/Screens/SignUp/SignUp.tsx +++ b/src/Screens/SignUp/SignUp.tsx @@ -1,9 +1,7 @@ import { useNavigation } from '@react-navigation/native'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { View, - Text, - TextInput, TouchableOpacity, StyleSheet, Alert, @@ -13,16 +11,18 @@ import { ScrollView, ToastAndroid, } from 'react-native'; -import colors from '../../Constant/Color'; -import { User, Phone, Mail, Lock, Eye, EyeOff, ArrowLeft, CheckSquare, Square } from 'lucide-react-native'; +import { User, Phone, Mail, Lock, CheckSquare, Square, Shield, UserPlus, Contact, Eye, EyeOff } from 'lucide-react-native'; import { useGetDesignation, useRegisterContractor } from '../../Services/ApiCall/Hooks'; import MyDropdown from '../../Component/Dropdown/MyDropdown'; -import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton'; +import PrimaryButton from '../../components/PrimaryButton'; +import MyInput from '../../components/MyInput'; +import InsideHeader from '../../Component/Header/InsideHeader'; import TermsAndConditionsModal from '../../Component/TermsAndConditionsModal/TermsAndConditionsModal'; +import AppText from '../../components/AppText'; import { Formik } from 'formik'; import * as Yup from 'yup'; -import Animated, { FadeInUp } from 'react-native-reanimated'; -import { InsideHeader } from '../../Component/Index'; +import Animated, { FadeInUp, FadeInDown } from 'react-native-reanimated'; +import { useTheme } from '../../Hooks/useTheme'; export type ContractorFormData = { firstName: string; @@ -40,7 +40,7 @@ export type ContractorFormData = { type DesignationOption = { label: string; - value: string; // string id from API + value: string; }; const SignupSchema = Yup.object().shape({ @@ -48,7 +48,7 @@ const SignupSchema = Yup.object().shape({ firstName: Yup.string().required('First name is required.'), middleName: Yup.string().optional(), lastName: Yup.string().required('Last name is required.'), - designation: Yup.string().required('Please select a designation.'), // string + designation: Yup.string().required('Please select a designation.'), mobileNumber: Yup.string() .matches(/^[6-9]\d{9}$/, 'Invalid mobile number.') .required('Mobile number is required.'), @@ -65,6 +65,7 @@ const SignupSchema = Yup.object().shape({ }); const Signup = () => { + const { colors, spacing, typography, shadows } = useTheme(); const navigation = useNavigation(); const [designation, setDesignation] = useState([]); const { data, isLoading: isDesignationLoading } = useGetDesignation(); @@ -74,6 +75,7 @@ const Signup = () => { const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); + const styles = useMemo(() => makeStyles(colors, spacing, shadows, typography), [colors, spacing, shadows, typography]); useEffect(() => { const list = (data as any)?.data; @@ -81,89 +83,15 @@ const Signup = () => { const formattedDesignation: DesignationOption[] = list.map((item: any) => ({ label: item.designationName, - value: String(item.designationid), // e.g. "3" + value: String(item.designationid), })); setDesignation(formattedDesignation); }, [data]); - - const dropdownStyle = { - borderWidth: 1, - borderColor: colors.border, - borderRadius: 12, - paddingHorizontal: 14, - height: 50, - backgroundColor: colors.white, - marginBottom: 10, - }; - - const renderInputWithIcon = ( - icon: 'user' | 'phone' | 'mail', - placeholder: string, - value: string, - onChangeText: (text: string) => void, - onBlur: () => void, - error?: string, - touched?: boolean, - props: object = {}, - ) => { - const IconComponent = icon === 'user' ? User : icon === 'phone' ? Phone : Mail; - - return ( - <> - - - - - {touched && error && {error}} - - ); - }; - - const renderPasswordInput = ( - placeholder: string, - value: string, - onChangeText: (text: string) => void, - onBlur: () => void, - show: boolean, - setShow: (show: boolean) => void, - error?: string, - touched?: boolean, - ) => ( - <> - - - - setShow(!show)}> - {show ? : } - - - {touched && error && {error}} - - ); - const handleSubmitForm = (values: any) => { - console.log('values', values); - if (!isCheck) { - Alert.alert('Check terms and condition'); + Alert.alert('Terms Required', 'Please accept the terms and conditions to proceed.'); return; } @@ -175,7 +103,7 @@ const Signup = () => { mobNo: values.mobileNumber.trim(), emailAdd: values.email.trim(), contractorType: values.bidderType, - designation: values.designation ? Number(values.designation) : 0, // convert string -> number + designation: values.designation ? Number(values.designation) : 0, password: values.password.trim(), otherDesignation: null, conPassword: values.confirmPassword.trim(), @@ -187,7 +115,7 @@ const Signup = () => { ToastAndroid.show(`${res.message}`, ToastAndroid.SHORT); navigation.pop(); } else { - ToastAndroid.show(`Registration failed : ${res.message}`, ToastAndroid.SHORT); + ToastAndroid.show(`Registration failed: ${res.message}`, ToastAndroid.SHORT); } }, onError: (error: any) => { @@ -197,21 +125,21 @@ const Signup = () => { }; return ( - - - + + + { - - Bidder Type - + {/* ═══ 1. Bidder Type Toggle ═══ */} + + + + + Bidder Type + + + {['Indian', 'Foreign'].map(type => ( setFieldValue('bidderType', type)} + activeOpacity={0.8} > - - {type} - + {type} Contractor + ))} {touched.bidderType && errors.bidderType && ( - {errors.bidderType} + + {errors.bidderType} + )} - - - - {renderInputWithIcon( - 'user', - 'First Name *', - values.firstName, - handleChange('firstName'), - () => handleBlur('firstName'), - errors.firstName, - touched.firstName, - )} - - - {renderInputWithIcon( - 'user', - 'Middle Name', - values.middleName, - handleChange('middleName'), - () => handleBlur('middleName'), - errors.middleName, - touched.middleName, - )} - + + {/* ═══ 2. Personal Information ═══ */} + + + + + Personal Information + - {renderInputWithIcon( - 'user', - 'Last Name *', - values.lastName, - handleChange('lastName'), - () => handleBlur('lastName'), - errors.lastName, - touched.lastName, - )} - + } + containerStyle={{ marginBottom: spacing.s }} + /> + + + + - - Designation { - setFieldValue('designation', item.value); - }} - placeholder={isDesignationLoading ? 'Loading...' : 'Choose Designation'} - style={dropdownStyle} + onChange={(item: any) => { setFieldValue('designation', item.value); }} + placeholder={isDesignationLoading ? 'Loading...' : 'Select Designation'} /> {touched.designation && errors.designation && ( - {errors.designation} + + {errors.designation} + )} - - {renderInputWithIcon( - 'phone', - 'Mobile Number *', - values.mobileNumber, - handleChange('mobileNumber'), - () => handleBlur('mobileNumber'), - errors.mobileNumber, - touched.mobileNumber, - { - keyboardType: 'phone-pad', - maxLength: 10, - }, - )} - {renderInputWithIcon( - 'mail', - 'Email *', - values.email, - handleChange('email'), - () => handleBlur('email'), - errors.email, - touched.email, - { - keyboardType: 'email-address', - autoCapitalize: 'none', - }, - )} - - - - {renderPasswordInput( - 'Password *', - values.password, - handleChange('password'), - () => handleBlur('password'), - showPassword, - setShowPassword, - errors.password, - touched.password, - )} - - {renderPasswordInput( - 'Confirm Password *', - values.confirmPassword, - handleChange('confirmPassword'), - () => handleBlur('confirmPassword'), - showConfirmPassword, - setShowConfirmPassword, - errors.confirmPassword, - touched.confirmPassword, - )} - - - - - setCheckModal(true)}> - {isCheck ? ( - - ) : ( - - )} - - - By continuing, I agree to the Terms and Conditions - + {/* ═══ 3. Contact Details ═══ */} + + + + + Contact Details + - - - handleSubmit()} + } + containerStyle={{ marginBottom: spacing.s }} + /> + + } + containerStyle={{ marginBottom: spacing.s }} /> + + {/* ═══ 4. Security ═══ */} + + + + + Security + + + + + : + } + rightIconPress={() => setShowPassword(prev => !prev)} + containerStyle={{ marginBottom: spacing.s }} + /> + + + : + } + rightIconPress={() => setShowConfirmPassword(prev => !prev)} + containerStyle={{ marginBottom: spacing.s }} + /> + + + + {/* ═══ Terms & Conditions ═══ */} + + setCheckModal(true)} + activeOpacity={0.8} + > + {isCheck ? ( + + ) : ( + + )} + + I agree to the{' '} + + Terms and Conditions + + + + + + + {/* ═══ Submit Button ═══ */} + + + + + + {/* ═══ Login Link ═══ */} + + navigation.pop()} + style={styles.loginRow} + activeOpacity={0.7} + > + + Already have an account?{' '} + + Sign In + + + + + + {checkModal && ( setCheckModal(false)} - onChecked={() => setIsCheck(true)} + onChecked={() => { setIsCheck(true); setCheckModal(false); }} /> )} @@ -413,99 +406,85 @@ const Signup = () => { export default Signup; -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.white, - }, + +// ── Styles ──────────────────────────────────────────────────────────── +const makeStyles = (colors: any, spacing: any, shadows: any, typography: any) => StyleSheet.create({ scrollContainer: { flexGrow: 1, - paddingHorizontal: 16, - paddingTop: 12, - paddingBottom: 24, - justifyContent: 'flex-start', + padding: spacing.m, + paddingBottom: 30, }, - label: { - fontSize: 15, - fontWeight: '600', - marginTop: 6, - marginBottom: 4, - color: colors.darkText, + sectionLabelRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + marginBottom: 6, + }, + sectionLabel: { + fontWeight: '700', + fontSize: typography.size.xs, + letterSpacing: 0.5, + textTransform: 'uppercase', }, toggleContainer: { flexDirection: 'row', - justifyContent: 'space-between', - marginBottom: 4, + borderRadius: 12, + padding: 3, + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.border, + height: 46, }, toggleButton: { flex: 1, - paddingVertical: 10, - borderRadius: 10, - borderWidth: 1, - borderColor: colors.primary, + justifyContent: 'center', alignItems: 'center', - marginHorizontal: 4, - backgroundColor: colors.white, + borderRadius: 9, }, - selectedToggle: { - backgroundColor: colors.primary, - borderColor: colors.primary, + toggleButtonActive: { + elevation: 2, }, - toggleText: { - fontSize: 14, - color: colors.grayText, + card: { + backgroundColor: colors.surface, + borderRadius: 14, + padding: 14, + marginBottom: spacing.m, }, - selectedToggleText: { - color: colors.white, - fontWeight: '600', - }, - row: { - flexDirection: 'row', - justifyContent: 'space-between', - marginTop: 6, - }, - halfInput: { - width: '48%', - }, - inputWrapper: { + cardHeader: { flexDirection: 'row', alignItems: 'center', - backgroundColor: colors.white, - borderRadius: 12, - borderWidth: 1, - borderColor: colors.border, - paddingHorizontal: 10, - marginTop: 8, - height: 52, + gap: 8, + marginBottom: spacing.s, + paddingBottom: spacing.xs, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, }, - icon: { - marginRight: 10, - }, - input: { - flex: 1, - fontSize: 15, - color: colors.darkText, - paddingVertical: 0, + cardTitle: { + fontWeight: '700', + fontSize: typography.size.s, + letterSpacing: 0.3, }, errorText: { - color: colors.error, - fontSize: 12, - marginTop: 3, - marginLeft: 4, + marginTop: 4, + fontSize: typography.size.xs, + fontWeight: '500', }, - termsRow: { + termsCard: { flexDirection: 'row', alignItems: 'center', - marginTop: 14, - marginHorizontal: 2, - }, - termsText: { - flex: 1, - fontSize: 13, - color: colors.darkText, - marginLeft: 8, + gap: 10, + backgroundColor: colors.surface, + borderRadius: 12, + padding: 14, + marginBottom: spacing.m, + borderWidth: 1, + borderColor: colors.border, }, buttonWrapper: { - marginTop: 18, + marginBottom: spacing.s, + }, + loginRow: { + alignItems: 'center', + marginBottom: spacing.m, }, }); diff --git a/src/Screens/Splash/Splash.tsx b/src/Screens/Splash/Splash.tsx index c14d539..bfd2a47 100644 --- a/src/Screens/Splash/Splash.tsx +++ b/src/Screens/Splash/Splash.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useRef, useState, useCallback } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import { Animated, StyleSheet, View } from 'react-native'; import { useNavigation, useIsFocused } from '@react-navigation/native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useSelector } from 'react-redux'; -import { useStore } from 'react-redux'; // For hydration check import { showToast } from '../../Constant/ShowDailog'; import { useRagistrationState } from '../../Services/ApiCall/Hooks'; @@ -24,37 +23,13 @@ const Splash: React.FC = () => { const isFocused = useIsFocused(); const fadeAnim = useRef(new Animated.Value(0)).current; const reduxToken = useSelector((state: any) => state.token?.token); - const store = useStore(); + const isPersistRehydrated = useSelector((state: any) => state?._persist?.rehydrated); const { mutateAsync: registrationStateMutate } = useRagistrationState(); - const [isHydrated, setIsHydrated] = useState(false); - const [isLoading, setIsLoading] = useState(true); + const hasSessionToken = Boolean(reduxToken && typeof reduxToken === 'object' && reduxToken.token); - useEffect(() => { - let unsubscribe: any; - const checkHydration = () => { - const token = (store.getState() as any).token?.token; - if (token !== undefined) { - setIsHydrated(true); - unsubscribe?.(); - } - }; - - unsubscribe = store.subscribe(checkHydration); - checkHydration(); // Check immediately - - return () => unsubscribe?.(); - }, []); - - - - const routeByOrgType = (params: { otherId: any; orgType: any }) => { - - - - // const orgType = params.orgType; - const orgType = "7" as string; // For testing purpose + const routeByOrgType = useCallback((params: { otherId: any; orgType: any }) => { + const orgType = String(params.orgType ?? ''); if (orgType === '4' || orgType === '5' || orgType === '6') { - navigation.replace(NavigationString.StageThreeScreen, params); return; } @@ -65,28 +40,35 @@ const Splash: React.FC = () => { } navigation.replace('Dashboard'); - }; + }, [navigation]); - const handleGetData = async () => { + const handleGetData = useCallback(async () => { try { const res = await registrationStateMutate(); + console.log("splash", res); + if (res?.status === false) { + navigation.reset({ + index: 0, + routes: [{ name: NavigationString.Login }], + }); + return; + } - if ( res?.data?.message==="No Response From Server"&& res?.data?.success===false){ - navigation.reset({ - index: 0, - routes: [{ name: NavigationString.Login }], - }); - return + if (res?.data?.message === 'No Response From Server' && res?.data?.success === false) { + navigation.reset({ + index: 0, + routes: [{ name: NavigationString.Login }], + }); + return; + } + if (res?.statusCode === 404 || res?.statusCode === 401) { + navigation.replace('Registration'); + return; } - if (res?.statusCode === 404 || res?.statusCode === 401) { - navigation.replace('Registration'); - return; - } if (res?.statusCode === 200 && res?.data) { const { registrationStatus, contOtherId, contOrganization } = res.data; const params = { otherId: contOtherId, orgType: contOrganization }; - switch (registrationStatus) { case null: case undefined: @@ -115,18 +97,19 @@ const Splash: React.FC = () => { if (error?.response?.status === 404) { navigation.replace('Registration'); } else { - await AsyncStorage.removeItem('token'); - navigation.replace('Signin'); + await AsyncStorage.removeItem('persist:root'); + navigation.replace(NavigationString.Login); } } - }; + }, [navigation, registrationStateMutate, routeByOrgType]); useEffect(() => { - if (!isFocused || !isHydrated) return; + if (!isFocused || !isPersistRehydrated) { + return; + } const runSplash = async () => { try { - setIsLoading(true); Animated.timing(fadeAnim, { toValue: 1, duration: 500, @@ -134,7 +117,7 @@ const Splash: React.FC = () => { }).start(); - if (!reduxToken.token) { + if (!hasSessionToken) { navigation.reset({ index: 0, routes: [{ name: NavigationString.Login }], @@ -142,7 +125,7 @@ const Splash: React.FC = () => { return; } - if (reduxToken.userType == 'tender') { + if (reduxToken.userType === 'tender') { await handleGetData(); } else { navigation.reset({ @@ -154,18 +137,16 @@ const Splash: React.FC = () => { } catch (error) { navigation.reset({ index: 0, - routes: [{ name: NavigationString.Login || 'Signin' }], + routes: [{ name: NavigationString.Login }], }); - } finally { - setIsLoading(false); } }; const timeoutId = setTimeout(runSplash, 200); return () => clearTimeout(timeoutId); - }, [isFocused, isHydrated]); + }, [fadeAnim, handleGetData, hasSessionToken, isFocused, isPersistRehydrated, navigation, reduxToken?.userType]); - if (!isHydrated) { + if (!isPersistRehydrated) { return ( { style={[styles.logo, { opacity: fadeAnim }]} resizeMode="contain" /> - {isLoading && ( - - {/* Optional spinner */} - - )} ); diff --git a/src/Screens/Tender/Component/EnvelopeCExcelPreview.tsx b/src/Screens/Tender/Component/EnvelopeCExcelPreview.tsx index abea835..46c21e4 100644 --- a/src/Screens/Tender/Component/EnvelopeCExcelPreview.tsx +++ b/src/Screens/Tender/Component/EnvelopeCExcelPreview.tsx @@ -21,16 +21,16 @@ import { encrypt, encryptWithRSA } from '../../../Services/Encryption/encrypt'; /* -------------------- UTILS -------------------- */ interface OfficerKey { - officersId: number; - officerPublicKey: string; - officerType: string; + officersId: number; + officerPublicKey: string; + officerType: string; } interface SaveEnvelopeOfficerDetail { - officerId: number; - officerEncryptedKey: string; - officerType: string; + officerId: number; + officerEncryptedKey: string; + officerType: string; } // Convert ArrayBuffer → Base64 @@ -61,7 +61,7 @@ const isTotalRow = (row: any) => const EnvelopeCExcelPreview = (props: any) => { const tenderData = props; - console.log("Tender Data",tenderData) + console.log("Tender Data", tenderData) const token = useSelector((state: any) => state.token.token); const navigation = useNavigation() as any; @@ -146,8 +146,8 @@ const EnvelopeCExcelPreview = (props: any) => { if (isTotalRow(row)) return; - const qtyKey:any = findColumnKey(columns, ['qty', 'quantity']); - const rateKey:any = findColumnKey(columns, ['rate', 'price']); + const qtyKey: any = findColumnKey(columns, ['qty', 'quantity']); + const rateKey: any = findColumnKey(columns, ['rate', 'price']); const valueKey = findColumnKey(columns, ['value', 'amount', 'total']); @@ -253,7 +253,7 @@ const EnvelopeCExcelPreview = (props: any) => { // Encrypt the entire file content console.log('🔒 Encrypting file content...'); - const encryptedContent:any = encrypt(fileContent, encryptionKey); + const encryptedContent: any = encrypt(fileContent, encryptionKey); console.log('✅ File content encrypted, Encrypted length:', encryptedContent?.length); @@ -353,6 +353,7 @@ const EnvelopeCExcelPreview = (props: any) => { deviceType: 'MOBILE', fileName: 'envelopeC_encrypted.xlsx', saveEnvelopesOfficersDetailsResDTO: officerEncryptedKeys, + randomKeyGenerated: ENCRYPTION_KEY, }; @@ -375,7 +376,7 @@ const EnvelopeCExcelPreview = (props: any) => { console.log('\n📝 STEP 6: Uploading encrypted file to server'); console.log('───────────────────────────────────────────────────────'); console.log('FormData prepared:', formData); - + const response = await axios.post(SUBMIT_ENVELOPE_C_UPLOAD, formData, { headers: { diff --git a/src/Screens/Tender/DeviceBinding.tsx b/src/Screens/Tender/DeviceBinding.tsx new file mode 100644 index 0000000..5fccc33 --- /dev/null +++ b/src/Screens/Tender/DeviceBinding.tsx @@ -0,0 +1,194 @@ +import React, { useState } from 'react'; +import { View, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native'; +import { InsideHeader } from '../../Component/Index'; +import AppText from '../../components/AppText'; +import { useTheme } from '../../Hooks/useTheme'; +import { useNavigation, useRoute } from '@react-navigation/native'; +import { AlertCircle, CheckCircle } from 'lucide-react-native'; +import DeviceInfo from 'react-native-device-info'; +import { apiCall } from '../../Axios/Axios'; +import { BASE_URL } from '../../Services/ApiCall/ApiUrls'; +import Geolocation from 'react-native-geolocation-service'; +import { request, PERMISSIONS, RESULTS } from 'react-native-permissions'; +import { Platform } from 'react-native'; +import { clampRGBA } from 'react-native-reanimated/lib/typescript/Colors'; + +const DeviceBinding = () => { + const { colors, spacing, typography } = useTheme(); + const navigation = useNavigation(); + const route = useRoute(); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + + const { message, showBindButton } = route.params || { + message: "Device is not mapped or not active. Please register and activate your device.", + showBindButton: false + }; + + const getLocation = (): Promise<{ latitude: string, longitude: string }> => { + return new Promise(async (resolve) => { + try { + const permission = Platform.OS === 'ios' + ? PERMISSIONS.IOS.LOCATION_WHEN_IN_USE + : PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; + + const result = await request(permission); + + if (result === RESULTS.GRANTED) { + Geolocation.getCurrentPosition( + (position) => { + resolve({ + latitude: position.coords.latitude.toString(), + longitude: position.coords.longitude.toString(), + }); + }, + (error) => { + console.log("Location Error: ", error); + resolve({ latitude: "23.77", longitude: "77.34" }); // Fallback to standard + }, + { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 } + ); + } else { + resolve({ latitude: "23.77", longitude: "77.34" }); // Fallback + } + } catch (err) { + console.log("Permission Error: ", err); + resolve({ latitude: "23.77", longitude: "77.34" }); // Fallback + } + }); + }; + + const handleBindDevice = async () => { + setLoading(true); + try { + const deviceId = await DeviceInfo.getUniqueId(); + const systemName = DeviceInfo.getSystemName(); + const systemVersion = DeviceInfo.getSystemVersion(); + const deviceOs = `${systemName} ${systemVersion}`; + const lastIp = await DeviceInfo.getIpAddress(); + const brand = DeviceInfo.getBrand(); + const model = DeviceInfo.getModel(); + const deviceName = `${brand} ${model}`; + const browser = "Mobile App"; + + const { latitude, longitude } = await getLocation(); + const location = `${longitude},${latitude}`; + + const payload = { + browser, + deviceOs, + deviceId, + lastIp, + deviceName, + location, + }; + + const response: any = await apiCall('post', `${BASE_URL}tenderBidding/deviceBinding`, payload); + + if (response?.status === 'SUCCESS' || response?.statusCode === 200) { + setSuccess(true); + // Optionally navigate back after a delay + setTimeout(() => { + navigation.goBack(); + }, 2000); + } + } catch (error) { + console.log("Error binding device: ", error); + } finally { + setLoading(false); + } + }; + + const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: spacing.l, + }, + iconContainer: { + marginBottom: spacing.l, + }, + title: { + marginBottom: spacing.m, + textAlign: 'center', + }, + message: { + textAlign: 'center', + marginBottom: spacing.xl, + lineHeight: 24, + }, + button: { + backgroundColor: colors.primary, + paddingVertical: spacing.m, + paddingHorizontal: spacing.xl, + borderRadius: 12, + width: '100%', + alignItems: 'center', + marginBottom: spacing.m, + }, + secondaryButton: { + backgroundColor: 'transparent', + paddingVertical: spacing.m, + width: '100%', + alignItems: 'center', + borderWidth: 1, + borderColor: colors.border, + borderRadius: 12, + } + }); + + return ( + + + + + {success ? ( + + ) : ( + + )} + + + + {success ? "Binding Successful" : "Device Not Bound"} + + + + {success ? "Your device binding request has been submitted successfully." : message} + + + {showBindButton && !success && ( + + {loading ? ( + + ) : ( + + Bind This Device + + )} + + )} + + navigation.goBack()} + > + + {success ? "Finish" : "Go Back"} + + + + + ); +}; + +export default DeviceBinding; diff --git a/src/Screens/Tender/DscBinding.tsx b/src/Screens/Tender/DscBinding.tsx new file mode 100644 index 0000000..859b7cb --- /dev/null +++ b/src/Screens/Tender/DscBinding.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { View, StyleSheet, TouchableOpacity } from 'react-native'; +import { InsideHeader } from '../../Component/Index'; +import AppText from '../../components/AppText'; +import { useTheme } from '../../Hooks/useTheme'; +import { useNavigation, useRoute } from '@react-navigation/native'; +import { AlertCircle } from 'lucide-react-native'; + +const DscBinding = () => { + const { colors, spacing, typography } = useTheme(); + const navigation = useNavigation(); + const route = useRoute(); + + // Fallback message just in case + const message = route.params?.message || "DSC is not mapped. Please map your DSC on the web portal before proceeding."; + + const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: spacing.l, + }, + iconContainer: { + marginBottom: spacing.l, + }, + title: { + marginBottom: spacing.m, + textAlign: 'center', + }, + message: { + textAlign: 'center', + marginBottom: spacing.xl, + lineHeight: 24, + }, + button: { + backgroundColor: colors.primary, + paddingVertical: spacing.m, + paddingHorizontal: spacing.xl, + borderRadius: 12, + width: '100%', + alignItems: 'center', + } + }); + + return ( + + + + + + + + + DSC Not Bound + + + + {message} + + + navigation.goBack()} + > + + Go Back + + + + + ); +}; + +export default DscBinding; diff --git a/src/Screens/Tender/LiveTender.tsx b/src/Screens/Tender/LiveTender.tsx index 0680d07..c111659 100644 --- a/src/Screens/Tender/LiveTender.tsx +++ b/src/Screens/Tender/LiveTender.tsx @@ -1,18 +1,19 @@ import React, { useCallback, useState, useEffect } from 'react'; -import { View, FlatList, ActivityIndicator, RefreshControl, Text } from 'react-native'; +import { View, FlatList, ActivityIndicator, RefreshControl, Text, StyleSheet } from 'react-native'; import Animated, { FadeInDown, FadeOutUp } from 'react-native-reanimated'; import { useNavigation } from '@react-navigation/native'; import { useFetchLiveAuction, useFetchLiveTender } from '../../Services/ApiCall/Hooks'; import { SearchInput } from '../../Component/Serach/SeacrhInput'; import { Skelton } from '../../Component/Index'; -import colors from '../../Constant/Color'; import { useDispatch, useSelector } from 'react-redux'; import { togglePurchaseTender } from '../../Redux/Slices/SelectedPurchaseTender'; import LiveTenderTile from './LiveTenderTile'; +import { useTheme } from '../../Hooks/useTheme'; // Using Theme const LiveTender = () => { const navigation: any = useNavigation(); const Dispatch = useDispatch() + const { colors } = useTheme(); // Hook const user = useSelector((state: any) => state?.token.token); const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender); const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch }: any = useFetchLiveTender(); @@ -47,7 +48,7 @@ const LiveTender = () => { const renderItem = useCallback( ({ item, index }: any) => { - if(item.tenderStatus==="Cancelled") return null; + if (item.tenderStatus === "Cancelled") return null; const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.tenderId === item.tenderId) return { ); if (isLoading) { - return ; + return + + ; } return ( - - { }} placeholder="Search...." /> + + {/* Liquid Background Blobs */} + + + + + + + { }} placeholder="Search Tenders..." filter={false} /> {showRefreshBanner && ( { paddingHorizontal: 14, marginTop: 6, borderRadius: 20, - backgroundColor: '#FFFFFF', + backgroundColor: colors.surface, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 8, @@ -102,7 +112,7 @@ const LiveTender = () => { @@ -124,7 +134,7 @@ const LiveTender = () => { } @@ -133,11 +143,23 @@ const LiveTender = () => { windowSize={7} removeClippedSubviews updateCellsBatchingPeriod={50} - onRefresh={refetch} - refreshing={isRefetching} /> ); }; +const styles = StyleSheet.create({ + blobContainer: { + ...StyleSheet.absoluteFillObject, + zIndex: -1, + overflow: 'hidden', + }, + blob: { + position: 'absolute', + width: 300, + height: 300, + borderRadius: 150, + }, +}); + export default LiveTender; \ No newline at end of file diff --git a/src/Screens/Tender/LiveTenderDetail.tsx b/src/Screens/Tender/LiveTenderDetail.tsx index bf515cc..89cfbcc 100644 --- a/src/Screens/Tender/LiveTenderDetail.tsx +++ b/src/Screens/Tender/LiveTenderDetail.tsx @@ -1,20 +1,15 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { Alert, ScrollView, StyleSheet, - Text, TouchableOpacity, View, - StatusBar, - SafeAreaView, } from 'react-native'; import { useRoute, useNavigation } from '@react-navigation/native'; import { useFetchLiveTenderById, } from '../../Services/ApiCall/Hooks'; -import { InsideHeader } from '../../Component/Index'; -import colors from '../../Constant/Color'; import ReactNativeBlobUtil from 'react-native-blob-util'; import { GET_DOCID } from '../../Services/ApiCall/ApiUrls'; import { @@ -27,20 +22,23 @@ import { } from 'lucide-react-native'; import moment from 'moment'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { showSuccessAlert, showToast } from '../../Constant/ShowDailog'; +import { showToast } from '../../Constant/ShowDailog'; +import AppText from '../../components/AppText'; +import { useTheme } from '../../Hooks/useTheme'; -const InfoRow = ({ label, value }: any) => ( + +const InfoRow = ({ label, value, styles }: any) => ( - {label} - {value || '-'} + {label} + {value || '-'} ); -const Section = ({ title, icon: Icon, children }: any) => ( +const Section = ({ title, icon: Icon, children, styles, colors }: any) => ( - {title} + {title} {children} @@ -48,11 +46,156 @@ const Section = ({ title, icon: Icon, children }: any) => ( const LiveTenderDetail = () => { const route = useRoute(); + const { colors, spacing, shadows, isDarkMode } = useTheme(); const navigation = useNavigation(); const tender = route?.params; const { data } = useFetchLiveTenderById(tender?.tenderId); const [downloadingId, setDownloadingId] = useState(null); const insets = useSafeAreaInsets() + + const styles = useMemo(() => StyleSheet.create({ + safeArea: { + flex: 1, + backgroundColor: colors.background, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 10, + borderBottomWidth: 1, + borderBottomColor: colors.border, + backgroundColor: colors.surface, + paddingVertical: 12, + }, + backBtn: { + padding: 8, + marginRight: 8, + }, + headerTitle: { + fontSize: 18, + fontWeight: '800', + color: colors.text, + }, + headerSub: { + fontSize: 13, + color: colors.textSecondary, + }, + liveBox: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + paddingHorizontal: 10, + paddingVertical: 5, + borderRadius: 20, + borderWidth: 1, + borderColor: colors.border, + backgroundColor: colors.background, + }, + dot: { + width: 8, + height: 8, + borderRadius: 4, + }, + liveText: { + fontSize: 11, + fontWeight: '800', + color: colors.textSecondary, + }, + content: { + padding: 16, + paddingBottom: 32, + }, + sectionContainer: { + backgroundColor: colors.surface, + borderRadius: 16, + padding: 16, + marginBottom: 16, + borderWidth: 1, + borderColor: colors.border, + ...shadows.light, + }, + sectionHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + marginBottom: 12, + paddingBottom: 6, + borderBottomWidth: 1, + borderBottomColor: colors.border, + }, + sectionTitle: { + fontSize: 14, + fontWeight: '800', + color: colors.text, + textTransform: 'uppercase', + letterSpacing: 0.4, + }, + card: { + backgroundColor: colors.surface, + borderRadius: 12, + }, + row: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 8, + paddingVertical: 8, + borderBottomWidth: 0.5, + borderBottomColor: colors.border, + }, + label: { + fontSize: 13, + color: colors.textSecondary, + flex: 1, + paddingRight: 8, + fontWeight: '600', + }, + value: { + fontSize: 13, + color: colors.text, + flex: 1, + textAlign: 'right', + fontWeight: '500', + }, + documentRow: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 10, + gap: 10, + borderBottomWidth: 1, + borderBottomColor: colors.border, + }, + docInfo: { + flexDirection: 'row', + alignItems: 'center', + flex: 1.4, + }, + fileIcon: { + width: 32, + height: 32, + borderRadius: 8, + backgroundColor: colors.primary + '20', // Opacity + alignItems: 'center', + justifyContent: 'center', + marginRight: 8, + }, + docName: { + flex: 1, + color: colors.text, + fontSize: 13, + fontWeight: '600', + }, + downloadBtn: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + downloadText: { + fontSize: 13, + fontWeight: '700', + color: colors.primary, + }, + }), [colors, shadows]); + if (!data?.data?.[0]) return null; const tenderData = data.data[0]; @@ -77,7 +220,7 @@ const LiveTenderDetail = () => { }).fetch('GET', downloadUrl, { Accept: 'application/pdf', }); - const stat = await ReactNativeBlobUtil.fs.stat(path); + // const stat = await ReactNativeBlobUtil.fs.stat(path); showToast('Download complete'); setDownloadingId(null); } catch (error: any) { @@ -98,193 +241,214 @@ const LiveTenderDetail = () => { }; return ( - - - + + navigation.goBack()} style={styles.backBtn} > - + - Tender Overview - + Tender Overview + Ref: {tenderData?.tenderNumber || '-'} - + - {isLive ? 'LIVE' : 'CLOSED'} + {isLive ? 'LIVE' : 'CLOSED'} -
+
- - - - - - - - + + + + + + + +
-
+
- - - - + + + +
-
+
- - + +
-
+
- +
-
+
-
+
-
+
- - + +
-
+
- + {tenderData?.approvalOfficerNameEmail?.map((o: any, i: number) => ( ))}
-
+
{tenderData?.tenderDocuments?.map((doc: any, i: number) => ( @@ -292,7 +456,7 @@ const LiveTenderDetail = () => { - {doc.fileName} + {doc.fileName} { - + {downloadingId === doc?.docId ? 'Downloading...' : 'Download'} - + ))} @@ -319,143 +483,3 @@ const LiveTenderDetail = () => { }; export default LiveTenderDetail; - -const styles = StyleSheet.create({ - safeArea: { - flex: 1, - backgroundColor: '#f8fafc', - }, - header: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal:10, - borderBottomWidth: 1, - borderBottomColor: '#f1f5f9', - backgroundColor: '#fff', - }, - backBtn: { - padding: 8, - marginRight: 8, - }, - headerTitle: { - fontSize: 18, - fontWeight: '800', - color: '#0f172a', - }, - headerSub: { - fontSize: 13, - color: '#64748b', - }, - liveBox: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - paddingHorizontal: 10, - paddingVertical: 5, - borderRadius: 20, - borderWidth: 1, - borderColor: '#e2e8f0', - }, - dot: { - width: 8, - height: 8, - borderRadius: 4, - }, - liveText: { - fontSize: 11, - fontWeight: '800', - color: '#475569', - }, - content: { - padding: 16, - paddingBottom: 32, - }, - sectionContainer: { - backgroundColor: '#ffffff', - borderRadius: 16, - padding: 16, - marginBottom: 16, - borderWidth: 1, - borderColor: '#e2e8f0', - }, - sectionHeader: { - flexDirection: 'row', - alignItems: 'center', - gap: 10, - marginBottom: 12, - paddingBottom: 6, - borderBottomWidth: 1, - borderBottomColor: '#f8fafc', - }, - sectionTitle: { - fontSize: 14, - fontWeight: '800', - color: '#1e293b', - textTransform: 'uppercase', - letterSpacing: 0.4, - }, - card: { - backgroundColor: '#ffffff', - borderRadius: 12, - }, - row: { - flexDirection: 'row', - justifyContent: 'space-between', - marginBottom: 8, - paddingVertical: 8, - borderBottomWidth: 0.5, - borderBottomColor: '#e5e7eb', - }, - label: { - fontSize: 13, - color: '#64748b', - flex: 1, - paddingRight: 8, - fontWeight: '600', - }, - value: { - fontSize: 13, - color: '#111827', - flex: 1, - textAlign: 'right', - fontWeight: '500', - }, - documentRow: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 10, - gap: 10, - borderBottomWidth: 1, - borderBottomColor: '#f1f5f9', - }, - docInfo: { - flexDirection: 'row', - alignItems: 'center', - flex: 1.4, - }, - fileIcon: { - width: 32, - height: 32, - borderRadius: 8, - backgroundColor: '#e0f2fe', - alignItems: 'center', - justifyContent: 'center', - marginRight: 8, - }, - docName: { - flex: 1, - color: '#111827', - fontSize: 13, - fontWeight: '600', - }, - downloadBtn: { - flexDirection: 'row', - alignItems: 'center', - gap: 4, - }, - downloadText: { - fontSize: 13, - fontWeight: '700', - color: colors.primary, - }, -}); diff --git a/src/Screens/Tender/LiveTenderTile.tsx b/src/Screens/Tender/LiveTenderTile.tsx index afa76ad..c08a848 100644 --- a/src/Screens/Tender/LiveTenderTile.tsx +++ b/src/Screens/Tender/LiveTenderTile.tsx @@ -1,13 +1,16 @@ import React, { memo } from 'react'; -import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import { View, StyleSheet, TouchableOpacity, Dimensions } from 'react-native'; import moment from 'moment'; -import colors from '../../Constant/Color'; import { LiveTenderTileProps } from './types/types'; -import { Calendar, Tag, Info, ArrowRight } from 'lucide-react-native'; -import TenderDetail from './Modal/TenderFullDetail'; -import TenderFullDetail from './Modal/TenderFullDetail'; +import { Calendar, Tag, Info, ArrowRight, Clock, MapPin } from 'lucide-react-native'; import { useNavigation } from '@react-navigation/native'; import { format } from '../../Helper/DateFormat'; +import { useTheme } from '../../Hooks/useTheme'; +import LinearGradient from 'react-native-linear-gradient'; +import AppText from '../../components/AppText'; +import NavigationString from '../../Constant/NavigationString'; + +const { width } = Dimensions.get('window'); const LiveTenderTile: React.FC = ({ contractorId, @@ -21,6 +24,8 @@ const LiveTenderTile: React.FC = ({ tenderNumber, }) => { const navigation: any = useNavigation(); + const { colors, shadows, spacing } = useTheme(); + const purchaseEnded = moment().isAfter(moment(purchaseEnd, 'YYYY-MM-DD HH:mm:ss')); const contractorIds = typeof data?.contractorId === 'string' @@ -30,227 +35,214 @@ const LiveTenderTile: React.FC = ({ : []; const purchased = contractorIds.includes(String(contractorId)); -if(data.tenderStatus==="Amended"){ - -} + + const getStatusColor = () => { + if (isPurchased) return colors.error; + if (purchaseEnded) return colors.textSecondary; + if (purchased) return colors.textSecondary; + return colors.success; + }; + + const statusColor = getStatusColor(); return ( - - {/* Status Tag */} - + navigation.navigate('LiveTenderDetail', data)} + style={[styles.container, { + backgroundColor: colors.surface, + shadowColor: colors.shadow, + borderColor: colors.border + }]} + > - navigation.navigate('LiveTenderDetail', data)} - > - {/* Header */} - - + + {/* Header Section */} + + - #{tenderNumber} + + #{tenderNumber} + - ₹{tenderFees} + + + ₹{tenderFees} + - {/* Work Title */} - + {/* Title Section */} + {tenderOfWork ?? 'General Tender Work'} - + {/* Info Grid */} - - - - - Start - {format.date6(`${tenderDispatchDate}`)} - + + + + + + + + Start Date + + {format.date6(`${tenderDispatchDate}`)} + + - - - - - Deadline - - {moment(purchaseEnd, 'YYYY-MM-DD HH:mm:ss').format('DD MMM, YY')} - + + + + + + + + Deadline + + {moment(purchaseEnd, 'YYYY-MM-DD HH:mm:ss').format('DD MMM, YY')} + + - {/* Action Bar */} - - navigation.navigate('LiveTenderDetail', data)}> - - Details + {/* Actions Section */} + + navigation.navigate('LiveTenderDetail', data)} + > + + View Details + navigation.navigate(NavigationString.PurchaseTenderList) : onPurchasePress} + style={styles.actionBtnWrapper} > - - {purchaseEnded ? (purchased ? 'Purchased' : 'Expired') : (purchased ? 'Purchased' : isPurchased ? 'Remove' : 'Add to Cart')} - - {(!purchaseEnded && !purchased) && ( - - )} + + {purchaseEnded ? (purchased ? 'Purchased' : 'Closed') : (purchased ? 'Purchased' : isPurchased ? 'GO TO CART' : 'Add to Cart')} + + {(!purchaseEnded && !purchased) && } + - - + + ); }; export default memo(LiveTenderTile); const styles = StyleSheet.create({ - cardWrapper: { - marginVertical: 10, - marginHorizontal: 16, - backgroundColor: '#fff', - borderRadius: 24, - flexDirection: 'row', - overflow: 'hidden', - - // Modern shadow - shadowColor: '#000', - shadowOffset: { width: 0, height: 10 }, - shadowOpacity: 0.08, - shadowRadius: 20, - elevation: 8, - }, - statusTag: { - width: 6, - height: '100%', - }, container: { - flex: 1, - padding: 20, + marginHorizontal: 16, + marginVertical: 10, + borderRadius: 20, + borderWidth: 1, + overflow: 'hidden', + elevation: 4, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.1, + shadowRadius: 12, }, - header: { + topLine: { + height: 4, + width: '100%', + }, + contentContainer: { + padding: 16, + }, + headerRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, }, - badge: { + idTag: { flexDirection: 'row', alignItems: 'center', - backgroundColor: colors.primary + '25', - paddingHorizontal: 12, - paddingVertical: 5, - borderRadius: 12, + paddingVertical: 4, + paddingHorizontal: 10, + borderRadius: 20, gap: 6, }, - badgeText: { - fontSize: 12, + title: { + marginBottom: 16, fontWeight: '700', - color: colors.primary, - }, - priceText: { - fontSize: 20, - fontWeight: '800', - color: '#0f172a', - }, - workTitle: { - fontSize: 16, - fontWeight: '600', - color: '#334155', lineHeight: 24, + }, + infoContainer: { + borderRadius: 16, + padding: 12, marginBottom: 16, }, - infoGrid: { + infoRow: { flexDirection: 'row', justifyContent: 'space-between', - backgroundColor: '#f0f4f8', - paddingVertical: 12, - paddingHorizontal: 16, - borderRadius: 16, - marginBottom: 18, + alignItems: 'center', }, - infoItem: { + infoCol: { flexDirection: 'row', alignItems: 'center', gap: 10, + flex: 1, }, - infoTextGroup: { - flexDirection: 'column', + divider: { + width: 1, + height: 30, + backgroundColor: 'rgba(0,0,0,0.05)', + marginHorizontal: 10, }, - infoLabel: { - fontSize: 10, - color: '#94a3b8', - textTransform: 'uppercase', - fontWeight: '700', + iconBox: { + // width: 32, + // height: 32, + // borderRadius: 16, + // alignItems: 'center', + // justifyContent: 'center', + // backgroundColor: 'rgba(0,0,0,0.03)', }, - infoValue: { - fontSize: 13, - fontWeight: '700', - color: '#1e293b', - }, - actionBar: { + actionRow: { flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', gap: 12, }, - iconBtn: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - paddingHorizontal: 10, - paddingVertical: 6, - borderRadius: 10, - backgroundColor: '#e2e8f0', - }, - iconBtnText: { - fontSize: 14, - fontWeight: '700', - color: colors.primary, - }, - mainButton: { + detailsBtn: { flex: 1, - height: 46, - borderRadius: 16, + paddingVertical: 10, + borderWidth: 1, + borderRadius: 14, + alignItems: 'center', + justifyContent: 'center', + }, + actionBtnWrapper: { + flex: 1.5, + }, + actionBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - gap: 8, - }, - btnActive: { - backgroundColor: '#4ade80', // light green - }, - btnPurchased: { - backgroundColor: colors.error, - borderWidth: 2, - borderColor: colors.error, - }, - btnDisabled: { - backgroundColor: '#e2e8f0', - }, - mainButtonText: { - fontSize: 14, - fontWeight: '800', + paddingVertical: 10, + borderRadius: 14, + gap: 6, }, }); diff --git a/src/Screens/Tender/Modal/TenderFullDetail.tsx b/src/Screens/Tender/Modal/TenderFullDetail.tsx index f0c7e5b..2a62fdb 100644 --- a/src/Screens/Tender/Modal/TenderFullDetail.tsx +++ b/src/Screens/Tender/Modal/TenderFullDetail.tsx @@ -7,7 +7,6 @@ import { ScrollView, SafeAreaView, Platform, - StatusBar, } from 'react-native'; import Modal from 'react-native-modal'; import moment from 'moment'; @@ -33,7 +32,7 @@ interface Props { const TenderFullDetail: React.FC = ({ visible, onClose, data }) => { - + const inset = useSafeAreaInsets(); const { data: tenderResponse }: any = useFetchTenderById(data?.tenderId); const tender = tenderResponse?.data?.[0]; @@ -74,8 +73,6 @@ const TenderFullDetail: React.FC = ({ visible, onClose, data }) => { statusBarTranslucent > - - {/* Header */} diff --git a/src/Screens/Tender/ProceedToBidDetail.tsx b/src/Screens/Tender/ProceedToBidDetail.tsx index 2b2454f..35c60cc 100644 --- a/src/Screens/Tender/ProceedToBidDetail.tsx +++ b/src/Screens/Tender/ProceedToBidDetail.tsx @@ -1,12 +1,24 @@ -import { StyleSheet, Text, View, ScrollView, StatusBar, TouchableOpacity, RefreshControl, ActivityIndicator, Platform } from 'react-native' -import React, { useCallback, useEffect } from 'react' +import { StyleSheet, View, ScrollView, TouchableOpacity, RefreshControl, ActivityIndicator } from 'react-native' +import React, { useCallback } from 'react' import { InsideHeader } from '../../Component/Index' -import colors from '../../Constant/Color' import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native' import { useFetchTenderById, useProceedToBid } from '../../Services/ApiCall/Hooks' +import { useTheme } from '../../Hooks/useTheme' +import AppText from '../../components/AppText' +import { CircleDot, Clock, CheckCircle, XCircle, Calendar, FileText, Shield, AlertCircle } from 'lucide-react-native' +import Animated, { FadeInUp, FadeInDown, FadeIn, SlideInRight, Layout } from 'react-native-reanimated' +import { apiCall } from '../../Axios/Axios' +import NavigationString from '../../Constant/NavigationString' +import DeviceInfo from 'react-native-device-info' +import { BASE_URL } from '../../Services/ApiCall/ApiUrls' +import { useSelector } from 'react-redux' +import { useESetuAdharVarification } from '../../Services/ApiCall/Hooks' +import ConfirmDialog from '../../Constant/Dialog/ConfirmDialog' +import { generateTransactionId } from '../../Helper/uniqueId' +const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView) const ProceedToBidDetail = () => { - // Sample data based on your JSON + const { colors, spacing, shadows, typography } = useTheme(); const route = useRoute(); const tenderDataId = route.params?.tender; const { data, refetch, isRefetching } = useProceedToBid(tenderDataId?.tenderId); @@ -15,101 +27,95 @@ const ProceedToBidDetail = () => { const [refreshing, setRefreshing] = React.useState(false); const [loading, setLoading] = React.useState(false); - - const navigation = useNavigation() - // Format date from array + const navigation = useNavigation(); + const user = useSelector((state: any) => state.token?.token); + const { mutateAsync: eSetuAdharVarification, isPending: isESetuPending } = useESetuAdharVarification(); + const [showESetuDialog, setShowESetuDialog] = React.useState(false); + const [isESetuNavigating, setIsESetuNavigating] = React.useState(false); - const handleStartBidding = () => { + const handleStartBidding = async () => { setLoading(true); try { - setLoading(true); - - navigation.navigate('TenderBidding', { tenderId: tenderDataId.tenderId }); + const deviceId = await DeviceInfo.getUniqueId(); + const url = `${BASE_URL}tenderBidding/getDsc-with-device-details?deviceId=${deviceId}`; + const response: any = await apiCall('get', url); + console.log("Response: ", response); + const { dsc, deviceBinding, deviceVerification, dscExpiryStatus } = response?.data || {}; + if (!dsc) { + navigation.navigate(NavigationString.DscBinding, { + message: "Please map your DSC from the web screen" + }); + } else if (!dscExpiryStatus) { + navigation.navigate(NavigationString.DscBinding, { + message: "Your DSC has been expired. Please map new DSC from the web screen" + }); + } else if (deviceBinding && deviceVerification === 'PENDING') { + navigation.navigate(NavigationString.DeviceBinding, { + message: "Please approve this device to enable bidding access from the web.", + showBindButton: false + }); + } else if (deviceBinding && deviceVerification === 'REVOKE') { + navigation.navigate(NavigationString.DeviceBinding, { + message: "Your device has been revoked. Please bind your device from the web portal.", + showBindButton: true + }); + } else if (!deviceBinding) { + navigation.navigate(NavigationString.DeviceBinding, { + message: "Please bind your device from the web portal.", + showBindButton: true + }); + } else { + // All checks passed (dsc is true, device is bound and approved) + // Now check Aadhaar verification (eSetu) + // setShowESetuDialog(true); + navigation.navigate(NavigationString.TenderBidding, { + tenderId: tenderDataId.tenderId + }); + } } catch (error) { console.log("Error in starting bidding: ", error); } finally { setLoading(false); } - } + + const handleProceedESetu = async () => { + try { + setIsESetuNavigating(true); + const payload = { + contId: user.id, + transactionId: generateTransactionId(), + udf1: "23.77", + udf2: "77.34", + udf3: "string", + udf4: "string", + responseUrl: "http://dev.192.168.6.194:8090/" + }; + + const res = await eSetuAdharVarification(payload); + setShowESetuDialog(false); + + // Pass returnRoute so KycWebView can come back here + navigation.navigate(NavigationString.KycWebViewScreen, { + res, + returnRoute: NavigationString.TenderBidding, + returnParams: { tenderId: tenderDataId.tenderId } + }); + } catch (err) { + console.log('eSetu Error:', err); + setIsESetuNavigating(false); + } finally { + setIsESetuNavigating(false); + } + }; + const formatDate = (dateArray: any[]) => { if (!dateArray || dateArray.length < 3) return "N/A"; const [year, month, day, hour = 0, minute = 0] = dateArray; return `${day.toString().padStart(2, '0')}/${month.toString().padStart(2, '0')}/${year} ${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; }; - // Status indicator component - const StatusIndicator = ({ status, label }: { status: boolean; label: string }) => ( - - - - {label}: {status ? "Submitted" : "Not Submitted"} - - - ); - - // Envelope log card component - const EnvelopeLogCard = ({ log, title }: { log: any; title: string }) => ( - - - {title} - - {log?.envelope} - - - - - Activity: - {log?.activity} - - - - Date & Time: - {log?.dateTime} - - - - System Info: - {log?.osName} / {log?.browserName} - - - - IP Address: - {log?.ipAddress} - - - - Status Message: - {log?.logMessage} - - - {log?.headingMsg && ( - - Heading: - {log?.headingMsg} - - )} - - ); - - // Information row component - const InfoRow = ({ label, value, isHighlighted = false }: { label: string; value: string; isHighlighted?: boolean }) => ( - - {label}: - {value} - - ); - - // Feature chip component - const FeatureChip = ({ label, enabled }: { label: string; enabled: boolean }) => ( - - - {label} {enabled ? '✓' : '✗'} - - - ); - const onRefresh = async () => { try { setRefreshing(true); @@ -125,483 +131,410 @@ const ProceedToBidDetail = () => { }, [tenderData?.tenderId]) ); + // Status indicator component with animation + const StatusIndicator = ({ status, label, index }: { status: boolean; label: string; index: number }) => ( + + {status ? ( + + ) : ( + + )} + + {label}: {status ? "Submitted" : "Not Submitted"} + + + ); + + // Envelope log card component with animation + const EnvelopeLogCard = ({ log, title, index }: { log: any; title: string; index: number }) => ( + + + {title} + + {log?.envelope} + + + + + Activity: + {log?.activity} + + + + Date & Time: + {log?.dateTime} + + + + System Info: + {log?.osName} / {log?.browserName} + + + + IP Address: + {log?.ipAddress} + + + + Status Message: + {log?.logMessage} + + + {log?.headingMsg && ( + + Heading: + {log?.headingMsg} + + )} + + ); + + // Information row component with animation + const InfoRow = ({ label, value, isHighlighted = false, index }: { label: string; value: string; isHighlighted?: boolean; index: number }) => ( + + {label}: + {value} + + ); + + const styles = makeStyles(colors, spacing, shadows); + return ( - + - - }> + } + > {/* Tender Header Section */} - + - {tenderData?.tenderNumber} - ID: {tenderData?.tenderId} + {tenderData?.tenderNumber} + ID: {tenderData?.tenderId} - - + + {tenderData?.biddingStatus ? "Active" : "Inactive"} - - + + - {tenderData?.nameofWork} + {tenderData?.nameofWork} - + - NIT No. - {tenderData?.nitNo} + NIT No. + {tenderData?.nitNo} - + - EMD Amount - ₹{tenderData?.emdInFig} + EMD Amount + ₹{tenderData?.emdInFig} - + {/* Envelope Submission Status */} - - Envelope Submission Status - - - {tenderData?.showEB &&} - {tenderData?.showLCS &&} - {tenderData?.showQCBS &&} - {tenderData?.showEC &&} - - + + Envelope Submission Status + + + {tenderData?.showEB && } + {tenderData?.showLCS && } + {tenderData?.showQCBS && } + {tenderData?.showEC && } - + {/* Envelope Logs */} {tenderData?.envelopeA_Logs && ( - - Submission Logs - {tenderData?.envelopeA_Logs && } - {tenderData?.envelopeB_Logs && } - {tenderData?.envelopeC_Logs && } - + + Submission Logs + {tenderData?.envelopeA_Logs && } + {tenderData?.envelopeB_Logs && } + {tenderData?.envelopeC_Logs && } + )} {/* Tender Details */} - - Tender Details - - - - - - + + Tender Details + + + + + + - + {/* Time Period */} - - Submission Time Period - - - Starts On: - {formatDate(tenderData?.submittedStartDate)} - - - Ends On: - {formatDate(tenderData?.submittedEndDate)} - + + Submission Time Period + + + + + Starts On: + + {formatDate(tenderData?.submittedStartDate)} + + + + + Ends On: + + {formatDate(tenderData?.submittedEndDate)} + - - - {/* Feature Flags - - Available Features - - - - - - - - - */} + {/* Final Status */} - - Overall Status - - All Envelopes Submitted: - - {tenderData?.envelopeASubmittStatus && tenderData?.envelopeBSubmittStatus && tenderData?.envelopeCSubmittStatus - ? "COMPLETED" : "PENDING"} - - - - Bidding Status: - + + Overall Status + + + + + All Envelopes Submitted: + + + {tenderData?.envelopeASubmittStatus && tenderData?.envelopeBSubmittStatus && tenderData?.envelopeCSubmittStatus ? "COMPLETED" : "PENDING"} + + + + + + + Bidding Status: + + {tenderData?.biddingStatus ? "ACTIVE" : "CLOSED"} - - - + + + - + - - { - tenderData?.proceedtoBid && !tenderData?.viewBid && tenderData?.biddingStatus && tenderData?.bidWithdrawStatus === false ? ( + + {tenderData?.proceedtoBid && !tenderData?.viewBid && tenderData?.biddingStatus && tenderData?.bidWithdrawStatus === false ? ( + + {loading ? ( + + ) : ( + Start Bidding + )} + + ) : ( + + + One time bidding is completed + + )} + - - {loading ? : 'Start Bidding'} - - - ) : - One time bidding is completed - } - + !isESetuPending && !isESetuNavigating && setShowESetuDialog(false)} + loading={isESetuPending || isESetuNavigating} + /> - ) } export default ProceedToBidDetail -const styles = StyleSheet.create({ +const makeStyles = (colors: any, spacing: any, shadows: any) => StyleSheet.create({ container: { flex: 1, - padding: 16, - marginBottom: 60, + padding: spacing.m, }, headerCard: { - backgroundColor: colors.primaryLight, - borderRadius: 12, - padding: 20, - marginBottom: 20, - borderLeftWidth: 4, - borderLeftColor: colors.primary, + backgroundColor: colors.surface, + borderRadius: 16, + padding: spacing.m, + marginBottom: spacing.m, }, tenderHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - marginBottom: 12, - }, - tenderNumber: { - fontSize: 22, - fontWeight: 'bold', - color: colors.primary, - }, - tenderId: { - fontSize: 14, - color: colors.darkGray, - marginTop: 4, }, statusBadge: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20, }, - statusBadgeText: { - color: colors.white, - fontWeight: '600', - fontSize: 12, - }, - workDescription: { - fontSize: 16, - color: colors.darkText, - lineHeight: 22, - marginBottom: 16, - }, headerInfoRow: { flexDirection: 'row', alignItems: 'center', - backgroundColor: colors.white, - borderRadius: 8, - padding: 12, + borderRadius: 10, + padding: spacing.s, + marginTop: spacing.m, }, headerInfoItem: { flex: 1, alignItems: 'center', }, - headerInfoLabel: { - fontSize: 12, - color: colors.darkGray, - marginBottom: 4, - }, - headerInfoValue: { - fontSize: 16, - fontWeight: '600', - color: colors.primary, - }, separator: { width: 1, height: 30, - backgroundColor: colors.lightGray, }, section: { - marginBottom: 20, - }, - sectionTitle: { - fontSize: 18, - fontWeight: '600', - color: colors.primary, - marginBottom: 12, - paddingLeft: 4, + marginBottom: spacing.m, }, statusGrid: { - backgroundColor: colors.white, + backgroundColor: colors.surface, borderRadius: 12, - padding: 16, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 3, + padding: spacing.m, }, statusContainer: { flexDirection: 'row', alignItems: 'center', - marginBottom: 12, - }, - statusDot: { - width: 12, - height: 12, - borderRadius: 6, - marginRight: 10, - }, - statusText: { - fontSize: 15, - fontWeight: '500', + marginBottom: spacing.s, + gap: 8, }, envelopeCard: { - backgroundColor: colors.white, + backgroundColor: colors.surface, borderRadius: 12, - padding: 16, - marginBottom: 12, + padding: spacing.m, + marginBottom: spacing.s, borderWidth: 1, - borderColor: colors.lightGray, }, cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - marginBottom: 12, - }, - cardTitle: { - fontSize: 16, - fontWeight: '600', - color: colors.primary, + marginBottom: spacing.s, }, envelopeBadge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, }, - envelopeBadgeText: { - color: colors.white, - fontWeight: 'bold', - fontSize: 12, - }, logDetailRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - marginBottom: 8, - }, - label: { - fontSize: 14, - color: colors.darkGray, - flex: 1, - }, - value: { - fontSize: 14, - color: colors.darkText, - fontWeight: '500', - flex: 2, - textAlign: 'right', - }, - successText: { - color: colors.success, - fontWeight: '600', + marginBottom: 6, }, detailsCard: { - backgroundColor: colors.white, + backgroundColor: colors.surface, borderRadius: 12, - padding: 16, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 3, + padding: spacing.m, }, infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - paddingVertical: 10, - borderBottomWidth: 1, - borderBottomColor: colors.lightestGray, - }, - infoLabel: { - fontSize: 14, - color: colors.darkGray, - }, - infoValue: { - fontSize: 15, - fontWeight: '500', - color: colors.darkText, - }, - highlightedValue: { - color: colors.success, - fontWeight: '600', + paddingVertical: spacing.s, + borderBottomWidth: StyleSheet.hairlineWidth, }, timeCard: { - backgroundColor: colors.white, + backgroundColor: colors.surface, borderRadius: 12, - padding: 16, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 3, + padding: spacing.m, }, timeRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - paddingVertical: 12, - borderBottomWidth: 1, - borderBottomColor: colors.lightestGray, - }, - timeLabel: { - fontSize: 15, - color: colors.darkGray, - fontWeight: '500', - }, - timeValue: { - fontSize: 15, - color: colors.primary, - fontWeight: '600', - }, - chipsContainer: { - flexDirection: 'row', - flexWrap: 'wrap', - gap: 10, - }, - chip: { - paddingHorizontal: 16, - paddingVertical: 8, - borderRadius: 20, - marginBottom: 8, - }, - chipText: { - fontSize: 14, - fontWeight: '500', + paddingVertical: spacing.s, + borderBottomWidth: StyleSheet.hairlineWidth, }, finalStatusCard: { - backgroundColor: colors.primaryLight, + backgroundColor: colors.surface, borderRadius: 12, - padding: 20, - marginTop: 10, + padding: spacing.m, + marginTop: spacing.s, + marginBottom: spacing.l, borderWidth: 2, - borderColor: colors.primary, - }, - finalStatusTitle: { - fontSize: 18, - fontWeight: 'bold', - color: colors.primary, - marginBottom: 16, - textAlign: 'center', }, finalStatusRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - marginBottom: 12, - paddingVertical: 8, - borderBottomWidth: 1, - borderBottomColor: 'rgba(0, 0, 0, 0.1)', - }, - finalStatusLabel: { - fontSize: 16, - color: colors.darkText, - fontWeight: '500', - }, - finalStatusValue: { - fontSize: 16, - fontWeight: 'bold', + paddingVertical: spacing.s, + borderBottomWidth: StyleSheet.hairlineWidth, }, footerSpace: { - height: 30, - }, - success: { - color: colors.success, - }, - error: { - color: colors.error, - }, - primary: { - color: colors.primary, - }, - warning: { - color: colors.warning, - }, - successLight: { - backgroundColor: colors.successLight, - }, - lightGray: { - backgroundColor: colors.lightGray, + height: 80, }, footer: { position: 'absolute', bottom: 0, left: 0, right: 0, - backgroundColor: colors.white, - padding: 16, + backgroundColor: colors.surface, + padding: spacing.m, borderTopWidth: 1, - borderTopColor: colors.lightGray, - elevation: 10, }, proceedButton: { backgroundColor: colors.primary, paddingVertical: 14, - borderRadius: 30, - alignItems: 'center', - }, - proceedButtonText: { - color: colors.white, - fontSize: 16, - fontWeight: '600', - }, - footerText: { - textAlign: 'center', - color: colors.darkGray, - fontSize: 14, - }, - actionButton: { - width: '100%', - height: 48, borderRadius: 12, - justifyContent: 'center', alignItems: 'center', - ...Platform.select({ - ios: { - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - }, - }), }, }); \ No newline at end of file diff --git a/src/Screens/Tender/ProceedToPurTender.tsx b/src/Screens/Tender/ProceedToPurTender.tsx index 68a10b0..90f5f8b 100644 --- a/src/Screens/Tender/ProceedToPurTender.tsx +++ b/src/Screens/Tender/ProceedToPurTender.tsx @@ -1,255 +1,496 @@ import React, { useState } from 'react'; -import { View, Text, FlatList, StyleSheet, TouchableOpacity, Alert } from 'react-native'; +import { + View, + FlatList, + StyleSheet, + TouchableOpacity, + ActivityIndicator, +} from 'react-native'; +import Animated, { FadeInDown, FadeIn } from 'react-native-reanimated'; +import { + CreditCard, + Calendar, + Hash, + IndianRupee, + Percent, + Server, + Wifi, + WifiOff, + Receipt, + CheckCircle2, +} from 'lucide-react-native'; import { InsideHeader, Loader } from '../../Component/Index'; -import colors from '../../Constant/Color'; -import { CreditCard, Calendar, User, DollarSign, ArrowRight } from 'lucide-react-native'; import { useDispatch, useSelector } from 'react-redux'; import { useOfflineTenderPurchase } from '../../Services/ApiCall/Hooks'; import { StackActions, useNavigation } from '@react-navigation/native'; -import NavigationString from '../../Constant/NavigationString'; import { showErrorAlert, showToast } from '../../Constant/ShowDailog'; import { clearPurchaseTender } from '../../Redux/Slices/SelectedPurchaseTender'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; const ProceedToPurTender = () => { - const navigation: any = useNavigation(); - const Dispatch = useDispatch() - const [loading, setLoading] = useState(false); - const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender); + const navigation: any = useNavigation(); + const Dispatch = useDispatch(); + const { colors, spacing, shadows } = useTheme(); + const [loading, setLoading] = useState(false); + const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender); - const { mutate, isPending } = useOfflineTenderPurchase() - const calculateTotalAmount = (item: any) => { - const gstAmount = - (Number(item.documentFees) + Number(item.portalCharge)) * - (Number(item.gst) / 100); + const { mutate, isPending } = useOfflineTenderPurchase(); - const payableAmount = - Number(item.documentFees) + Number(item.portalCharge) + gstAmount; - return payableAmount - } + const calculateTotalAmount = (item: any) => { + const gstAmount = + (Number(item.documentFees) + Number(item.portalCharge)) * + (Number(item.gst) / 100); + return Number(item.documentFees) + Number(item.portalCharge) + gstAmount; + }; - const handleOfflinePurchase = async () => { - const orderNo = `ORD${Date.now()}`; - const body = { - tenderDetails: SelectedPurchaseList.map((item: any, index: number) => ({ - gst: item.gst, - regId: item.regId || 0, - deptId: item.deptId || 1, - gstAmount: item.gst / 100, - payableAmount: calculateTotalAmount(item), - portalCharge: item.portalCharge, - tenderFees: item.documentFees, - tenderId: item.tenderId, - tenderNumber: item.tenderNumber, - tenderStatus: item.tenderStatus ?? "", - tenderType: item.tenderType ?? "", - isPurchased: item.purchaseStatus ?? false, - totalPayableAmount: calculateTotalAmount(item), - percentageAmount: calculateTotalAmount(item) / 100, - newTenderDocumentFees: item.documentFees * index + 1, - })), - orderNo: orderNo, - }; + const grandTotal = SelectedPurchaseList.reduce( + (sum: number, item: any) => sum + calculateTotalAmount(item), + 0 + ); - try { - mutate(body, { - onSuccess: (res) => { - if (res.statusCode === 200) { - showToast(res.message); - navigation.dispatch(StackActions.pop(2)); - Dispatch( - clearPurchaseTender() - ); - - } - }, - onError: (error) => { - showErrorAlert('Something went wrong'); - console.log(error, "================purchse Error"); - } - }) - - } catch (error) { - console.log("purchase offline error", error); - } - } - - const handlePurchase = (type: 'online' | 'offline') => { - setLoading(true); - setTimeout(() => { - setLoading(false); - type === 'online' ? showErrorAlert('Online Purchase is disabled') : handleOfflinePurchase(); - - }, 1200); + const handleOfflinePurchase = async () => { + const orderNo = `ORD${Date.now()}`; + const body = { + tenderDetails: SelectedPurchaseList.map((item: any, index: number) => ({ + gst: item.gst, + regId: item.regId || 0, + deptId: item.deptId || 1, + gstAmount: item.gst / 100, + payableAmount: calculateTotalAmount(item), + portalCharge: item.portalCharge, + tenderFees: item.documentFees, + tenderId: item.tenderId, + tenderNumber: item.tenderNumber, + tenderStatus: item.tenderStatus ?? '', + tenderType: item.tenderType ?? '', + isPurchased: item.purchaseStatus ?? false, + totalPayableAmount: calculateTotalAmount(item), + percentageAmount: calculateTotalAmount(item) / 100, + newTenderDocumentFees: item.documentFees * index + 1, + })), + orderNo, }; - const renderItem = ({ item }: { item: any[0] }) => ( - - - {item.auctionNumber} - {item.auctionPattern} - + try { + mutate(body, { + onSuccess: (res: any) => { + if (res.statusCode === 200) { + showToast(res.message); + navigation.dispatch(StackActions.pop(2)); + Dispatch(clearPurchaseTender()); + } + }, + onError: (error: any) => { + showErrorAlert('Something went wrong'); + console.log(error, '================purchse Error'); + }, + }); + } catch (error) { + console.log('purchase offline error', error); + } + }; - - - End Date: {item.purchaseEnd} - + const handlePurchase = (type: 'online' | 'offline') => { + setLoading(true); + setTimeout(() => { + setLoading(false); + type === 'online' + ? showErrorAlert('Online Purchase is disabled') + : handleOfflinePurchase(); + }, 1200); + }; - - - Tender No: {item.tenderNumber} - + const styles = makeStyles(colors, spacing, shadows); - - - Tender Fees: ₹{item.documentFees} - - - - - GST ({item.gst}%): ₹{item.gst / 100} - - - - - Portal Charge: ₹{item.portalCharge} - - - - - Total: ₹{calculateTotalAmount(item)} - - - ); + const renderItem = ({ item, index }: { item: any; index: number }) => { + const total = calculateTotalAmount(item); + const gstAmount = + (Number(item.documentFees) + Number(item.portalCharge)) * + (Number(item.gst) / 100); return ( - - - - item?.tenderId?.toString()} - contentContainerStyle={styles.container} - /> - + + {/* Card Top Accent */} + - - handlePurchase('online')} - > - Purchase Online - - - handlePurchase('offline')} - > - Purchase Offline - - + + {/* Header */} + + + + {String(index + 1).padStart(2, '0')} + - - {loading && ( - - + + + {item.auctionOfWork || item.auctionNumber} + + {item.auctionPattern ? ( + + + {item.auctionPattern} + - )} + ) : null} + + + + {/* Divider */} + + + {/* Info Rows */} + } + label="Tender No" + value={item.tenderNumber} + colors={colors} + /> + } + label="Purchase End" + value={item.purchaseEnd || '—'} + colors={colors} + /> + + {/* Divider */} + + + {/* Fee breakdown */} + + + FEE BREAKDOWN + + + + + + + + {/* Total Banner */} + + + + + Total Payable + + + + ₹ {total.toFixed(2)} + + + ); + }; + + return ( + + + + {/* Grand Total Strip */} + + + + {SelectedPurchaseList.length} tender{SelectedPurchaseList.length !== 1 ? 's' : ''} + + + ₹ {grandTotal.toFixed(2)} + + + + + + Ready to pay + + + + + item?.tenderId?.toString()} + contentContainerStyle={styles.container} + showsVerticalScrollIndicator={false} + /> + + {/* Bottom Action Buttons */} + + handlePurchase('online')} + > + + + Online + + + + handlePurchase('offline')} + > + + + Offline + + + + + {/* Loading Overlay */} + {(loading || isPending) && ( + + + + + Processing purchase… + + + + )} + + ); }; export default ProceedToPurTender; -const styles = StyleSheet.create({ +/* ─── Sub-components ─── */ + +const InfoRow = ({ + icon, + label, + value, + colors, +}: { + icon: React.ReactNode; + label: string; + value: string; + colors: any; +}) => ( + + {icon} + + {label} + + + {value} + + +); + +const infoRowStyles = StyleSheet.create({ + row: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 5, + gap: 6, + }, + iconWrap: { + width: 22, + alignItems: 'center', + }, + label: { + flex: 1, + fontWeight: '500', + } as any, + value: { + fontWeight: '600', + } as any, +}); + +const FeeRow = ({ + label, + value, + colors, +}: { + label: string; + value: string; + colors: any; +}) => ( + + + {label} + + + {value} + + +); + +const feeRowStyles = StyleSheet.create({ + row: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: 3, + }, +}); + +/* ─── Styles factory ─── */ +const makeStyles = (colors: any, spacing: any, shadows: any) => + StyleSheet.create({ screen: { - flex: 1, + flex: 1, + backgroundColor: colors.background, + }, + grandTotalStrip: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: colors.surface, + marginHorizontal: spacing.m, + marginTop: spacing.m, + marginBottom: spacing.s, + paddingHorizontal: spacing.m, + paddingVertical: spacing.m, + borderRadius: 18, + ...shadows.medium, + }, + grandTotalRight: { + flexDirection: 'row', + alignItems: 'center', + gap: 5, + backgroundColor: `${colors.success}15`, + paddingHorizontal: spacing.s + 2, + paddingVertical: spacing.xs + 2, + borderRadius: 20, }, container: { - padding: 16, - paddingBottom: 120, + paddingHorizontal: spacing.m, + paddingTop: spacing.s, + paddingBottom: 120, }, card: { - backgroundColor: '#fff', - borderRadius: 16, - padding: 16, - marginBottom: 16, - elevation: 5, - shadowColor: '#000', - shadowOpacity: 0.12, - shadowOffset: { width: 0, height: 3 }, - shadowRadius: 8, + backgroundColor: colors.surface, + borderRadius: 20, + marginBottom: spacing.m, + overflow: 'hidden', + ...shadows.medium, + }, + cardAccent: { + height: 4, + backgroundColor: colors.primary, + }, + cardBody: { + padding: spacing.m, }, cardHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - marginBottom: 12, + flexDirection: 'row', + alignItems: 'flex-start', + marginBottom: spacing.s, }, - title: { - fontSize: 16, - fontWeight: '700', - color: '#0F172A', + indexBadge: { + width: 32, + height: 32, + borderRadius: 10, + backgroundColor: colors.primary, + alignItems: 'center', + justifyContent: 'center', + marginTop: 2, }, - pattern: { - fontSize: 14, - color: colors.primary, - fontWeight: '600', + patternBadge: { + alignSelf: 'flex-start', + backgroundColor: `${colors.primary}15`, + paddingHorizontal: spacing.s, + paddingVertical: 2, + borderRadius: 8, + marginTop: 4, }, - cardRow: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - marginBottom: 4, + divider: { + height: StyleSheet.hairlineWidth, + backgroundColor: colors.border, + marginVertical: spacing.s, }, - text: { - fontSize: 14, - color: '#555', + feeSection: { + backgroundColor: colors.overlay, + borderRadius: 12, + padding: spacing.s + 2, + marginBottom: spacing.s, }, - totalRow: { - marginTop: 10, - backgroundColor: colors.primary, - borderRadius: 12, - paddingVertical: 10, - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - gap: 8, + feeHeader: { + letterSpacing: 1.2, + marginBottom: spacing.xs + 2, + fontWeight: '700', + } as any, + totalBanner: { + backgroundColor: colors.primary, + borderRadius: 14, + paddingVertical: 12, + paddingHorizontal: spacing.m, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', }, - total: { - fontSize: 15, - fontWeight: '700', - color: '#fff', + totalLeft: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.s, }, - bottomButtonContainer: { - position: 'absolute', - bottom: 0, - left: 0, - flexDirection: 'row', - backgroundColor: colors.white, - width: '100%', - paddingVertical: 20, - paddingHorizontal: 10, - elevation: 5 + footer: { + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + flexDirection: 'row', + paddingHorizontal: spacing.m, + paddingTop: spacing.s + 4, + paddingBottom: spacing.m + 4, + backgroundColor: colors.surface, + gap: spacing.s, + ...shadows.strong, }, button: { - flex: 1, - backgroundColor: colors.primary, - paddingVertical: 14, - borderRadius: 12, - alignItems: 'center', - flexDirection: 'row', - justifyContent: 'center', + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 14, + borderRadius: 16, + gap: spacing.s, }, - buttonText: { - color: '#fff', - fontWeight: '700', - fontSize: 14, + onlineButton: { + backgroundColor: colors.surface, + borderWidth: 1.5, + borderColor: colors.primary, }, + offlineButton: { + backgroundColor: colors.buttonPrimary, + ...shadows.medium, + }, + onlineBtnText: { + letterSpacing: 1.2, + } as any, + offlineBtnText: { + letterSpacing: 1.2, + } as any, loadingOverlay: { - ...StyleSheet.absoluteFillObject, - backgroundColor: 'rgba(0,0,0,0.2)', - justifyContent: 'center', - alignItems: 'center', - zIndex: 10, + ...StyleSheet.absoluteFillObject, + backgroundColor: 'rgba(0,0,0,0.3)', + justifyContent: 'center', + alignItems: 'center', + zIndex: 10, }, -}); + loadingCard: { + backgroundColor: colors.surface, + borderRadius: 20, + padding: spacing.l, + alignItems: 'center', + ...shadows.strong, + }, + }); diff --git a/src/Screens/Tender/PurchaseTenderAllList.tsx b/src/Screens/Tender/PurchaseTenderAllList.tsx index 90715d9..3e45860 100644 --- a/src/Screens/Tender/PurchaseTenderAllList.tsx +++ b/src/Screens/Tender/PurchaseTenderAllList.tsx @@ -34,7 +34,7 @@ const PurchaseTenderAllList = () => { return () => clearTimeout(timer); } }, [isRefetching]); - + const format = 'DD-MM-YYYY HH:mm:ss'; function isTenderActive(item: any): boolean { @@ -70,10 +70,10 @@ const PurchaseTenderAllList = () => { ); }, [isFetchingNextPage]); - + return ( - { }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} /> + { }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} filter={false} /> {showRefreshBanner && ( { - const Navigation: any = useNavigation() - const Dispatch = useDispatch() + const Navigation: any = useNavigation(); + const Dispatch = useDispatch(); + const { colors, spacing, shadows, typography } = useTheme(); const [expandedIndex, setExpandedIndex] = useState(null); const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender); + const { width } = useWindowDimensions(); + const toggleExpand = (index: number) => { setExpandedIndex(prev => (prev === index ? null : index)); }; - useEffect(()=>{ - if(SelectedPurchaseList.length === 0){ - Navigation.goBack() - } - },[SelectedPurchaseList] + useEffect(() => { + if (SelectedPurchaseList.length === 0) { + Navigation.goBack(); + } + }, [SelectedPurchaseList]); - ) + const styles = makeStyles(colors, spacing, shadows); return ( - + - - - {/* Cards */} - {SelectedPurchaseList.map((item: any, index: any) => { - const expanded = expandedIndex === index; - return ( - - toggleExpand(index)} - > - + {/* Summary Strip */} + + + + + + + {SelectedPurchaseList.length} + + {' '}tender{SelectedPurchaseList.length !== 1 ? 's' : ''} selected for purchase + + - - Tender No: {item.tenderNumber} - + + {SelectedPurchaseList.map((item: any, index: number) => ( + + ))} - - Status:{' '} - - {item.tenderStatus === 'Opened' ? 'Open' : 'Approved'} - - - - - {/* Expanded Content */} - {expanded && ( - - {/* {renderRow('', item.departmentName)} */} - {/* {renderRow('Pattern', item.auctionPattern)} */} - {renderRow('Fees', `₹ ${item.documentFees}`)} - {renderRow('Department', item.departmentName)} - {renderRow('Purchase End', item.purchaseEnd)} - - { Dispatch(togglePurchaseTender(item)) }}> - - Remove - - - )} - - ); - })} + - {/* Footer */} - { Navigation.navigate(NavigationString.ProceedToPurTender) }}> - Proceed to Purchase - + {/* Floating Proceed Button */} + + Navigation.navigate(NavigationString.ProceedToPurTender)} + > + + Proceed to Purchase + + + + {SelectedPurchaseList.length} + + + + + ); }; export default PurchaseTenderList; -const CardHeader = ({ title, expanded }: { - title: string; - expanded: boolean; -}) => { - const rotation = useSharedValue(expanded ? 1 : 0); - rotation.value = withTiming(expanded ? 1 : 0, { duration: 220 }); - const arrowStyle = useAnimatedStyle(() => ({ - transform: [{ rotate: `${rotation.value * 180}deg` }], - })); +/* ─── Sub-components ─── */ - return ( - - {title} - - - - - ); -}; - -const renderRow = (label: string, value: string) => ( - - {label} - {value} +const DetailRow = ({ + label, + value, + colors, + valueColor, +}: { + label: string; + value: string; + colors: any; + valueColor?: string; +}) => ( + + + {label} + + + {value} + ); - -const styles = StyleSheet.create({ - screen: { - flex: 1, - backgroundColor: '#F8FAFC', - }, - container: { - padding: 16, - paddingBottom: 110, - }, - header: { - flexDirection: 'row', - justifyContent: 'space-between', - marginBottom: 14, - }, - heading: { - fontSize: 22, - fontWeight: '800', - color: colors.black, - }, - card: { - backgroundColor: '#fff', - borderRadius: 18, - padding: 16, - marginBottom: 14, - borderWidth: 1, - borderColor: '#E5E7EB', - }, - cardHeader: { +const detailRowStyles = StyleSheet.create({ + row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - }, - title: { - fontSize: 16, - fontWeight: '700', - color: '#0F172A', - flex: 1, - marginRight: 12, - }, - subTitle: { - fontSize: 14, - color: '#64748B', - marginTop: 6, - }, - details: { - marginTop: 16, - paddingTop: 14, - borderTopWidth: 1, - borderTopColor: '#E5E7EB', - }, - row: { - marginVertical: 6, + paddingVertical: 8, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: 'rgba(0,0,0,0.06)', }, label: { - fontSize: 13, - color: '#64748B', - }, + flex: 1, + fontWeight: '500', + } as any, value: { - fontSize: 15, + flex: 1, + textAlign: 'right', fontWeight: '600', - color: '#0F172A', - }, - removeButton: { - marginTop: 18, - backgroundColor: colors.primary, - borderRadius: 14, - paddingVertical: 12, - flexDirection: 'row', - justifyContent: 'center', - gap: 8, - }, - removeText: { - color: '#fff', - fontWeight: '700', - }, - proceedButton: { - position: 'absolute', - bottom: 16, - left: 16, - right: 16, - backgroundColor: colors.primary, - paddingVertical: 16, - borderRadius: 18, - alignItems: 'center', - }, - proceedText: { - color: '#fff', - fontSize: 16, - fontWeight: '800', - }, + } as any, }); + +/* ─── Chevron animated style hook ─── */ +function useChevronStyle(expanded: boolean) { + const rotation = useSharedValue(expanded ? 1 : 0); + rotation.value = withTiming(expanded ? 1 : 0, { duration: 220 }); + return useAnimatedStyle(() => ({ + transform: [{ rotate: `${rotation.value * 180}deg` }], + })); +} + +/* ─── Extracted Tender Item Component ─── */ +const TenderItem = ({ item, index, expanded, toggleExpand, colors, styles }: any) => { + const Dispatch = useDispatch(); + const isOpen = item.tenderStatus === 'Opened'; + const chevronStyle = useChevronStyle(expanded); + console.log(item, "itemmmmmmm"); + + return ( + + + + + toggleExpand(index)} + > + + + + + {item.tenderNumber} + + + + + + + {item.auctionOfWork} + + + + + + + + + + {item.departmentName || 'Department'} + + + + + {expanded && ( + + + + + + + + Dispatch(togglePurchaseTender(item))} + > + + + Remove + + + + )} + + + ); +}; + +/* ─── Styles factory ─── */ +const makeStyles = (colors: any, spacing: any, shadows: any) => + StyleSheet.create({ + screen: { + flex: 1, + backgroundColor: colors.background, + }, + summaryStrip: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.surface, + marginHorizontal: spacing.m, + marginTop: spacing.m, + marginBottom: spacing.s, + paddingHorizontal: spacing.m, + paddingVertical: spacing.s + 2, + borderRadius: 14, + gap: spacing.s, + ...shadows.light, + }, + summaryIcon: { + width: 28, + height: 28, + borderRadius: 8, + backgroundColor: `${colors.primary}15`, + alignItems: 'center', + justifyContent: 'center', + }, + container: { + paddingHorizontal: spacing.m, + paddingTop: spacing.s, + }, + card: { + backgroundColor: colors.surface, + borderRadius: 20, + marginBottom: spacing.m, + flexDirection: 'row', + overflow: 'hidden', + ...shadows.medium, + }, + cardExpanded: { + ...shadows.strong, + }, + accentBar: { + width: 5, + borderTopLeftRadius: 20, + borderBottomLeftRadius: 20, + }, + cardInner: { + flex: 1, + paddingHorizontal: spacing.m, + paddingVertical: spacing.m, + }, + badgeRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginBottom: spacing.s, + }, + tenderBadge: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: `${colors.primary}15`, + paddingHorizontal: spacing.s, + paddingVertical: 3, + borderRadius: 8, + gap: 4, + }, + badgeText: { + fontWeight: '600', + letterSpacing: 0.2, + } as any, + statusBadge: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: spacing.s + 2, + paddingVertical: 3, + borderRadius: 20, + gap: 5, + }, + statusDot: { + width: 6, + height: 6, + borderRadius: 3, + }, + titleRow: { + flexDirection: 'row', + alignItems: 'flex-start', + justifyContent: 'space-between', + gap: spacing.s, + }, + title: { + flex: 1, + color: undefined, // will use AppText default from theme + fontWeight: '700', + } as any, + deptRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 5, + marginTop: spacing.xs + 2, + }, + deptText: { + letterSpacing: 0.3, + } as any, + detailsContainer: { + marginTop: spacing.s, + }, + divider: { + height: 1, + backgroundColor: colors.border, + marginBottom: spacing.s, + }, + removeButton: { + marginTop: spacing.m, + backgroundColor: colors.error, + borderRadius: 14, + paddingVertical: 12, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + gap: spacing.s, + }, + removeText: { + letterSpacing: 1, + } as any, + footerContainer: { + position: 'absolute', + bottom: 20, + left: spacing.m, + right: spacing.m, + }, + proceedButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 16, + borderRadius: 20, + gap: spacing.s, + ...shadows.strong, + }, + proceedText: { + letterSpacing: 1.5, + } as any, + countBadge: { + backgroundColor: '#fff', + borderRadius: 12, + minWidth: 24, + height: 24, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 6, + }, + }); diff --git a/src/Screens/Tender/PurchaseTenderTile.tsx b/src/Screens/Tender/PurchaseTenderTile.tsx index d648f7b..6c56755 100644 --- a/src/Screens/Tender/PurchaseTenderTile.tsx +++ b/src/Screens/Tender/PurchaseTenderTile.tsx @@ -1,268 +1,263 @@ -import React, { use, useEffect } from 'react'; -import { View, Text, StyleSheet, TouchableOpacity, Platform } from 'react-native'; +import React, { useMemo } from 'react'; +import { View, StyleSheet, TouchableOpacity, Platform } from 'react-native'; import Animated, { FadeInUp, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; import moment from 'moment'; import CountdownTimer from '../../Component/Counter/CountdownTimer'; -import colors from '../../Constant/Color'; import { useFetchTenderById } from '../../Services/ApiCall/Hooks'; import { useNavigation } from '@react-navigation/native'; import TenderFullDetail from './Modal/TenderFullDetail'; +import AppText from '../../components/AppText'; +import { useTheme } from '../../Hooks/useTheme'; +import { Calendar, Wallet, Banknote, ArrowRight, CircleDot, Clock } from 'lucide-react-native'; const PurchaseTenderTile = ({ item, onPressDetails }: any) => { - const scale = useSharedValue(1); - const elevation = useSharedValue(8); + const { colors, spacing, shadows } = useTheme(); const navigation = useNavigation(); + const scale = useSharedValue(1); + const elevation = useSharedValue(2); // Reduced initial elevation for a sleeker look + const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], elevation: elevation.value, })); - const { data: tenderResponse, }: any = useFetchTenderById(item?.tenderId); + const { data: tenderResponse }: any = useFetchTenderById(item?.tenderId); // Assuming id is reachable here, verified in original + // Fix: Original code used item?.tenderId, but useFetchTenderById might need checks. + // Keeping logic same as original: item contains tenderId? + // Original check: const { data: tenderResponse, }: any = useFetchTenderById(item?.tenderId); + const tender = tenderResponse?.data; - const handlePressIn = () => { - scale.value = withTiming(0.97, { duration: 120 }); - elevation.value = withTiming(3, { duration: 120 }); + scale.value = withTiming(0.98, { duration: 150 }); }; const handlePressOut = () => { - scale.value = withTiming(1, { duration: 160 }); - elevation.value = withTiming(8, { duration: 160 }); + scale.value = withTiming(1, { duration: 150 }); }; const isLive = item?.biddingStatus === true; - - const statusColor = isLive ? colors.primary : '#94A3B8'; - const lightStatusBg = isLive ? '#EFF6FF' : '#F8FAFC'; const [showDetail, setShowDetail] = React.useState(false); + // Dynamic colors based on status + const statusConfig = useMemo(() => { + if (isLive) { + return { + color: colors.primary, + bg: colors.primary + '15', // 15 = ~8% opacity + label: 'LIVE NOW', + icon: CircleDot + }; + } + return { + color: colors.textSecondary, + bg: colors.secondary + '15', + label: 'CLOSED', + icon: Clock + }; + }, [isLive, colors]); + + const styles = useMemo(() => styling(colors, spacing), [colors, spacing]); return ( - - - - {/* Header */} - - - - - - {isLive ? 'LIVE' : 'CLOSED'} - - - {item?.tenderNumber} + {/* Header Section */} + + + + + + {statusConfig.label} + + + ID: {item?.tenderNumber} + + - - + + + {tender?.workDescription || item?.tenderNumber || 'Tender Details'} + + + + + + + {/* Info Grid */} + + + + + + + Tender Fee + ₹{Number(item?.tenderFeeInFig || 0).toLocaleString('en-IN')} - - Tender Fee + - {/* Date */} - { - setShowDetail(true) - }}> - - Bid Ends: - {moment( - item?.keydates?.bidSubmissionDate?.endDateTime, - 'DD-MM-YYYY HH:mm:ss' - ).format('DD MMM YYYY, hh:mm A')} - - - - {/* Footer */} - - - Remaining Time + + + + + + Bid Ends + + {moment(item?.keydates?.bidSubmissionDate?.endDateTime, 'DD-MM-YYYY HH:mm:ss').format('DD MMM, hh:mm A')} + + + + + {/* Footer / Timer Action */} + + + Time Remaining + + + {/* Timer */} + {isLive ? ( ) : ( - --:--:-- + + -- : -- : -- + )} + {/* CTA Button */} { - if (isLive) { + if (isLive && tender) { navigation.navigate('ProceedToBidDetail', { tender }); + } else if (isLive && !tender) { + console.warn("Tender data possibly missing for navigation"); + navigation.navigate('ProceedToBidDetail', { tender: item }); } }} - activeOpacity={0.85} style={[ styles.actionButton, - { backgroundColor: statusColor }, - !isLive && styles.disabledButton, + { backgroundColor: isLive ? colors.primary : colors.border }, + !isLive && styles.disabledButton ]} > - - {isLive ? 'Proceed to Bid' : 'Tender Closed'} - + + {isLive ? 'Bid Now' : 'Closed'} + + {isLive && } - {/* Tender Detail Modal */} setShowDetail(false)} /> - ); }; -export default PurchaseTenderTile; - - -const styles = StyleSheet.create({ - shadowContainer: { - marginHorizontal: 5, - marginTop: 14, - marginBottom: 6, +const styling = (colors: any, spacing: any) => StyleSheet.create({ + container: { + marginHorizontal: spacing.s, + marginBottom: spacing.m, borderRadius: 16, - backgroundColor: '#fff', - elevation: 8, + backgroundColor: colors.surface, }, - cardInternal: { + card: { borderRadius: 16, - overflow: 'hidden', - flexDirection: 'row', - backgroundColor: '#fff', + backgroundColor: colors.surface, + padding: spacing.m, + borderWidth: 1, + borderColor: 'rgba(0,0,0,0.03)', }, - statusAccent: { - width: 5, - height: '100%', + header: { + marginBottom: spacing.m, }, - mainInfo: { - flex: 1, - padding: 16, - }, - headerRow: { + headerTopUser: { flexDirection: 'row', justifyContent: 'space-between', - alignItems: 'flex-start', - marginBottom: 12, + alignItems: 'center', }, statusBadge: { flexDirection: 'row', alignItems: 'center', - paddingHorizontal: 8, - paddingVertical: 3, - borderRadius: 6, - marginBottom: 6, - alignSelf: 'flex-start', - }, - statusDot: { - width: 6, - height: 6, - borderRadius: 3, - marginRight: 5, + paddingHorizontal: 10, + paddingVertical: 5, + borderRadius: 20, + gap: 6 }, statusText: { + fontWeight: '700', fontSize: 10, - fontWeight: '800', - letterSpacing: 0.5, - }, - title: { - fontWeight: '800', - fontSize: 16, - color: '#1E293B', - }, - amountContainer: { - alignItems: 'flex-end', - }, - amount: { - fontWeight: '900', - fontSize: 18, - color: '#0F172A', - }, - feeLabel: { - fontSize: 10, - color: '#64748B', - fontWeight: '500', - }, - dateRow: { - backgroundColor: '#F8FAFC', - paddingHorizontal: 10, - paddingVertical: 8, - borderRadius: 10, - marginBottom: 16, - borderWidth: 1, - borderColor: '#F1F5F9', - }, - dateText: { - fontSize: 12, - color: '#334155', - fontWeight: '600', - }, - dateLabel: { - fontWeight: '400', - color: '#64748B', - }, - footer: { - borderTopWidth: 1, - borderTopColor: '#F1F5F9', - paddingTop: 12, - }, - footerTop: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: 12, - }, - footerLabel: { - fontSize: 11, - fontWeight: '600', - color: '#94A3B8', textTransform: 'uppercase', }, - endedText: { - fontSize: 13, - fontWeight: '700', - color: '#CBD5E1', + divider: { + height: 1, + backgroundColor: colors.border, + marginBottom: spacing.m, + opacity: 0.5 }, - actionButton: { - width: '100%', - height: 48, - borderRadius: 12, + infoContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: spacing.l, + gap: spacing.s + }, + infoRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + flex: 1, + }, + iconBox: { + width: 36, + height: 36, + borderRadius: 10, + backgroundColor: colors.primary + '15', // light opacity primary justifyContent: 'center', alignItems: 'center', - ...Platform.select({ - ios: { - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - }, - }), + }, + footer: { + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: colors.background, // Slightly distinct bg for footer + marginHorizontal: -spacing.m, + marginBottom: -spacing.m, + padding: spacing.m, + borderBottomLeftRadius: 16, + borderBottomRightRadius: 16, + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: colors.border, + }, + timerContainer: { + flex: 1, + }, + actionButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 10, + paddingHorizontal: 20, + borderRadius: 12, }, disabledButton: { - backgroundColor: '#E2E8F0', - }, - addText: { - color: '#fff', - fontSize: 14, - fontWeight: '800', - letterSpacing: 0.2, - }, + opacity: 0.7 + } }); + +export default PurchaseTenderTile; diff --git a/src/Screens/Tender/Tender.tsx b/src/Screens/Tender/Tender.tsx index 728e2b9..e747fd4 100644 --- a/src/Screens/Tender/Tender.tsx +++ b/src/Screens/Tender/Tender.tsx @@ -1,7 +1,6 @@ import React, { useState, useRef, useMemo } from 'react'; import { View, - Text, StyleSheet, TouchableOpacity, Animated, @@ -12,40 +11,39 @@ import { useFetchLiveTender, useFetchPurchaseTender, } from '../../Services/ApiCall/Hooks'; -import colors from '../../Constant/Color'; -import { ShoppingCart, Lock } from 'lucide-react-native'; +import { ShoppingCart, Lock, Radio, Package } from 'lucide-react-native'; import { useNavigation } from '@react-navigation/native'; import NavigationString from '../../Constant/NavigationString'; -import FontsFamily from '../../Constant/FontsFamily'; import { useSelector } from 'react-redux'; import LiveTender from './LiveTender'; import PurchaseTenderAllList from './PurchaseTenderAllList'; +import { useTheme } from '../../Hooks/useTheme'; +import AppText from '../../components/AppText'; const { width: SCREEN_WIDTH } = Dimensions.get('window'); -const TAB_HEIGHT = 44; +const TAB_HEIGHT = 48; const HORIZONTAL_PADDING = 32; -const SWITCH_INSETS = 2; +const SWITCH_INSETS = 3; const Tender = () => { + const { colors, spacing, shadows } = useTheme(); const Navigation = useNavigation() as any; const { data: liveTenderData }: any = useFetchLiveTender(); const { data: purchaseTenderData }: any = useFetchPurchaseTender(); - const SelectedPurchaseList = useSelector( - (state: any) => state?.selectPurchaseTender, - ); + const SelectedPurchaseList = useSelector((state: any) => state?.selectPurchaseTender); const reduxToken = useSelector((state: any) => state.token?.token); const liveCount = liveTenderData?.pages?.[0]?.totalCount || 0; const purchaseCount = purchaseTenderData?.pages?.[0]?.totalCount || 0; - const [selectedTab, setSelectedTab] = useState<'live' | 'purchase'>('live'); - const translateX = useRef(new Animated.Value(0)).current; - const TAB_WIDTH = useMemo(() => { const availableWidth = SCREEN_WIDTH - HORIZONTAL_PADDING - SWITCH_INSETS * 2; - return availableWidth / 2.4; + return availableWidth / 2; }, []); + const [selectedTab, setSelectedTab] = useState<'live' | 'purchase'>('purchase'); + const translateX = useRef(new Animated.Value(TAB_WIDTH)).current; + const onTabPress = (tab: 'live' | 'purchase', index: number) => { setSelectedTab(tab); Animated.spring(translateX, { @@ -55,252 +53,295 @@ const Tender = () => { }).start(); }; - // Check if user is auction type const isAuctionUser = reduxToken?.userType?.toLowerCase() === 'auction'; + const cartCount = SelectedPurchaseList.length; - // Render restricted access message - const renderRestrictedAccess = () => ( - - - - + const styles = makeStyles(colors, spacing, shadows, TAB_WIDTH, TAB_HEIGHT); + + if (isAuctionUser) { + return ( + + + + + {/* Double-ring Lock Icon */} + + + + + + + + Access Restricted + + + + You're logged in as an{' '} + + Auction user + + {'. '}Tender features are not available for your account type. + + + + + Switch to a Tender account to access live & purchase tender features. + + + + Navigation.navigate(NavigationString.Login)} + > + + Switch to Tender Login + + + - - Tender Access Restricted - - - You are currently logged in as an Auction user. Tender features are not available for your account type. - - - - To access tender features, please login with a Tender user account. - - - Navigation.navigate(NavigationString.Login)} - > - Switch to Tender Login - - - ); + ); + } return ( - {isAuctionUser ? ( - // Show restricted access message for auction users - renderRestrictedAccess() - ) : ( - // Show normal tender UI for tender users - <> - - + {/* Segmented Control */} + + {/* Sliding pill */} + + + {/* Live Tab */} + onTabPress('live', 0)} + > + + - + Live {liveCount ? `(${liveCount})` : ''} + + - onTabPress('live', 0)} - > - - Live ({liveCount}) - - + {/* Purchase Tab */} + onTabPress('purchase', 1)} + > + + + Purchased {purchaseCount ? `(${purchaseCount})` : ''} + + + + - onTabPress('purchase', 1)} - > - - Purchased ({purchaseCount}) - - - + {/* Content */} + {selectedTab === 'live' ? : } - - {SelectedPurchaseList.length ? ( - - - {SelectedPurchaseList.length} - - - ) : null} - { - Navigation.navigate(NavigationString.PurchaseTenderList); - }} - > - - - + {/* Cart Floating Button */} + Navigation.navigate(NavigationString.PurchaseTenderList)} + > + + {cartCount > 0 && ( + + + {cartCount} + - - {selectedTab === 'live' ? : } - - )} + )} + ); }; export default Tender; -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.white, - }, - topRow: { - flexDirection: 'row', - justifyContent: 'space-around', - alignItems: 'center', - paddingHorizontal: 16, - }, - switchWrapper: { - flexDirection: 'row', - marginVertical: 14, - backgroundColor: '#f1f5f9', - borderRadius: 18, - padding: 2, - height: TAB_HEIGHT, - }, - tab: { - height: TAB_HEIGHT - 4, - alignItems: 'center', - justifyContent: 'center', - zIndex: 2, - }, - tabText: { - fontSize: 14, - color: '#64748b', - lineHeight: 18, - fontFamily: FontsFamily.poppinsSemiBold, - }, - activeText: { - color: colors.primary, - }, - activeTab: { - position: 'absolute', - top: 2, - left: 2, - height: TAB_HEIGHT - 4, - backgroundColor: '#fff', - borderRadius: 16, - shadowColor: '#000', - shadowOpacity: 0.1, - shadowRadius: 6, - elevation: 3, - }, - restrictedContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - padding: 24, - backgroundColor: colors.lightGray, - }, - restrictedCard: { - backgroundColor: colors.white, - borderRadius: 24, - padding: 32, - alignItems: 'center', - maxWidth: 400, - width: '100%', - - }, - iconContainer: { - width: 120, - height: 120, - borderRadius: 60, - backgroundColor: colors.primaryHighlight, - justifyContent: 'center', - alignItems: 'center', - marginBottom: 24, - }, - restrictedTitle: { - fontSize: 24, - fontWeight: '700', - color: colors.darkText, - textAlign: 'center', - marginBottom: 16, - fontFamily: FontsFamily.poppinsBold, - }, - restrictedMessage: { - fontSize: 16, - color: colors.grayText, - textAlign: 'center', - lineHeight: 24, - marginBottom: 12, - fontFamily: FontsFamily.poppinsRegular, - }, - restrictedSubMessage: { - fontSize: 14, - color: colors.grayText, - textAlign: 'center', - lineHeight: 22, - marginBottom: 32, - fontFamily: FontsFamily.poppinsRegular, - }, - loginButton: { - backgroundColor: colors.primary, - paddingHorizontal: 32, - paddingVertical: 14, - borderRadius: 12, - shadowColor: colors.primary, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.3, - shadowRadius: 8, - elevation: 4, - }, - loginButtonText: { - color: colors.white, - fontSize: 16, - fontWeight: '600', - fontFamily: FontsFamily.poppinsSemiBold, - }, -}); +/* ─── Styles factory ─── */ +const makeStyles = (colors: any, spacing: any, shadows: any, TAB_WIDTH: number, TAB_HEIGHT: number) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + + /* ── Tab Row ── */ + topRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: spacing.m, + paddingVertical: spacing.s + 2, + }, + switchWrapper: { + flexDirection: 'row', + backgroundColor: colors.surface, + borderRadius: 16, + padding: SWITCH_INSETS, + height: TAB_HEIGHT, + ...shadows.light, + }, + tab: { + height: TAB_HEIGHT - SWITCH_INSETS * 2, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 5, + zIndex: 2, + }, + tabText: { + fontWeight: '600', + letterSpacing: 0.3, + } as any, + activeTabText: { + fontWeight: '700', + } as any, + activeTab: { + position: 'absolute', + top: SWITCH_INSETS, + left: SWITCH_INSETS, + height: TAB_HEIGHT - SWITCH_INSETS * 2, + backgroundColor: colors.background, + borderRadius: 13, + ...shadows.medium, + }, + + /* ── Cart Floating Button ── */ + cartFloatingButton: { + position: 'absolute', + bottom: spacing.xl + 80, + right: spacing.l, + width: 56, + height: 56, + borderRadius: 28, + backgroundColor: colors.primary, + alignItems: 'center', + justifyContent: 'center', + ...shadows.strong, + zIndex: 999, + }, + cartBadge: { + position: 'absolute', + top: 0, + right: 0, + minWidth: 20, + height: 20, + borderRadius: 10, + backgroundColor: colors.background, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 4, + borderWidth: 1.5, + borderColor: colors.primary, + }, + cartBadgeText: { + fontSize: 10, + fontWeight: '800', + lineHeight: 13, + } as any, + + /* ── Restricted Access ── */ + restrictedContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: spacing.l, + backgroundColor: colors.background, + }, + restrictedCard: { + backgroundColor: colors.surface, + borderRadius: 28, + padding: spacing.l, + alignItems: 'center', + width: '100%', + maxWidth: 400, + ...shadows.strong, + }, + lockRing: { + width: 100, + height: 100, + borderRadius: 50, + backgroundColor: `${colors.primary}12`, + alignItems: 'center', + justifyContent: 'center', + marginBottom: spacing.l, + borderWidth: 1.5, + borderColor: `${colors.primary}25`, + }, + lockInner: { + width: 72, + height: 72, + borderRadius: 36, + backgroundColor: `${colors.primary}18`, + alignItems: 'center', + justifyContent: 'center', + }, + restrictedTitle: { + marginBottom: spacing.s + 4, + } as any, + restrictedMessage: { + marginBottom: spacing.s, + lineHeight: 24, + } as any, + hintBox: { + backgroundColor: colors.overlay, + borderRadius: 14, + paddingHorizontal: spacing.m, + paddingVertical: spacing.s + 2, + marginBottom: spacing.l, + width: '100%', + }, + loginButton: { + backgroundColor: colors.buttonPrimary, + paddingHorizontal: spacing.l, + paddingVertical: 14, + borderRadius: 16, + width: '100%', + alignItems: 'center', + ...shadows.medium, + }, + loginBtnText: { + letterSpacing: 1.2, + } as any, + }); diff --git a/src/Screens/Tender/tenderBidding/TenderBidding.tsx b/src/Screens/Tender/tenderBidding/TenderBidding.tsx index 17e74c4..e61dbcc 100644 --- a/src/Screens/Tender/tenderBidding/TenderBidding.tsx +++ b/src/Screens/Tender/tenderBidding/TenderBidding.tsx @@ -2,21 +2,24 @@ import { ActivityIndicator, Alert, FlatList, + KeyboardAvoidingView, + Platform, ScrollView, StyleSheet, - Text, ToastAndroid, TouchableOpacity, View, } from 'react-native'; import React, { useState } from 'react'; +import Animated, { FadeInUp, FadeInDown, SlideInRight } from 'react-native-reanimated'; import { useNavigation, useRoute } from '@react-navigation/native'; import MyInput from '../../../../src/Component/MyInput'; import { pick, types } from '@react-native-documents/picker'; import { InsideHeader } from '../../../Component/Index'; -import colors from '../../../Constant/Color'; +import { useTheme } from '../../../Hooks/useTheme'; +import AppText from '../../../components/AppText'; import { SUBMIT_ENVELOPE_A, SUBMIT_ENVELOPE_B, SUBMIT_ENVELOPE_FILE_A, SUBMIT_ENVELOPE_FILE_B, SUBMIT_QCBS_AND_LCS } from '../../../Services/ApiCall/ApiUrls'; -import { CheckSquare, Square } from 'lucide-react-native'; +import { CheckSquare, Square, FileUp, ChevronRight, AlertCircle } from 'lucide-react-native'; import { apiCall } from '../../../Axios/Axios'; import EnvelopeCExcelPreview from '../Component/EnvelopeCExcelPreview'; import { useFetchTenderById, useProceedToBid } from '../../../Services/ApiCall/Hooks'; @@ -42,6 +45,7 @@ interface EnvelopeButtonProps { interface InfoRowProps { title: string; value: string; + index?: number; } @@ -62,11 +66,10 @@ interface SaveEnvelopeOfficerDetail { const TenderBidding = () => { const route = useRoute(); const navigation = useNavigation(); - + const { colors, spacing, shadows, typography } = useTheme(); const tenderId = useState(route.params?.tenderId || null); - const { data: tenderResponse, refetch: tenderRefetch }: any = useFetchTenderById(route.params?.tenderId); const bidData = tenderResponse?.data; const { data: tender, refetch } = useProceedToBid(route.params?.tenderId); @@ -103,31 +106,44 @@ const TenderBidding = () => { // State for LCS/QCBS selected options - stores {questionId: selectedOption} const [selectedOptions, setSelectedOptions] = useState>({}); + const styles = makeStyles(colors, spacing, shadows); - const InfoRow = ({ title, value }: InfoRowProps) => ( - - {title} - {value || '-'} - - ); + // ── Sub-components ────────────────────────────────────────────── - - const EnvelopeButton = ({ title, envelopeType, onPress }: EnvelopeButtonProps & { envelopeType: EnvelopeType }) => ( - ( + - {title} - + {title} + {value || '-'} + ); + const EnvelopeButton = ({ title, envelopeType, onPress }: EnvelopeButtonProps & { envelopeType: EnvelopeType }) => { + const isActive = activeEnvelope === envelopeType; + return ( + + + {title} + + + ); + }; + + const handleEnvelopeChange = ( envelopeType: EnvelopeType, sequenceId: number, @@ -203,80 +219,89 @@ const TenderBidding = () => { return ( - + - - {labelName || '-'} - {item?.[`labelMandatory${activeEnvelope}`] && *} + {/* Label */} + + + {labelName || '-'} + + {item?.[`labelMandatory${activeEnvelope}`] && ( + + * + + )} + + {/* Input directly below the label, inside the same card */} + {showInput ? ( + + {labelType === '1' && ( + + handleEnvelopeChange(envelopeType, sequenceId, text) + } + keyboardType="numeric" + /> + )} + + {labelType === '2' && ( + + handleEnvelopeChange(envelopeType, sequenceId, text) + } + keyboardType="default" + /> + )} + + {labelType === '3' && ( + <> + pickPdf(activeEnvelope, sequenceId)} + activeOpacity={0.7} + > + + + {envelopForm?.[activeEnvelope]?.[sequenceId] + ? 'Change Document' + : 'Upload Document'} + + + + {envelopForm?.[activeEnvelope]?.[sequenceId] && ( + + 📄 {(envelopForm[activeEnvelope][sequenceId] as any)?.name} + + )} + + )} + + {labelType === '4' && ( + + handleEnvelopeChange(envelopeType, sequenceId, text) + } + keyboardType="numeric" + /> + )} + + ) : ( + + Envelope {activeEnvelope} unavailable + + )} - - - {showInput ? ( - <> - {labelType === '1' && ( - - handleEnvelopeChange(envelopeType, sequenceId, text) - } - keyboardType="numeric" - /> - )} - - - {labelType === '2' && ( - - handleEnvelopeChange(envelopeType, sequenceId, text) - } - keyboardType="default" - /> - )} - - - {labelType === '3' && ( - <> - pickPdf(activeEnvelope, sequenceId)} - > - - {envelopForm?.[activeEnvelope]?.[sequenceId] - ? 'Change Document' - : 'Upload Document'} - - - - - {envelopForm?.[activeEnvelope]?.[sequenceId] && ( - - {(envelopForm[activeEnvelope][sequenceId] as any)?.name} - - )} - - )} - - - {labelType === '4' && ( - - handleEnvelopeChange(envelopeType, sequenceId, text) - } - keyboardType="numeric" - /> - )} - - ) : Envelope {activeEnvelope} unavailable} - + ); }; @@ -286,36 +311,50 @@ const TenderBidding = () => { const selectedOption = selectedOptions[questionId]; return ( - - {item.question} + + + {item.question} + {item.parameters?.map((param: any, index: number) => { const isSelected = selectedOption?.option === param.option; return ( - handleOptionSelect(questionId, param)} + entering={SlideInRight.delay(index * 80).springify()} > - - - {isSelected && } + handleOptionSelect(questionId, param)} + activeOpacity={0.7} + > + + + {isSelected && } + + + + {param.option} + + - - - {param.option} - - {/* Mark: {param.mark} */} - - - + + ); })} - + ); }; @@ -409,7 +448,7 @@ const TenderBidding = () => { }; - const buildEnvelopePayload = () => { + const buildEnvelopePayload = (needCData: Boolean = false) => { console.log('\n🏗️ BUILDING ENVELOPE PAYLOAD'); console.log('═══════════════════════════════════════════════════════'); console.log('📋 Active Envelope:', activeEnvelope); @@ -436,66 +475,50 @@ const TenderBidding = () => { console.log('\n🔐 ENCRYPTING FIELD DATA'); - console.log('───────────────────────────────────────────────────────'); - const saveData = list - .filter((item: any) => item[`labelType${activeEnvelope}`] !== '3') + console.log(`─────────────────────────────────────────────────────── ${list}`); + + const combinedData = list + .filter((item: any) => { + const labelType = item[`labelType${activeEnvelope}`]; + return needCData ? labelType === '3' : labelType !== '3'; + }) .map((item: any, index: number) => { const seqId = item[`tenderEnvelope${activeEnvelope}SequnceId`]; - const rawValue = formValues[seqId]; + const labelType = item[`labelType${activeEnvelope}`]; const labelName = item[`labelName${activeEnvelope}`]; + const isMandatory = item[`labelMandatory${activeEnvelope}`]; + const rawValue = formValues[seqId]; + console.log(`\n Field ${index + 1}: ${labelName} (ID: ${seqId}, Type: ${labelType})`); - console.log(`\n Field ${index + 1}: ${labelName} (ID: ${seqId})`); - console.log(` Raw Value: "${rawValue}"`); + if (labelType === '3') { + console.log(` 📎 File Field detected`); + return { + [`tenderEnvelope${activeEnvelope}SequnceId`]: seqId, + [`labelName${activeEnvelope}`]: labelName, + [`labelType${activeEnvelope}`]: labelType, + [`labelMandatory${activeEnvelope}`]: isMandatory, + }; + } else { + console.log(` Raw Value: "${rawValue}"`); + const encryptedValue = rawValue ? encrypt(String(rawValue), ENCRYPTION_KEY) : ''; + const valueInWords = rawValue ? convertDigitsToWords(String(rawValue)) : ''; + const encryptedValueInWord = valueInWords ? encrypt(valueInWords, ENCRYPTION_KEY) : ''; + console.log(` Encrypted Value: ${encryptedValue ? encryptedValue.substring(0, 30) + '...' : 'N/A'}`); - const encryptedValue = rawValue - ? encrypt(String(rawValue), ENCRYPTION_KEY) - : ''; - - - console.log(` Encrypted Value: ${encryptedValue ? encryptedValue.substring(0, 30) + '...' : 'N/A'}`); - - - const valueInWords = rawValue - ? convertDigitsToWords(String(rawValue)) - : ''; - const encryptedValueInWord = valueInWords - ? encrypt(valueInWords, ENCRYPTION_KEY) - : ''; - - - console.log(` Encrypted Words: ${encryptedValueInWord ? encryptedValueInWord.substring(0, 30) + '...' : 'N/A'}`); - - - return { - [`tenderEnvelope${activeEnvelope}SequnceId`]: seqId, - [`labelName${activeEnvelope}`]: labelName, - [`labelType${activeEnvelope}`]: item[`labelType${activeEnvelope}`], - [`labelMandatory${activeEnvelope}`]: item[`labelMandatory${activeEnvelope}`], - fieldValue: encryptedValue, - fieldValueInWord: encryptedValueInWord, - }; + return { + [`tenderEnvelope${activeEnvelope}SequnceId`]: seqId, + [`labelName${activeEnvelope}`]: labelName, + [`labelType${activeEnvelope}`]: labelType, + [`labelMandatory${activeEnvelope}`]: isMandatory, + fieldValue: encryptedValue, + fieldValueInWord: encryptedValueInWord, + }; + } }); - - console.log('\n✅ Field encryption completed:', saveData.length, 'fields'); - - - const fileData = list - .filter((item: any) => item[`labelType${activeEnvelope}`] === '3') - .map((item: any) => { - const seqId = item[`tenderEnvelope${activeEnvelope}SequnceId`]; - console.log(`📎 File Field: ${item[`labelName${activeEnvelope}`]} (ID: ${seqId})`); - - - return { - [`tenderEnvelope${activeEnvelope}SequnceId`]: seqId, - [`labelName${activeEnvelope}`]: item[`labelName${activeEnvelope}`], - [`labelType${activeEnvelope}`]: item[`labelType${activeEnvelope}`], - [`labelMandatory${activeEnvelope}`]: item[`labelMandatory${activeEnvelope}`], - }; - }); + console.log('\n✅ Field processing completed:', combinedData.length, 'fields'); const officerEncryptedKeys = buildOfficerEncryptedKeys(ENCRYPTION_KEY); @@ -516,14 +539,11 @@ const TenderBidding = () => { ], - [`save${activeEnvelope}Data`]: envelopForm?.[activeEnvelope]?.[3] - ? fileData - : saveData, - + [`save${activeEnvelope}Data`]: combinedData, deviceType: 'Mobile', - + randomKeyGenerated: ENCRYPTION_KEY, saveEnvelopesOfficersDetailsResDTO: officerEncryptedKeys, }; @@ -546,58 +566,58 @@ const TenderBidding = () => { }; // Build payload for LCS/QCBS submission -const buildQcbsPayload = () => { - console.log('\n🏗️ BUILDING QCBS/LCS PAYLOAD'); - console.log('═══════════════════════════════════════════════════════'); - - const questions = tenderData?.tenderEnvelopeQcbsList?.questions || []; - - // Get the encryption key for LCS/QCBS - const ENCRYPTION_KEY = tenderData?.envelopeKeyDetailsResDTO?.envelopeQcbsAndLcs || ''; - - if (!ENCRYPTION_KEY) { - console.error('❌ ENCRYPTION KEY NOT FOUND FOR', activeEnvelope); - Alert.alert('Error', 'Encryption key not available'); - return null; - } - - console.log(`🔑 Using encryption key for ${activeEnvelope}:`, ENCRYPTION_KEY); - - // Build officer encrypted keys - const officerEncryptedKeys = buildOfficerEncryptedKeys(ENCRYPTION_KEY); - - // Map questions with ONLY the selected parameter - const questionPayload = questions.map((question: any) => { - const selected = selectedOptions[question.questionId]; - - // Only include the selected parameter in the array - const parameters = selected ? [{ - option: encrypt(String(selected.option), ENCRYPTION_KEY), - mark: selected.mark, - strUser: encrypt(String(selected.strUser || "1"), ENCRYPTION_KEY) - }] : []; - - return { - questionId: question.questionId, - question: question.question, - parameters: parameters // Only contains the selected option + const buildQcbsPayload = () => { + console.log('\n🏗️ BUILDING QCBS/LCS PAYLOAD'); + console.log('═══════════════════════════════════════════════════════'); + + const questions = tenderData?.tenderEnvelopeQcbsList?.questions || []; + + // Get the encryption key for LCS/QCBS + const ENCRYPTION_KEY = tenderData?.envelopeKeyDetailsResDTO?.envelopeQcbsAndLcs || ''; + + if (!ENCRYPTION_KEY) { + console.error('❌ ENCRYPTION KEY NOT FOUND FOR', activeEnvelope); + Alert.alert('Error', 'Encryption key not available'); + return null; + } + + console.log(`🔑 Using encryption key for ${activeEnvelope}:`, ENCRYPTION_KEY); + + // Build officer encrypted keys + const officerEncryptedKeys = buildOfficerEncryptedKeys(ENCRYPTION_KEY); + + // Map questions with ONLY the selected parameter + const questionPayload = questions.map((question: any) => { + const selected = selectedOptions[question.questionId]; + + // Only include the selected parameter in the array + const parameters = selected ? [{ + option: encrypt(String(selected.option), ENCRYPTION_KEY), + mark: selected.mark, + strUser: encrypt(String(selected.strUser || "1"), ENCRYPTION_KEY) + }] : []; + + return { + questionId: question.questionId, + question: question.question, + parameters: parameters // Only contains the selected option + }; + }); + + const payload = { + tenderId: tenderData?.tenderId, + templateId: tenderData?.tenderEnvelopeQcbsList?.templateId, + qcbsTemplateName: tenderData?.tenderEnvelopeQcbsList?.qcbsTemplateName || "string", + questions: questionPayload, + deviceType: 'Mobile', + saveEnvelopesOfficersDetailsResDTO: officerEncryptedKeys, }; - }); - - const payload = { - tenderId: tenderData?.tenderId, - templateId: tenderData?.tenderEnvelopeQcbsList?.templateId, - qcbsTemplateName: tenderData?.tenderEnvelopeQcbsList?.qcbsTemplateName || "string", - questions: questionPayload, - deviceType: 'Mobile', - saveEnvelopesOfficersDetailsResDTO: officerEncryptedKeys, + + console.log('📋 QCBS/LCS PAYLOAD:', JSON.stringify(payload, null, 2)); + console.log('═══════════════════════════════════════════════════════\n'); + + return payload; }; - - console.log('📋 QCBS/LCS PAYLOAD:', JSON.stringify(payload, null, 2)); - console.log('═══════════════════════════════════════════════════════\n'); - - return payload; -}; @@ -651,17 +671,14 @@ const buildQcbsPayload = () => { const payload = buildEnvelopePayload(); - console.log('📦 Payload generated:', payload ? 'SUCCESS' : 'FAILED'); + console.log('📦 Payload generated: ', payload); + const envelopeList = tenderData?.[`tenderEnvelope${activeEnvelope}List`] || []; + const fileField = envelopeList.find((item: any) => item[`labelType${activeEnvelope}`] === '3'); + const fileSeqId = fileField ? fileField[`tenderEnvelope${activeEnvelope}SequnceId`] : null; + const hasFile = fileSeqId ? !!envelopForm?.[activeEnvelope]?.[fileSeqId] : false; - if (!payload) { - console.log('❌ Payload build failed'); - setLoading(false); - return; - } - - - if (envelopForm?.[activeEnvelope]?.[3]) { + if (hasFile) { console.log('📎 File upload required - Uploading file first...'); const result = await handleSubmitEnvelopeFile(SUBMIT_ENVELOPE_FILE_A); @@ -732,17 +749,15 @@ const buildQcbsPayload = () => { const payload = buildEnvelopePayload(); - console.log('📦 Payload generated:', payload ? 'SUCCESS' : 'FAILED'); + console.log(`📦 Payload generated: ${JSON.stringify(payload)}`); + + const envelopeListB = tenderData?.[`tenderEnvelope${activeEnvelope}List`] || []; + const fileFieldB = envelopeListB.find((item: any) => item[`labelType${activeEnvelope}`] === '3'); + const fileSeqIdB = fileFieldB ? fileFieldB[`tenderEnvelope${activeEnvelope}SequnceId`] : null; + const hasFileB = fileSeqIdB ? !!envelopForm?.[activeEnvelope]?.[fileSeqIdB] : false; - if (!payload) { - console.log('❌ Payload build failed'); - setLoading(false); - return; - } - - - if (envelopForm?.[activeEnvelope]?.[3]) { + if (hasFileB) { console.log('📎 File upload required - Uploading file first...'); const result = await handleSubmitEnvelopeFile(SUBMIT_ENVELOPE_FILE_B); @@ -818,7 +833,7 @@ const buildQcbsPayload = () => { const payload = buildQcbsPayload(); try { - + console.log('📤 Submitting to:', SUBMIT_QCBS_AND_LCS); const res = await apiCall('post', SUBMIT_QCBS_AND_LCS, payload) as any; @@ -848,7 +863,7 @@ const buildQcbsPayload = () => { console.log('URL:', url); - const payload = buildEnvelopePayload(); + const payload = buildEnvelopePayload(true); if (!payload) { @@ -857,7 +872,16 @@ const buildQcbsPayload = () => { } - const fileDetail: any = envelopForm?.[activeEnvelope]?.[3]; + const list = tenderData?.[`tenderEnvelope${activeEnvelope}List`] || []; + const fileField = list.find((item: any) => item[`labelType${activeEnvelope}`] === '3'); + const seqId = fileField ? fileField[`tenderEnvelope${activeEnvelope}SequnceId`] : null; + + if (!seqId || !envelopForm?.[activeEnvelope]?.[seqId]) { + console.error('❌ No file found to upload for envelope', activeEnvelope); + return null; + } + + const fileDetail: any = envelopForm?.[activeEnvelope]?.[seqId]; console.log('📄 File Details:'); console.log(' Name:', fileDetail?.name); console.log(' Type:', fileDetail?.type); @@ -889,227 +913,321 @@ const buildQcbsPayload = () => { }; + // ── Render ─────────────────────────────────────────────────────── + return ( - + - - {/* Tender Info */} - - - - - - - - - - - - - - {/* Envelope Buttons */} - - Bid Envelopes - - {tenderData?.showEA && ( - { - console.log('🔄 Switched to Envelope A'); - setActiveEnvelope('A'); - }} - /> - )} - {tenderData?.showEB && ( - { - console.log('🔄 Switched to Envelope B'); - setActiveEnvelope('B'); - }} - /> - )} - {tenderData?.showLCS && ( - { - console.log('🔄 Switched to Envelope LCS'); - setActiveEnvelope('LCS'); - }} - /> - )} - {tenderData?.showQCBS && ( - { - console.log('🔄 Switched to Envelope QCBS'); - setActiveEnvelope('QCBS'); - }} - /> - )} - {tenderData?.showEC && ( - { - console.log('🔄 Switched to Envelope C'); - setActiveEnvelope('C'); - }} - /> - )} - - - - - {/* Envelope A/B List */} - {tenderData?.[`tenderEnvelope${activeEnvelope}List`]?.length > 0 && ( - - Envelope {activeEnvelope} Details - item?.[`tenderEnvelope${activeEnvelope}SequnceId`]?.toString()} - renderItem={({ item }) => renderEnvelopeItem({ item, envelopeType: activeEnvelope })} - scrollEnabled={false} - /> - - - - { - setCheck(!check); - console.log('☑️ Terms & Conditions checked:', !check); - }}> - {check ? ( - - ) : ( - - )} - - Check First Terms & Condition + + + {/* ═══════════ TENDER INFO HEADER CARD ═══════════ */} + + + + + {bidData?.tenderNumber || 'Tender'} + + + Tender Bidding + + - - )} - {/* LCS/QCBS Questions */} - {(tenderData?.showLCS || tenderData?.showQCBS) && - (tenderData?.tenderEnvelopeQcbsList?.questions?.length > 0) && - (activeEnvelope === 'LCS' || activeEnvelope === 'QCBS') && ( - - - {activeEnvelope} Questions (Select One Option Per Question) - - - Please select the most appropriate option for each question - - - item.questionId.toString()} - renderItem={renderQcbsQuestion} - scrollEnabled={false} - /> - - - { - setCheck(!check); - console.log('☑️ Terms & Conditions checked:', !check); - }}> - {check ? ( - - ) : ( - - )} - - Check First Terms & Condition + + + NIT No. + + {bidData?.nitNo || '-'} + + + + + EMD Amount + + ₹{bidData?.emdInFig || '-'} + + - - )} + + + + Re-Bid Allowed + + {bidData?.tenderRebid ? 'Yes' : 'No'} + + + + + Bid Withdraw + + {bidData?.bidWithdraw || '-'} + + + + - {activeEnvelope === 'C' && ( - - Envelope C Status - - - {tenderData?.envelopeCSubmittStatus ? ( - <> - - Envelope C has been submitted successfully. - - {tenderData?.envelopeC_Logs && ( - <> - Date: {tenderData.envelopeC_Logs.dateTime} - Message: {tenderData.envelopeC_Logs.logMessage} - + {/* ═══════════ ENVELOPE TABS ═══════════ */} + + + Bid Envelopes + + + + {tenderData?.showEA && ( + { + console.log('🔄 Switched to Envelope A'); + setActiveEnvelope('A'); + }} + /> )} - - ) : ( - - )} - - )} - - - - {(activeEnvelope === 'LCS' || activeEnvelope === 'QCBS') && - tenderData?.tenderEnvelopeQcbsList?.questions?.length > 0 ? ( - - - {loading ? ( - - ) : ( - `Submit ${activeEnvelope}` + {tenderData?.showEB && ( + { + console.log('🔄 Switched to Envelope B'); + setActiveEnvelope('B'); + }} + /> )} - - - ) : hasEnvelopes() && activeEnvelope !== 'C' ? ( - { - if (activeEnvelope === 'A') { - if (!check) { - console.log('⚠️ Terms & Conditions not checked'); - Alert.alert('Info', 'Check first terms & condition'); - return; - } - handleSubmitEnvelopeA(); - } else if (activeEnvelope === 'B') { - if (!check) { - console.log('⚠️ Terms & Conditions not checked'); - Alert.alert('Info', 'Check first terms & condition'); - return; - } - handleSubmitEnvelopeB(); - } - }} + {tenderData?.showLCS && ( + { + console.log('🔄 Switched to Envelope LCS'); + setActiveEnvelope('LCS'); + }} + /> + )} + {tenderData?.showQCBS && ( + { + console.log('🔄 Switched to Envelope QCBS'); + setActiveEnvelope('QCBS'); + }} + /> + )} + {tenderData?.showEC && ( + { + console.log('🔄 Switched to Envelope C'); + setActiveEnvelope('C'); + }} + /> + )} + + + + + + {/* ═══════════ ENVELOPE A/B DETAILS ═══════════ */} + {tenderData?.[`tenderEnvelope${activeEnvelope}List`]?.length > 0 && ( + - - {loading ? : `Submit Bid (${activeEnvelope})`} - - - ) : ( - { - console.log('⬅️ Going back'); - navigation.goBack(); - }} - > - Go Back - + + Envelope {activeEnvelope} Details + + + item?.[`tenderEnvelope${activeEnvelope}SequnceId`]?.toString()} + renderItem={({ item }) => renderEnvelopeItem({ item, envelopeType: activeEnvelope })} + scrollEnabled={false} + /> + + + { + setCheck(!check); + console.log('☑️ Terms & Conditions checked:', !check); + }}> + {check ? ( + + ) : ( + + )} + + + Accept Terms & Conditions + + + + )} - - + + + {/* ═══════════ LCS/QCBS QUESTIONS ═══════════ */} + {(tenderData?.showLCS || tenderData?.showQCBS) && + (tenderData?.tenderEnvelopeQcbsList?.questions?.length > 0) && + (activeEnvelope === 'LCS' || activeEnvelope === 'QCBS') && ( + + + {activeEnvelope} Questions + + + + Select the most appropriate option for each question + + + item.questionId.toString()} + renderItem={renderQcbsQuestion} + scrollEnabled={false} + /> + + + { + setCheck(!check); + console.log('☑️ Terms & Conditions checked:', !check); + }}> + {check ? ( + + ) : ( + + )} + + + Accept Terms & Conditions + + + + + )} + + + {/* ═══════════ ENVELOPE C ═══════════ */} + {activeEnvelope === 'C' && ( + + + Envelope C Status + + + {tenderData?.envelopeCSubmittStatus ? ( + <> + + + ✅ Submitted Successfully + + + {tenderData?.envelopeC_Logs && ( + + + Date & Time: + {tenderData.envelopeC_Logs.dateTime} + + + Message: + {tenderData.envelopeC_Logs.logMessage} + + + )} + + ) : ( + + )} + + + )} + + + + + + + + {/* ═══════════ FOOTER CTA ═══════════ */} + + {(activeEnvelope === 'LCS' || activeEnvelope === 'QCBS') && + tenderData?.tenderEnvelopeQcbsList?.questions?.length > 0 ? ( + + {loading ? ( + + ) : ( + + Submit {activeEnvelope} + + )} + + ) : hasEnvelopes() && activeEnvelope !== 'C' ? ( + { + if (activeEnvelope === 'A') { + if (!check) { + console.log('⚠️ Terms & Conditions not checked'); + Alert.alert('Info', 'Check first terms & condition'); + return; + } + handleSubmitEnvelopeA(); + } else if (activeEnvelope === 'B') { + if (!check) { + console.log('⚠️ Terms & Conditions not checked'); + Alert.alert('Info', 'Check first terms & condition'); + return; + } + handleSubmitEnvelopeB(); + } + }} + disabled={loading} + activeOpacity={0.85} + > + {loading ? ( + + ) : ( + + Submit Bid ({activeEnvelope}) + + )} + + ) : ( + // + // + // + // No submission available for this envelope + // + // + null + )} + ); }; @@ -1118,116 +1236,115 @@ const buildQcbsPayload = () => { export default TenderBidding; -const styles = StyleSheet.create({ +// ── Styles ──────────────────────────────────────────────────────────── +const makeStyles = (colors: any, spacing: any, shadows: any) => StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.white + padding: spacing.m, }, - scrollContainer: { - padding: 16, - paddingBottom: 100, + headerCard: { + backgroundColor: colors.surface, + borderRadius: 16, + padding: spacing.m, + marginBottom: spacing.m, }, - loadingContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - card: { - backgroundColor: colors.primaryHighlight, - borderRadius: 12, - padding: 16, - marginBottom: 16, - elevation: 2, - shadowColor: '#000', - shadowOffset: { width: 0, height: 1 }, - shadowOpacity: 0.1, - shadowRadius: 2, - }, - sectionTitle: { - fontSize: 16, - fontWeight: '600', - marginBottom: 12, - color: '#333', - }, - row: { + tenderHeader: { flexDirection: 'row', justifyContent: 'space-between', - marginBottom: 8, + alignItems: 'center', }, - infoRow: { + headerInfoRow: { + flexDirection: 'row', + alignItems: 'center', + borderRadius: 10, + padding: spacing.s, + marginTop: spacing.m, + }, + headerInfoItem: { flex: 1, - marginBottom: 10, + alignItems: 'center', }, - label: { - fontSize: 13, - color: '#777', - marginBottom: 2, + separator: { + width: 1, + height: 30, }, - value: { - fontSize: 15, - color: '#222', - fontWeight: '500', + section: { + marginBottom: spacing.m, + }, + envelopeTabsCard: { + backgroundColor: colors.surface, + borderRadius: 12, + padding: spacing.m, }, envelopeRow: { flexDirection: 'row', - gap: 10, + gap: spacing.s, flexWrap: 'wrap', }, envelopeButton: { - backgroundColor: colors.secondaryHighLight, paddingVertical: 10, - paddingHorizontal: 14, - borderRadius: 8, + paddingHorizontal: 16, + borderRadius: 20, + borderWidth: 1.5, + borderColor: colors.border, + backgroundColor: colors.surface, minWidth: 100, alignItems: 'center', }, - envelopeButtonText: { - color: colors.primary, - fontWeight: '600', - fontSize: 14, + envelopeButtonActive: { + backgroundColor: colors.primary, + borderColor: colors.primary, + }, + detailsCard: { + backgroundColor: colors.surface, + borderRadius: 12, + padding: spacing.m, }, envelopeItem: { - borderWidth: 1, - borderColor: '#eee', + backgroundColor: colors.surface, borderRadius: 10, - padding: 12, - marginBottom: 12, - backgroundColor: colors.primaryHighlight, + padding: spacing.m, + marginBottom: spacing.s, + borderWidth: 1, + borderColor: colors.border, + borderLeftWidth: 3, + borderLeftColor: colors.primary, + }, + uploadButton: { flexDirection: 'row', - justifyContent: 'space-between' + alignItems: 'center', + gap: 8, + paddingVertical: 10, + paddingHorizontal: 16, + borderRadius: 10, + borderWidth: 1.5, + borderColor: colors.primary, + backgroundColor: colors.surface, + marginTop: spacing.s, + alignSelf: 'flex-start', }, questionCard: { - backgroundColor: '#f9f9f9', - borderRadius: 10, - padding: 14, - marginBottom: 16, + backgroundColor: colors.surface, + borderRadius: 12, + padding: spacing.m, + marginBottom: spacing.m, borderWidth: 1, - borderColor: '#e0e0e0', - }, - questionText: { - fontSize: 16, - fontWeight: '600', - color: '#333', - marginBottom: 12, - }, - instructionText: { - fontSize: 13, - color: '#666', - marginBottom: 12, - fontStyle: 'italic', + borderColor: colors.border, + borderLeftWidth: 3, + borderLeftColor: colors.primary, }, optionContainer: { - backgroundColor: '#fff', - borderRadius: 8, - padding: 12, - marginBottom: 8, - borderWidth: 1, - borderColor: '#ddd', + backgroundColor: colors.surface, + borderRadius: 10, + padding: spacing.m, + marginBottom: spacing.s, + borderWidth: 1.5, + borderColor: colors.border, }, optionContainerSelected: { borderColor: colors.primary, borderWidth: 2, - backgroundColor: colors.secondaryHighLight, + backgroundColor: `${colors.primary}08`, }, optionRow: { flexDirection: 'row', @@ -1235,63 +1352,65 @@ const styles = StyleSheet.create({ gap: 12, }, radioButton: { - width: 20, - height: 20, - borderRadius: 10, + width: 22, + height: 22, + borderRadius: 11, borderWidth: 2, - borderColor: colors.primary, + borderColor: colors.border, justifyContent: 'center', alignItems: 'center', }, radioButtonInner: { - width: 10, - height: 10, - borderRadius: 5, + width: 11, + height: 11, + borderRadius: 6, backgroundColor: colors.primary, }, - optionText: { - fontSize: 14, - color: '#333', - marginBottom: 4, + infoRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: spacing.s, + borderBottomWidth: StyleSheet.hairlineWidth, }, - optionTextSelected: { - fontWeight: '600', - color: colors.primary, + termsRow: { + flexDirection: 'row', + gap: 12, + alignItems: 'center', + marginTop: spacing.m, + paddingTop: spacing.m, + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: colors.border, }, - markText: { - fontSize: 12, - color: '#666', + statusBadge: { + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 20, + alignSelf: 'flex-start', + marginBottom: spacing.m, + }, + logDetailRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 6, + }, + footerSpace: { + height: 80, }, footer: { - flexDirection: 'row', - justifyContent: 'flex-end', - marginVertical: 20, + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + backgroundColor: colors.surface, + padding: spacing.m, + borderTopWidth: 1, }, - primaryButton: { + proceedButton: { backgroundColor: colors.primary, paddingVertical: 14, - paddingHorizontal: 28, - borderRadius: 10, - minWidth: 120, + borderRadius: 12, alignItems: 'center', }, - primaryButtonText: { - color: '#fff', - fontWeight: '600', - fontSize: 15, - }, - secondaryButton: { - borderWidth: 1, - borderColor: '#EEF2FF', - paddingVertical: 14, - paddingHorizontal: 28, - borderRadius: 10, - minWidth: 120, - alignItems: 'center', - }, - secondaryButtonText: { - color: colors.primary, - fontWeight: '600', - fontSize: 15, - }, }); \ No newline at end of file diff --git a/src/Screens/index.ts b/src/Screens/index.ts index 0b7ab9e..f1ed94f 100644 --- a/src/Screens/index.ts +++ b/src/Screens/index.ts @@ -22,6 +22,8 @@ import PurchaseTenderList from "./Tender/PurchaseTenderList"; import Tender from "./Tender/Tender"; import TenderBidding from "./Tender/tenderBidding/TenderBidding"; import DecryptionScreen from "./TestScreen/DecryptionScreen"; +import DeviceBinding from "./Tender/DeviceBinding"; +import DscBinding from "./Tender/DscBinding"; const Screens = { @@ -48,8 +50,8 @@ const Screens = { ResetPassword, KycWebViewScreen, StageThreeScreen, - DecryptionScreen - - + DecryptionScreen, + DeviceBinding, + DscBinding } export default Screens \ No newline at end of file diff --git a/src/Services/ApiCall/ApiUrls.tsx b/src/Services/ApiCall/ApiUrls.tsx index a38b8ac..4388296 100644 --- a/src/Services/ApiCall/ApiUrls.tsx +++ b/src/Services/ApiCall/ApiUrls.tsx @@ -1,7 +1,8 @@ +// const BASE_URL = `http://192.168.6.194:8090/` const BASE_URL = `https://procurelinc.in/EProcurementSB/` -// const BASE_URL_Auction = `https://procurelinc.in/AuctionPostgre/api/auction/` +// const BASE_URL_Auction = `http://192.168.6.194/AuctionPostgre/api/auction/` export const BASE_URL_Auction = `https://procurelinc.in/Auction/` -// const BASE_URL = `https://192.168.29.226:8090/EProcurementSB/` +// const BASE_URL = `http://192.168.29.226:8090/` const GET_DESIGNATION = `${BASE_URL}contractor/getAllDesignationContractor` const SAVE_CONTRACTOR_STAGE1 = `${BASE_URL}contractor/registerContractorStage1` const GET_CONTRACTOR_CLASS = `${BASE_URL}contractor/allContractorClassSecond` @@ -24,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 = `https://procurelinc.in/EProcurementSB/eTendering/getTenderById?tenderId=` +const GET_TENDER_BY_ID = `http://192.168.6.194:8090/eTendering/getTenderById?tenderId=` const LOG_IN_URL = `${BASE_URL_Auction}auction/loginWithoutDecryption` const LIVE_TENDER = `${BASE_URL}liveTender/getAllLiveTenders` const LIVE_AUCTION = `${BASE_URL_Auction}api/auction/getLiveAuctionForMobile` @@ -32,22 +33,22 @@ const PURCHASE_AUCTION_PAGE = `${BASE_URL_Auction}api/auction/getMyAuctionForMob const PROCEED_PURCHASE_AUCTION = `${BASE_URL_Auction}api/auction/proceedToPurchase` const PURCHASE_AUCTION = `${BASE_URL_Auction}api/auction/purchaseAuction` const PURCHASE_OFFLINE_AUCTION = `${BASE_URL_Auction}api/auction/purchaseAuctionOffline` -const PURCHASE_TENDER = `https://procurelinc.in/EProcurementSB/tenderPayment/purchaseTender` -const PURCHASE_TENDER_OFFLINE = `https://procurelinc.in/EProcurementSB/tenderPayment/purchaseTenderOffline` -const AUCTION_PURCHASE_STATUS = `https://api.godemo.in/Auction/api/auction/transactionStatus?orderNo=` -const GET_TENDER_DETAILS = `https://procurelinc.in/EProcurementSB/tenderBidding/getTenderDetail?tenderId=` +const PURCHASE_TENDER = `${BASE_URL}tenderPayment/purchaseTender` +const PURCHASE_TENDER_OFFLINE = `${BASE_URL}tenderPayment/purchaseTenderOffline` +const AUCTION_PURCHASE_STATUS = `${BASE_URL_Auction}api/auction/transactionStatus?orderNo=` +const GET_TENDER_DETAILS = `${BASE_URL}tenderBidding/getTenderDetail?tenderId=` const GET_DOCID = `${BASE_URL}eTendering/download/docId?docId=` -// https://procurelinc.in/EProcurementSB/eTendering/download/docId?docId=158 +// http://192.168.6.194:8090/eTendering/download/docId?docId=158 // const DOWNLOAD_DOC = `${BASE_URL}eTende ring/download/docId?docId=` const DOWNLOAD_DOC = `${BASE_URL}eTendering/download/docId?docId=` const TWO_FACTOR_AUTH = `${BASE_URL}contractor/two-factor-auth?status=` -const AUCTION_LOGIN = `https://api.godemo.in/Auction/auction/loginWithoutDecryption` +const AUCTION_LOGIN = `${BASE_URL_Auction}auction/loginWithoutDecryption` const GET_AUCTION_DETAILS = `${BASE_URL_Auction}api/auction/getAuction/id` -const TANDER_PURCHASE_STATUS = `https://procurelinc.in/EProcurementSB/tenderPayment/transactionStatus?orderNo=` +const TANDER_PURCHASE_STATUS = `${BASE_URL}tenderPayment/transactionStatus?orderNo=` const GET_MY_AUCTION = `${BASE_URL_Auction}api/auction/getMyAuction` -const GET_MY_TENDER = "https://procurelinc.in/EProcurementSB/tenderBidding/getAllPurchasedTendersByConId" +const GET_MY_TENDER = `${BASE_URL}tenderBidding/getAllPurchasedTendersByConId` const GET_DOCID_AUCTION = `${BASE_URL_Auction}api/auction/viewDocumentsForAuction?auctionId=` -const DOWNLOAD_DOC_AUCTION = "https://api.godemo.in/Auction/api/auction/downloadDocumentForAuction?documentId=" +const DOWNLOAD_DOC_AUCTION = `${BASE_URL_Auction}api/auction/downloadDocumentForAuction?documentId` const FORARD_AUCTION_ITEM = `${BASE_URL_Auction}api/auction/getForwardAuctionItems/auctionNumber` const AUCTION_ITEM_SUBMIT = `${BASE_URL_Auction}api/auction/addEMDDocumentsItemWise` const EMD_CHECK = `${BASE_URL_Auction}api/auction/getEMDDocumentsForAuction` @@ -80,12 +81,12 @@ const PROCEED_TO_BID = `${BASE_URL}tenderBidding/proceedToBid?tenderId=` const SUBMIT_ENVELOPE_A = `${BASE_URL}tenderBidding/getSaveEnvelopeAData` const SUBMIT_ENVELOPE_B = `${BASE_URL}tenderBidding/getSaveEnvelopeBData` const SUBMIT_ENVELOPE_FILE_A = `${BASE_URL}tenderBidding/addDocumentsEnvelopeA` -const SUBMIT_ENVELOPE_FILE_B = `https://procurelinc.in/EProcurementSB/tenderBidding/addDocumentsEnvelopeB` +const SUBMIT_ENVELOPE_FILE_B = `${BASE_URL}tenderBidding/addDocumentsEnvelopeB` const GET_ENVELOPE_C = `${BASE_URL}eTendering/getEnvelopeCTemplate/tenderId?tenderId=` const SUBMIT_ENVELOPE_C_UPLOAD = `${BASE_URL}tenderBidding/getSaveEnvelopeCData` -const GET_CONTRACTOR_PROFILE =`${BASE_URL}contractor/getcontractorfulldetails` +const GET_CONTRACTOR_PROFILE = `${BASE_URL}contractor/getcontractorfulldetails` const ESETU_VARIFICATION = `${BASE_URL}tenderBidding/eSetuAdharVarification` -const TENDER_BID_DETAIL = `${BASE_URL}tenderBidding/viewToBid?tenderId=` +const TENDER_BID_DETAIL = `${BASE_URL}tenderBidding/viewToBid?tenderId=` const SUBMIT_QCBS_AND_LCS = `${BASE_URL}tenderBidding/getSaveEnvelopeQCBSData` diff --git a/src/Services/ApiCall/apis.ts b/src/Services/ApiCall/apis.ts index ae1b977..9bb4542 100644 --- a/src/Services/ApiCall/apis.ts +++ b/src/Services/ApiCall/apis.ts @@ -1,7 +1,9 @@ import axios from "axios"; -import { AUCTION_DOCUMENT_DOWNLOAD, DASHBOARD_COUNT, EMD_CHECK, FORARD_AUCTION_ITEM, GET_AUCTION_DETAILS, GET_MY_AUCTION, GET_MY_TENDER, GET_TENDER_DETAILS, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION, PROCEED_TO_BID, PURCHASE_TENDER_OFFLINE, TENDER_LOGIN, PURCHASE_AUCTION_LIST, PURCHASE_OFFLINE_AUCTION, REVERSE_AUCTION_ITEM, VIEW_AUCTION_DOCUMENTS, VIEW_AUCTION_ITEM_DETAIL, STAGETHREE, REJ_STAGE_TWO, GETCONTRACTORBYSTATE, GET_MASTER_DATA, ALL_DESIGNATION, GET_REGISTRATION_STATE, PARTNER_API_ENDPOINT, SAVE_CONTRACTOR_STAGE1, VERIFY_GST, VERIFY_ADHAR, SEND_ADHAROTP, SUBMIT_ENVELOPE_FILE_B, SUBMIT_ENVELOPE_FILE_A, SUBMIT_ENVELOPE_A, SUBMIT_ENVELOPE_B, VERIFY_ORGPAN, PURCHASE_AUCTION_PAGE, GET_TENDER_BY_ID, GET_CONTRACTOR_PROFILE, CONTRACTOR_PROFILE_UPDATE, - ESETU_VARIFICATION, TENDER_BID_DETAIL, - TENDER_DASHBOARD} from "./ApiUrls"; +import { + AUCTION_DOCUMENT_DOWNLOAD, DASHBOARD_COUNT, EMD_CHECK, FORARD_AUCTION_ITEM, GET_AUCTION_DETAILS, GET_MY_AUCTION, GET_MY_TENDER, GET_TENDER_DETAILS, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION, PROCEED_TO_BID, PURCHASE_TENDER_OFFLINE, TENDER_LOGIN, PURCHASE_AUCTION_LIST, PURCHASE_OFFLINE_AUCTION, REVERSE_AUCTION_ITEM, VIEW_AUCTION_DOCUMENTS, VIEW_AUCTION_ITEM_DETAIL, STAGETHREE, REJ_STAGE_TWO, GETCONTRACTORBYSTATE, GET_MASTER_DATA, ALL_DESIGNATION, GET_REGISTRATION_STATE, PARTNER_API_ENDPOINT, SAVE_CONTRACTOR_STAGE1, VERIFY_GST, VERIFY_ADHAR, SEND_ADHAROTP, SUBMIT_ENVELOPE_FILE_B, SUBMIT_ENVELOPE_FILE_A, SUBMIT_ENVELOPE_A, SUBMIT_ENVELOPE_B, VERIFY_ORGPAN, PURCHASE_AUCTION_PAGE, GET_TENDER_BY_ID, GET_CONTRACTOR_PROFILE, CONTRACTOR_PROFILE_UPDATE, + ESETU_VARIFICATION, TENDER_BID_DETAIL, + TENDER_DASHBOARD +} from "./ApiUrls"; import { apiCall } from "../../Axios/Axios"; // User @@ -59,7 +61,7 @@ export const fetchPurchaseAuction = async (): Promise => { } export const fetchPurchaseAuctionPage = async (payload: any): Promise => { - + const data = await apiCall('get', `${PURCHASE_AUCTION_PAGE}`, {}, payload); return data } @@ -132,7 +134,7 @@ export const fetchPurchaseTender = async ({ -export const fetchLiveTender = async ({page, size}:{page:number, size:number}): Promise => { +export const fetchLiveTender = async ({ page, size }: { page: number, size: number }): Promise => { const data = await apiCall('get', `${LIVE_TENDER}?page=${page}&size=${size}`); return data } @@ -174,24 +176,24 @@ export const verifyPanHandler = ( return apiCall( 'post', VERIFY_ORGPAN, - payload, + payload, ); }; export const updateUserProfile = ( payload: { - firstName: string, - middleName: string, - lastName: string, - mobileNo: string, - emailId: string, - fullname: string + firstName: string, + middleName: string, + lastName: string, + mobileNo: string, + emailId: string, + fullname: string } ): Promise => { return apiCall( 'put', CONTRACTOR_PROFILE_UPDATE, - payload, + payload, ); }; export const fetchPurchaseAuctionList = async (payload: any): Promise => { @@ -226,13 +228,13 @@ export const contractorStageThreeSubmit = ( }; export const contractorStageTwoSubmit = ( -fileData: any + fileData: any ): Promise => { return apiCall( 'post', REJ_STAGE_TWO, fileData, - undefined, + undefined, 'multipart/form-data' ); }; @@ -255,7 +257,7 @@ export const fetchCityDropdown = async (stateId: number): Promise => { export const fetchMasterData = async (): Promise => { - const data = await apiCall( + const data = await apiCall( 'get', `${GET_MASTER_DATA}` ); @@ -267,7 +269,7 @@ export const getDesignation = async (): Promise => { } export const getRagistrationState = async (): Promise => { - const data = await apiCall( + const data = await apiCall( 'get', `${GET_REGISTRATION_STATE}` ); @@ -275,9 +277,9 @@ export const getRagistrationState = async (): Promise => { } -export const eSetuAdharVarification = async (payLoad:any ): Promise => { +export const eSetuAdharVarification = async (payLoad: any): Promise => { - const data = await apiCall( + const data = await apiCall( 'post', `${ESETU_VARIFICATION}`, payLoad, @@ -294,7 +296,7 @@ export const partnerShipSubmit = ( PARTNER_API_ENDPOINT, payload, ); -}; +}; export const registerContractor = async (payload: any): Promise => { @@ -322,9 +324,9 @@ export type VerifyAadhaarOtpPayload = { export const verifyAadhaarOtp = ( payload: { - otp: string; - refNumber: string; -} + otp: string; + refNumber: string; + } ): Promise => { return apiCall( 'post', @@ -339,8 +341,8 @@ export type VerifyGstPayload = { export const verifyGstNumber = ( payload: { - gstNumber: string; -} + gstNumber: string; + } ): Promise => { return apiCall( 'post', @@ -382,7 +384,7 @@ export const uploadEnvelopeFile = async ({ const url = activeEnvelope === 'B' ? SUBMIT_ENVELOPE_FILE_B : SUBMIT_ENVELOPE_FILE_A - const data = await apiCall('post', url, formData, { + const data = await apiCall('post', url, formData, { headers: { 'Content-Type': 'multipart/form-data', }, @@ -391,8 +393,8 @@ export const uploadEnvelopeFile = async ({ return data } -export const tenderDetails = async (tenderId:any ): Promise => { - const data = await apiCall('get', `${GET_TENDER_DETAILS}${tenderId}`, ); +export const tenderDetails = async (tenderId: any): Promise => { + const data = await apiCall('get', `${GET_TENDER_DETAILS}${tenderId}`,); return data } diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..043f99c --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,36 @@ +import axios from 'axios'; +import { store } from '../Redux/Store'; +import { setToken } from '../Redux/Slices/Token'; + +const apiClient = axios.create({ + baseURL: 'http://procurelinc.com/api', // Replace with actual base URL if different + headers: { + 'Content-Type': 'application/json', + }, +}); + +apiClient.interceptors.request.use( + (config) => { + const state = store.getState(); + const token = state.token?.token?.token; + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +apiClient.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + store.dispatch(setToken({})); + } + return Promise.reject(error); + } +); + +export default apiClient; diff --git a/src/components/AppBackground.tsx b/src/components/AppBackground.tsx new file mode 100644 index 0000000..5e4841a --- /dev/null +++ b/src/components/AppBackground.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { StyleSheet, View, StatusBar, Dimensions } from 'react-native'; +import Animated, { useAnimatedStyle, interpolate } from 'react-native-reanimated'; +import { useTheme } from '../Hooks/useTheme'; +import ImagePath from '../Constant/ImagePath'; + +const { width, height } = Dimensions.get('window'); + +interface AppBackgroundProps { + children: React.ReactNode; + secondarySource?: any; + crossFadeProgress?: Animated.SharedValue; +} + +const AppBackground: React.FC = ({ children, secondarySource, crossFadeProgress }) => { + const { isDarkMode } = useTheme(); + const primarySource = isDarkMode ? ImagePath.DarkBg : ImagePath.LightBg; + + const primaryAnimatedStyle = useAnimatedStyle(() => { + if (!crossFadeProgress) return { opacity: 1 }; + return { + opacity: interpolate(crossFadeProgress.value, [0, 1], [1, 0]), + }; + }); + + const secondaryAnimatedStyle = useAnimatedStyle(() => { + if (!crossFadeProgress) return { opacity: 0 }; + return { + opacity: interpolate(crossFadeProgress.value, [0, 1], [0, 1]), + }; + }); + + return ( + + + {/* Primary Layer */} + + + {/* Secondary Layer (Cross-fade) */} + {secondarySource && ( + + )} + + + {children} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + background: { + width: width, + height: height, + }, +}); + +export default AppBackground; diff --git a/src/components/AppStatusBar.tsx b/src/components/AppStatusBar.tsx new file mode 100644 index 0000000..b7df782 --- /dev/null +++ b/src/components/AppStatusBar.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { StatusBar, Platform } from 'react-native'; +import { useTheme } from '../Hooks/useTheme'; + +const AppStatusBar: React.FC = () => { + const { isDarkMode } = useTheme(); + + return ( + + ); +}; + +export default AppStatusBar; diff --git a/src/components/AppText.tsx b/src/components/AppText.tsx new file mode 100644 index 0000000..78ea566 --- /dev/null +++ b/src/components/AppText.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Text, TextProps, StyleSheet, TextStyle } from 'react-native'; +import { useTheme } from '../Hooks/useTheme'; +import { typography } from '../theme/typography'; + +// Define the available variants based on our typography object +export type TypographyVariant = 'h1' | 'h2' | 'h3' | 'h4' | 'body' | 'bodySmall' | 'caption' | 'button'; + +interface AppTextProps extends TextProps { + variant?: TypographyVariant; + size?: number; // Override size if absolutely necessary + color?: string; // Override color + bold?: boolean; // Use bold font family safely (avoids Android fontFamily conflicts) + semiBold?: boolean; // Use semiBold font family + medium?: boolean; // Use medium font family + centered?: boolean; + style?: TextStyle | TextStyle[]; + children: React.ReactNode; +} + +const AppText: React.FC = ({ + variant = 'body', + size, + color, + bold, + semiBold, + medium, + centered, + style, + children, + ...props +}) => { + const { colors } = useTheme(); + + // Get the base style for the selected variant + // @ts-ignore - safe because we typed variant to match keys + const variantStyle = typography[variant] || typography.body; + + const textStyle: TextStyle = { + ...variantStyle, + color: color || colors.text, + textAlign: centered ? 'center' : 'auto', + }; + + // Apply font weight via fontFamily (Android-safe approach) + if (bold) { + textStyle.fontFamily = typography.fonts.bold; + } else if (semiBold) { + textStyle.fontFamily = typography.fonts.medium; // Poppins-SemiBold if available, using medium as safe fallback + } else if (medium) { + textStyle.fontFamily = typography.fonts.medium; + } + + if (size) { + textStyle.fontSize = size; + } + + // Flatten all styles into a single object to avoid Android fontFamily conflicts + // when style arrays contain conflicting fontFamily values + const mergedStyle = StyleSheet.flatten([textStyle, style]); + + return ( + + {children} + + ); +}; + +export default AppText; diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..1e3433f --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, Image, StyleSheet, Dimensions } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { ArrowLeft, Menu } from 'lucide-react-native'; +import { useNavigation } from '@react-navigation/native'; +import { useTheme } from '../Hooks/useTheme'; +// Preserving existing constant imports for now, should be refactored later +import ImagePath from '../Constant/ImagePath'; +import FontsFamily from '../Constant/FontsFamily'; + +const { width } = Dimensions.get('window'); + +interface HeaderProps { + title: string; + showDrawer?: boolean; + showBack?: boolean; + onBackPress?: () => void; + onDrawerPress?: () => void; + rightIcon?: React.ReactNode; +} + +const Header: React.FC = ({ + title, + showDrawer = false, + showBack = false, + onBackPress, + onDrawerPress, + rightIcon +}) => { + const insets = useSafeAreaInsets(); + const navigation = useNavigation(); + const { colors, spacing, typography, shadows } = useTheme(); + + const handleBack = () => { + if (onBackPress) { + onBackPress(); + } else { + navigation.goBack(); + } + }; + + return ( + + + + {showDrawer && ( + + + + )} + {showBack && ( + + + + )} + + {title} + + + + + {rightIcon || ( + + )} + + + + ); +}; + +export default Header; + +const styles = StyleSheet.create({ + container: { + width: width, + justifyContent: 'center', + }, + row: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + height: '100%', + }, + left: { + flexDirection: 'row', + alignItems: 'center', + flex: 1, + }, + right: { + flexDirection: 'row', + alignItems: 'center', + }, + iconButton: { + padding: 4, + }, + title: { + // Font family can be added here if defined in theme + }, + logo: { + width: 40, + height: 40, + }, +}); diff --git a/src/components/Loader.tsx b/src/components/Loader.tsx new file mode 100644 index 0000000..efa3bb8 --- /dev/null +++ b/src/components/Loader.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { View, ActivityIndicator, StyleSheet, ViewStyle } from 'react-native'; +import { useTheme } from '../Hooks/useTheme'; + +interface LoaderProps { + size?: 'small' | 'large'; + color?: string; + style?: ViewStyle; +} + +const Loader: React.FC = ({ size = "large", color, style }) => { + const { colors } = useTheme(); + return ( + + + + ); +}; + +export default Loader; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/src/components/MyInput.tsx b/src/components/MyInput.tsx new file mode 100644 index 0000000..e00eff1 --- /dev/null +++ b/src/components/MyInput.tsx @@ -0,0 +1,154 @@ +import { StyleSheet, TextInput, View, TextInputProps, TouchableOpacity, Text } from 'react-native'; +import React from 'react'; +import { useTheme } from '../Hooks/useTheme'; +import { CircleCheck } from 'lucide-react-native'; + +type MyInputProps = { + icon?: string; + placeholder?: string; + value?: string; + onChangeText?: (text: string) => void; + isVerify?: boolean; + maxLength?: number; + onFocus?: () => void; + onBlur?: (e?: any) => void; + focused?: boolean; + keyboardType?: TextInputProps['keyboardType']; + editable?: boolean; + autoCapitalize?: TextInputProps['autoCapitalize']; + rightIcon?: React.ReactNode; + rightIconPress?: () => void; + label?: string; + error?: string; + containerStyle?: import('react-native').ViewStyle; +} & TextInputProps; + +const MyInput: React.FC = ({ + icon, + placeholder, + value, + onChangeText, + isVerify, + maxLength, + onFocus, + onBlur, + focused: externalFocused, + keyboardType, + editable = true, + autoCapitalize, + rightIcon, + rightIconPress, + label, + error, + style, // This goes to inputWrapper + containerStyle, // This goes to outer container + ...textInputProps +}) => { + const { colors, spacing, typography, shadows } = useTheme(); + const [internalFocused, setInternalFocused] = React.useState(false); + + const isFocused = externalFocused !== undefined ? externalFocused : internalFocused; + + const handleFocus = () => { + setInternalFocused(true); + onFocus && onFocus(); + }; + + const handleBlur = (e?: any) => { + setInternalFocused(false); + onBlur && onBlur(e); + }; + + return ( + + {label && ( + + {label} + + )} + + + {isVerify && ( + + )} + {rightIcon && ( + + {rightIcon} + + )} + + {error && ( + + • {error} + + )} + + ); +}; + +export default MyInput; + +const styles = StyleSheet.create({ + container: { + width: '100%', + }, + label: { + fontWeight: '500', + }, + inputWrapper: { + flexDirection: 'row', + alignItems: 'center', + borderRadius: 12, // Softer rounding + }, + input: { + flex: 1, + paddingVertical: 0, + }, +}); diff --git a/src/components/PrimaryButton.tsx b/src/components/PrimaryButton.tsx new file mode 100644 index 0000000..8ba9e6b --- /dev/null +++ b/src/components/PrimaryButton.tsx @@ -0,0 +1,103 @@ +import { StyleSheet, Text, TouchableOpacity, ActivityIndicator, ViewStyle, TextStyle } from 'react-native'; +import React from 'react'; +import { useTheme } from '../Hooks/useTheme'; + +type PrimaryButtonProps = { + title: string; + onPress: () => void; + style?: ViewStyle; + textStyle?: TextStyle; + disabled?: boolean; + loading?: boolean; + variant?: 'primary' | 'secondary' | 'outline' | 'ghost'; // Added ghost +}; + +const PrimaryButton: React.FC = ({ + title, + onPress, + style, + textStyle, + disabled, + loading, + variant = 'primary', +}) => { + const { colors, spacing, typography, shadows } = useTheme(); + + const getBackgroundColor = () => { + if (disabled) return colors.warmGray; // Disabled is warm gray + if (variant === 'primary') return colors.buttonPrimary; // Sand + if (variant === 'secondary') return colors.surface; // Off-white + return 'transparent'; + }; + + const getBorderColor = () => { + if (variant === 'outline') return colors.primary; // Sand border + if (variant === 'secondary') return colors.border; // WarmGray border + return 'transparent'; + }; + + const getTextColor = () => { + if (disabled) return colors.surface; + if (variant === 'primary') return colors.buttonText; // Off-white on Sand + if (variant === 'secondary') return colors.primary; // Sand on Off-white + if (variant === 'outline') return colors.primary; // Sand + if (variant === 'ghost') return colors.secondary; // WarmGray + return colors.text; + }; + + return ( + + {loading ? ( + + ) : ( + + {title} + + )} + + ); +}; + +export default PrimaryButton; + +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center', + minHeight: 56, + }, + text: { + textAlign: 'center', + }, +}); diff --git a/src/components/SidebarModal.tsx b/src/components/SidebarModal.tsx new file mode 100644 index 0000000..bb1a519 --- /dev/null +++ b/src/components/SidebarModal.tsx @@ -0,0 +1,196 @@ +import React from 'react'; +import { View, StyleSheet, TouchableOpacity, Image, Dimensions } from 'react-native'; +import Modal from 'react-native-modal'; +import { useTheme } from '../Hooks/useTheme'; +import AppText from './AppText'; +import { Moon, Sun, Monitor, X, LogOut, FileText, User } from 'lucide-react-native'; +import ImagePath from '../Constant/ImagePath'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useDispatch } from 'react-redux'; +import { removeToken } from '../Redux/Slices/Token'; +import NavigationString from '../Constant/NavigationString'; +import { useNavigation } from '@react-navigation/native'; + +interface SidebarModalProps { + isVisible: boolean; + onClose: () => void; +} + +const { width, height } = Dimensions.get('window'); + +const SidebarModal: React.FC = ({ isVisible, onClose }) => { + const { colors, themeMode, toggleTheme } = useTheme(); + const insets = useSafeAreaInsets(); + const dispatch = useDispatch(); + const navigation = useNavigation(); + + const handleLogout = () => { + dispatch(removeToken()); + // Navigation reset is handled by MainStack/Routes usually, or we can navigate to Login + navigation.reset({ + index: 0, + routes: [{ name: NavigationString.Login }], + }); + }; + + const renderThemeOption = (mode: 'light' | 'dark' | 'system', label: string, Icon: any) => { + const isActive = themeMode === mode; + return ( + toggleTheme(mode)} + > + + + {label} + + + ); + }; + + const renderMenuItem = (label: string, Icon: any, onPress: () => void) => ( + + + + {label} + + + ); + + return ( + + + + {/* Header */} + + + + + ProcureLinc + v1.0.0 + + + + + + + + {/* Menu Items */} + + {renderMenuItem('Profile', User, () => { + onClose(); + navigation.navigate(NavigationString.Profile); + })} + {renderMenuItem('Terms & Conditions', FileText, () => { + onClose(); + // Navigate to Terms + })} + + + {/* Bottom Section: Theme & Logout */} + + Appearance + + {renderThemeOption('light', 'Light', Sun)} + {renderThemeOption('dark', 'Dark', Moon)} + {renderThemeOption('system', 'System', Monitor)} + + + + + Log Out + + + + + + ); +}; + +const styles = StyleSheet.create({ + modal: { + margin: 0, + justifyContent: 'flex-start', + }, + container: { + width: width * 0.8, + height: '100%', + shadowColor: "#000", + shadowOffset: { width: 2, height: 0 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 20, + borderBottomWidth: 1, + }, + profileSection: { + flexDirection: 'row', + alignItems: 'center', + }, + logo: { + width: 40, + height: 40, + marginRight: 12, + borderRadius: 20, + }, + closeButton: { + padding: 8, + }, + content: { + flex: 1, + padding: 20, + }, + menuItem: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 16, + }, + footer: { + padding: 20, + borderTopWidth: 1, + paddingBottom: 40, + }, + themeRow: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 24, + }, + themeOption: { + alignItems: 'center', + padding: 8, + borderRadius: 8, + flexDirection: 'row', + }, + logoutButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + padding: 12, + borderRadius: 12, + marginTop: 10, + }, +}); + +export default SidebarModal; diff --git a/src/theme/colors.ts b/src/theme/colors.ts new file mode 100644 index 0000000..9955422 --- /dev/null +++ b/src/theme/colors.ts @@ -0,0 +1,49 @@ +// STRICT PALETTE - NO OTHER COLORS ALLOWED +const palette = { + offWhite: '#F9F8F6', + beige: '#EFE9E3', + warmGray: '#D9CFC7', + sand: '#C9B59C', + black: '#000000', +} as const; + +export const lightColors = { + background: 'rgb(255, 249, 243)', + surface: 'rgb(255, 255, 255)', + primary: '#e39546ff', + primaryLight: '#FDBA74', + secondary: palette.warmGray, + text: '#0f172a', + textSecondary: '#9e9e9eff', + border: 'rgba(226, 232, 240, 0.6)', + buttonPrimary: '#e39546ff', + buttonText: '#FFFFFF', + error: '#ef4444', + success: '#10b981', + shadow: 'rgba(0,0,0,0.1)', + glass: 'rgba(255, 255, 255, 0.7)', + overlay: 'rgba(0,0,0,0.05)', + ...palette, +}; + +export const darkColors = { + background: '#212121ff', + surface: '#252627ff', + primary: '#e39546ff', + primaryLight: '#FDBA74', + secondary: '#475569', + text: '#f8fafc', + textSecondary: '#cdcdcdff', + border: 'rgba(51, 65, 85, 0.6)', + buttonPrimary: '#e39546ff', + buttonText: '#FFFFFF', + error: '#f87171', + success: '#34d399', + shadow: 'rgba(0,0,0,0.4)', + glass: 'rgba(42, 43, 45, 0.7)', + overlay: 'rgba(255,255,255,0.1)', + ...palette, +}; + +export const colors = lightColors; // Default export for backwards compatibility + diff --git a/src/theme/index.ts b/src/theme/index.ts new file mode 100644 index 0000000..c008514 --- /dev/null +++ b/src/theme/index.ts @@ -0,0 +1,4 @@ +export * from './colors'; +export * from './spacing'; +export * from './typography'; +export * from './shadows'; diff --git a/src/theme/shadows.ts b/src/theme/shadows.ts new file mode 100644 index 0000000..bf6d680 --- /dev/null +++ b/src/theme/shadows.ts @@ -0,0 +1,37 @@ +import { colors } from './colors'; + +export const shadows = { + // Delicate shadow using warm gray + light: { + shadowColor: colors.shadow, + shadowOffset: { + width: 0, + height: 4, + }, + shadowOpacity: 0.1, // Subtle + shadowRadius: 12, + elevation: 2, + }, + // Deeper shadow for cards + medium: { + shadowColor: colors.shadow, + shadowOffset: { + width: 0, + height: 8, + }, + shadowOpacity: 0.15, + shadowRadius: 24, + elevation: 4, + }, + // Floating action or major modal + strong: { + shadowColor: colors.shadow, + shadowOffset: { + width: 0, + height: 12, + }, + shadowOpacity: 0.2, // Distinct but soft + shadowRadius: 32, + elevation: 8, + }, +} as const; diff --git a/src/theme/spacing.ts b/src/theme/spacing.ts new file mode 100644 index 0000000..500eeea --- /dev/null +++ b/src/theme/spacing.ts @@ -0,0 +1,8 @@ +export const spacing = { + xs: 4, + s: 8, + m: 16, + l: 24, + xl: 32, + xxl: 40, +} as const; diff --git a/src/theme/typography.ts b/src/theme/typography.ts new file mode 100644 index 0000000..8b06287 --- /dev/null +++ b/src/theme/typography.ts @@ -0,0 +1,78 @@ +import FontsFamily from '../Constant/FontsFamily'; + +export const typography = { + // semantic variants + h1: { + fontFamily: FontsFamily.poppinsBold, + fontSize: 32, + lineHeight: 40, + letterSpacing: -0.5, + }, + h2: { + fontFamily: FontsFamily.poppinsSemiBold, + fontSize: 24, + lineHeight: 32, + letterSpacing: 0, + }, + h3: { + fontFamily: FontsFamily.poppinsMedium, + fontSize: 20, + lineHeight: 28, + letterSpacing: 0.15, + }, + h4: { + fontFamily: FontsFamily.poppinsMedium, + fontSize: 18, + lineHeight: 24, + letterSpacing: 0.15, + }, + body: { + fontFamily: FontsFamily.poppinsRegular, + fontSize: 16, + lineHeight: 24, + letterSpacing: 0.5, + }, + bodySmall: { + fontFamily: FontsFamily.poppinsRegular, + fontSize: 14, + lineHeight: 20, + letterSpacing: 0.25, + }, + caption: { + fontFamily: FontsFamily.poppinsRegular, + fontSize: 12, + lineHeight: 16, + letterSpacing: 0.4, + }, + button: { + fontFamily: FontsFamily.poppinsMedium, + fontSize: 14, + lineHeight: 20, + letterSpacing: 1.25, + textTransform: 'uppercase' as const, + }, + // legacy support (optional, but good for transition) + size: { + vs: 10, + xs: 12, + s: 14, + m: 16, + l: 18, + xl: 20, + xxl: 24, + xxxl: 32, + }, + weight: { + regular: '400', + medium: '500', + bold: '700', + } as const, + fonts: { + regular: FontsFamily.poppinsRegular, + medium: FontsFamily.poppinsMedium, + bold: FontsFamily.poppinsBold, + light: FontsFamily.poppinsLight, + thin: FontsFamily.poppinsThin, + italic: FontsFamily.poppinsItalic, + } +} as const;