Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd672f39f5 | ||
|
|
32049d90bd | ||
|
|
66461370a2 | ||
|
|
c1ecc97e29 | ||
|
|
499e680404 | ||
|
|
080f1b57cd | ||
|
|
d9cb9952ad | ||
|
|
78a74472dd | ||
|
|
76ba2b66f7 | ||
|
|
feb1ee2707 | ||
|
|
7c949eb570 | ||
|
|
b6c927367d | ||
|
|
96d71d1011 | ||
|
|
b4b136e801 |
@@ -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
|
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||||
# screenshots whenever they are needed.
|
# screenshots whenever they are needed.
|
||||||
# For more information about the recommended setup visit:
|
# 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/report.xml
|
||||||
**/fastlane/Preview.html
|
**/fastlane/Preview.html
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"java.compile.nullAnalysis.mode": "automatic",
|
||||||
|
"java.configuration.updateBuildConfiguration": "interactive"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
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 { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { AlertNotificationRoot } from 'react-native-alert-notification';
|
import { AlertNotificationRoot } from 'react-native-alert-notification';
|
||||||
import { PersistGate } from 'redux-persist/integration/react';
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
@@ -11,21 +11,24 @@ import { NavigationContainer } from '@react-navigation/native';
|
|||||||
import colors from './src/Constant/Color';
|
import colors from './src/Constant/Color';
|
||||||
import { TextEncoder } from 'text-encoding';
|
import { TextEncoder } from 'text-encoding';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
import AppStatusBar from './src/components/AppStatusBar';
|
||||||
LogBox.ignoreAllLogs(true);
|
LogBox.ignoreAllLogs(true);
|
||||||
|
|
||||||
|
import { navigationRef } from './src/Navigation/NavigationService';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
global.TextEncoder = TextEncoder;
|
global.TextEncoder = TextEncoder;
|
||||||
return (
|
return (
|
||||||
<NavigationContainer >
|
<NavigationContainer ref={navigationRef}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||||
<AlertNotificationRoot>
|
<AlertNotificationRoot>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PersistGate persistor={persistor}>
|
<PersistGate persistor={persistor}>
|
||||||
<View style={{ flex: 1, backgroundColor: colors.white, paddingBottom: insets.bottom }}>
|
<View style={{ flex: 1, backgroundColor: colors.white, paddingBottom: insets.bottom }}>
|
||||||
<StatusBar barStyle="dark-content" />
|
<AppStatusBar />
|
||||||
<Routes />
|
<Routes />
|
||||||
</View>
|
</View>
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
@@ -38,3 +41,4 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
ruby ">= 2.6.10"
|
||||||
|
|
||||||
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
|
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
|
||||||
|
|||||||
@@ -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
|
# 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
|
## Step 1: Start Metro
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ Then, and every time you update your native dependencies, run:
|
|||||||
bundle exec pod install
|
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
|
```sh
|
||||||
# Using npm
|
# 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!
|
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:
|
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?
|
### 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 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](https://reactnative.dev/docs/getting-started).
|
- If you're curious to learn more about React Native, check out the [docs](http://reactnative.dev/docs/getting-started).
|
||||||
|
|
||||||
# Troubleshooting
|
# 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
|
# Learn More
|
||||||
|
|
||||||
To learn more about React Native, take a look at the following resources:
|
To learn more about React Native, take a look at the following resources:
|
||||||
|
|
||||||
- [React Native Website](https://reactnative.dev) - learn more about React Native.
|
- [React Native Website](http://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.
|
- [Getting Started](http://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**.
|
- [Learn the Basics](http://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.
|
- [Blog](http://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.
|
- [`@facebook/react-native`](http://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native.
|
||||||
|
|||||||
@@ -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\\Code\\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\\Code\\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\\Code\\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\\Code\\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\\Code\\User\\globalStorage\\redhat.java\\1.54.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\scala\\init.gradle
|
||||||
auto.sync=false
|
auto.sync=false
|
||||||
build.scans.enabled=false
|
build.scans.enabled=false
|
||||||
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ react {
|
|||||||
// entryFile = file("../js/MyApplication.android.js")
|
// entryFile = file("../js/MyApplication.android.js")
|
||||||
//
|
//
|
||||||
// A list of extra flags to pass to the 'bundle' commands.
|
// 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 = []
|
// extraPackagerArgs = []
|
||||||
|
|
||||||
/* Hermes Commands */
|
/* Hermes Commands */
|
||||||
@@ -99,7 +99,7 @@ android {
|
|||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
// Caution! In production, you need to generate your own keystore file.
|
// 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
|
signingConfig signingConfigs.debug
|
||||||
minifyEnabled enableProguardInReleaseBuilds
|
minifyEnabled enableProguardInReleaseBuilds
|
||||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
|
|||||||
@@ -8,14 +8,12 @@ import com.facebook.react.defaults.DefaultReactActivityDelegate
|
|||||||
class MainActivity : ReactActivity() {
|
class MainActivity : ReactActivity() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
* Returns the name of the main component registered from JavaScript.
|
||||||
* rendering of the component.
|
|
||||||
*/
|
*/
|
||||||
override fun getMainComponentName(): String = "procure"
|
override fun getMainComponentName(): String = "procure"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
|
* Returns the instance of the [ReactActivityDelegate].
|
||||||
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
|
|
||||||
*/
|
*/
|
||||||
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
||||||
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
|
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
|
||||||
|
|||||||
@@ -22,13 +22,13 @@
|
|||||||
|
|
||||||
<selector>
|
<selector>
|
||||||
<!--
|
<!--
|
||||||
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
|
This file is a copy of abc_edit_text_material (http://bit.ly/3k8fX7I).
|
||||||
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
|
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
|
||||||
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
|
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
|
||||||
|
|
||||||
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
||||||
|
|
||||||
For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.
|
For more info, see http://bit.ly/3CdLStv (react-native/pull/29452) and http://bit.ly/3nxOMoR.
|
||||||
-->
|
-->
|
||||||
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
||||||
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
|
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
# 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.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# 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
|
# 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
|
# 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
|
android.useAndroidX=true
|
||||||
|
|
||||||
# Use this property to specify which architecture you want to build.
|
# Use this property to specify which architecture you want to build.
|
||||||
# You can also override it from the CLI using
|
# You can also override it from the CLI using
|
||||||
# ./gradlew <task> -PreactNativeArchitectures=x86_64
|
# ./gradlew <task> -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.
|
# Use this property to enable support to the new architecture.
|
||||||
# This will allow you to use TurboModules and the Fabric render in
|
# This will allow you to use TurboModules and the Fabric render in
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@@ -57,10 +57,10 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (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.
|
# 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
|
# This is normally unused
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
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
|
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.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
@rem you may not use this file except in compliance with the License.
|
@rem you may not use this file except in compliance with the License.
|
||||||
@rem You may obtain a copy of the License at
|
@rem You may obtain a copy of the License at
|
||||||
@rem
|
@rem
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
@rem http://www.apache.org/licenses/LICENSE-2.0
|
||||||
@rem
|
@rem
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
Diagnose log at 2026-05-08T08:22:50Z
|
||||||
|
|
||||||
|
AppPath: C:\Users\Lincpay\AppData\Local\Programs\Git\mingw64\bin\git-credential-manager.exe
|
||||||
|
InstallDir: C:\Users\Lincpay\AppData\Local\Programs\Git\mingw64\bin\
|
||||||
|
Version: 2.5.0+d34930736e131ad80e5690e5634ced1808aff3e2
|
||||||
|
|
||||||
|
------------
|
||||||
|
Diagnostic: Environment
|
||||||
|
Skipped: False
|
||||||
|
Success: True
|
||||||
|
Exception: None
|
||||||
|
Log:
|
||||||
|
OSType: Windows
|
||||||
|
OSVersion: 10.0 (build 26200)
|
||||||
|
Reading environment variables... OK
|
||||||
|
Variables:
|
||||||
|
HOMEPATH=\Users\Lincpay
|
||||||
|
DriverData=C:\Windows\System32\Drivers\DriverData
|
||||||
|
COMPUTERNAME=DESKTOP-TAFQRBL
|
||||||
|
GIT_TRACE2_PARENT_SID=3fbdeb66-dea1-404d-b65e-01414608f6f1
|
||||||
|
CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
|
||||||
|
OneDrive=C:\Users\Lincpay\OneDrive
|
||||||
|
CODEX_THREAD_ID=019e0662-b081-7d10-a202-980cf9131ff1
|
||||||
|
SESSIONNAME=Console
|
||||||
|
TMP=C:\Users\Lincpay\AppData\Local\Temp
|
||||||
|
PROCESSOR_REVISION=8e0c
|
||||||
|
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW;.CPL
|
||||||
|
USERNAME=Lincpay
|
||||||
|
GIT_EXEC_PATH=C:/Users/Lincpay/AppData/Local/Programs/Git/mingw64/libexec/git-core
|
||||||
|
TEMP=C:\Users\Lincpay\AppData\Local\Temp
|
||||||
|
LOCALAPPDATA=C:\Users\Lincpay\AppData\Local
|
||||||
|
MSYSTEM=MINGW64
|
||||||
|
TERM=xterm-256color
|
||||||
|
PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 142 Stepping 12, GenuineIntel
|
||||||
|
ProgramData=C:\ProgramData
|
||||||
|
windir=C:\WINDOWS
|
||||||
|
JAVA_HOME=C:\Program Files\Microsoft\jdk-17.0.14.7-hotspot\
|
||||||
|
USERDOMAIN=DESKTOP-TAFQRBL
|
||||||
|
PROCESSOR_LEVEL=6
|
||||||
|
NUMBER_OF_PROCESSORS=8
|
||||||
|
TMPDIR=C:\Users\Lincpay\AppData\Local\Temp
|
||||||
|
VSCODE_IPC_HOOK=\\.\pipe\547cb42d-1.107.0-main-sock
|
||||||
|
ProgramFiles(x86)=C:\Program Files (x86)
|
||||||
|
PUBLIC=C:\Users\Public
|
||||||
|
VSCODE_NLS_CONFIG={"userLocale":"en-us","osLocale":"en-in","resolvedLanguage":"en","defaultMessagesFile":"C:\\Users\\Lincpay\\AppData\\Local\\Programs\\Antigravity\\resources\\app\\out\\nls.messages.json","locale":"en-us","availableLanguages":{}}
|
||||||
|
CommonProgramFiles=C:\Program Files (x86)\Common Files
|
||||||
|
VSCODE_ESM_ENTRYPOINT=vs/workbench/api/node/extensionHostProcess
|
||||||
|
GRADLE_HOME=C:\ProgramData\chocolatey\lib\gradle\tools\gradle-8.12.1
|
||||||
|
ProgramFiles=C:\Program Files (x86)
|
||||||
|
PLINK_PROTOCOL=ssh
|
||||||
|
COLORTERM=truecolor
|
||||||
|
MSYS=enable_pcon
|
||||||
|
PATH=C:/Users/Lincpay/AppData/Local/Programs/Git/mingw64/libexec/git-core;C:\Users\Lincpay\AppData\Local\Programs\Git\mingw64\bin;C:\Users\Lincpay\AppData\Local\Programs\Git\usr\bin;C:\Users\Lincpay\bin;C:\Users\Lincpay\.codex\tmp\arg0\codex-arg0wLCfTN;C:\Python313\Scripts\;C:\Python313\;C:\Program Files\Microsoft\jdk-17.0.14.7-hotspot\bin;C:\Program Files\Java\jdk-17\bin;C:\Users\Lincpay\AppData\Local\Android\Sdk\platform-tools;C:\ProgramData\chocolatey\bin;C:\Users\DELL\AppData\Local\nvm;C:\nvm4w\nodejs;C:\Users\Lincpay\AppData\Local\Android\Sdk\emulator;C:\Program Files\nodejs\;C:\Windows\System32;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Users\Lincpay\.console-ninja\.bin;C:\Users\Lincpay\AppData\Local\Microsoft\WindowsApps;C:\Users\Lincpay\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\Lincpay\AppData\Local\Programs\Git\cmd;C:\Users\Lincpay\AppData\Local\Microsoft\WindowsApps;C:\Program Files (x86)\MongoDB Atlas CLI\;C:\Users\Lincpay\AppData\Local\Programs\Antigravity\bin;C:\Users\Lincpay\AppData\Local\Android\Sdk\emulator;C:\Users\Lincpay\AppData\Local\Android\Sdk\platform-tools;C:\Users\Lincpay\AppData\Local\Programs\cursor\resources\app\bin;c:\Users\Lincpay\.antigravity\extensions\openai.chatgpt-26.506.21252-win32-x64\bin\windows-x86_64
|
||||||
|
ChocolateyInstall=C:\ProgramData\chocolatey
|
||||||
|
DEBUG=release
|
||||||
|
VSCODE_L10N_BUNDLE_LOCATION=
|
||||||
|
VSCODE_CODE_CACHE_PATH=C:\Users\Lincpay\AppData\Roaming\Antigravity\CachedData\15487b3041e65228cae24980a3f796c905ef582c
|
||||||
|
ProgramW6432=C:\Program Files
|
||||||
|
NVM_SYMLINK=C:\nvm4w\nodejs
|
||||||
|
NVM_HOME=C:\Users\DELL\AppData\Local\nvm
|
||||||
|
HOME=C:\Users\Lincpay
|
||||||
|
VSCODE_HANDLES_UNCAUGHT_ERRORS=true
|
||||||
|
LC_CTYPE=C.UTF-8
|
||||||
|
CommonProgramW6432=C:\Program Files\Common Files
|
||||||
|
ZES_ENABLE_SYSMAN=1
|
||||||
|
LOGONSERVER=\\DESKTOP-TAFQRBL
|
||||||
|
VSCODE_CWD=C:\Users\Lincpay\AppData\Local\Programs\Antigravity
|
||||||
|
EFC_10100_1592913036=1
|
||||||
|
SystemRoot=C:\WINDOWS
|
||||||
|
CODEX_INTERNAL_ORIGINATOR_OVERRIDE=codex_vscode
|
||||||
|
USERDOMAIN_ROAMINGPROFILE=DESKTOP-TAFQRBL
|
||||||
|
APPDATA=C:\Users\Lincpay\AppData\Roaming
|
||||||
|
HOMEDRIVE=C:
|
||||||
|
RUST_LOG=warn
|
||||||
|
VSCODE_PID=14604
|
||||||
|
PSModulePath=C:\Users\Lincpay\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
|
||||||
|
ELECTRON_RUN_AS_NODE=1
|
||||||
|
CHROME_CRASHPAD_PIPE_NAME=\\.\pipe\crashpad_14604_MTACWRXOBAGKDEGK
|
||||||
|
PROCESSOR_ARCHITEW6432=AMD64
|
||||||
|
PROCESSOR_ARCHITECTURE=x86
|
||||||
|
USERPROFILE=C:\Users\Lincpay
|
||||||
|
OS=Windows_NT
|
||||||
|
ComSpec=C:\WINDOWS\system32\cmd.exe
|
||||||
|
SystemDrive=C:
|
||||||
|
VSCODE_CRASH_REPORTER_PROCESS_TYPE=extensionHost
|
||||||
|
OneDriveConsumer=C:\Users\Lincpay\OneDrive
|
||||||
|
ALLUSERSPROFILE=C:\ProgramData
|
||||||
|
|
||||||
|
|
||||||
|
------------
|
||||||
|
Diagnostic: File system
|
||||||
|
Skipped: False
|
||||||
|
Success: True
|
||||||
|
Exception: None
|
||||||
|
Log:
|
||||||
|
Temporary directory is 'C:\Users\Lincpay\AppData\Local\Temp\'...
|
||||||
|
Checking basic file I/O...
|
||||||
|
Writing to temporary file 'C:\Users\Lincpay\AppData\Local\Temp\96ba4635bdd4d82432d7df06'... OK
|
||||||
|
Reading from temporary file 'C:\Users\Lincpay\AppData\Local\Temp\96ba4635bdd4d82432d7df06'... OK
|
||||||
|
Deleting temporary file 'C:\Users\Lincpay\AppData\Local\Temp\96ba4635bdd4d82432d7df06'... OK
|
||||||
|
Testing IFileSystem instance...
|
||||||
|
UserHomePath: C:\Users\Lincpay
|
||||||
|
UserDataDirectoryPath: C:\Users\Lincpay\.gcm
|
||||||
|
GetCurrentDirectory(): D:\ProcureLinc\Procurelinc
|
||||||
|
|
||||||
|
------------
|
||||||
|
Diagnostic: Networking
|
||||||
|
Skipped: False
|
||||||
|
Success: True
|
||||||
|
Exception: None
|
||||||
|
Log:
|
||||||
|
Checking networking and HTTP stack...
|
||||||
|
Creating HTTP client... OK
|
||||||
|
IsNetworkAvailable: True
|
||||||
|
Sending HEAD request to http://example.com...Sending HEAD request to https://example.com... OK
|
||||||
|
OK
|
||||||
|
Acquiring free TCP port... OK
|
||||||
|
Testing local HTTP loopback connections...
|
||||||
|
Creating new HTTP listener for http://localhost:55697/... OK
|
||||||
|
Waiting for loopback connection... OK
|
||||||
|
Writing response... OK
|
||||||
|
Waiting for response data... OK
|
||||||
|
Loopback connection data OK
|
||||||
|
|
||||||
|
------------
|
||||||
|
Diagnostic: Git
|
||||||
|
Skipped: False
|
||||||
|
Success: True
|
||||||
|
Exception: None
|
||||||
|
Log:
|
||||||
|
Getting Git version... OK
|
||||||
|
Git version is '2.45.2.windows.1'
|
||||||
|
Locating current repository...Git repository at 'D:/ProcureLinc/Procurelinc/.git'
|
||||||
|
OK
|
||||||
|
Listing all Git configuration... OK
|
||||||
|
Git configuration:
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig diff.astextplain.textconv=astextplain
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig filter.lfs.clean=git-lfs clean -- %f
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig filter.lfs.smudge=git-lfs smudge -- %f
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig filter.lfs.process=git-lfs filter-process
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig filter.lfs.required=true
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig http.sslbackend=openssl
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig http.sslcainfo=C:/Users/Lincpay/AppData/Local/Programs/Git/mingw64/etc/ssl/certs/ca-bundle.crt
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig core.autocrlf=true
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig core.fscache=true
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig core.symlinks=false
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig pull.rebase=false
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig credential.helper=manager
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig credential.https://dev.azure.com.usehttppath=true
|
||||||
|
file:C:/Users/Lincpay/AppData/Local/Programs/Git/etc/gitconfig init.defaultbranch=master
|
||||||
|
file:C:/Users/Lincpay/.gitconfig user.email=abintoppo96@gmail.com
|
||||||
|
file:C:/Users/Lincpay/.gitconfig user.name=Abin Toppo
|
||||||
|
file:C:/Users/Lincpay/.gitconfig credential.https://git.lincpay.in.provider=generic
|
||||||
|
file:C:/Users/Lincpay/.gitconfig credential.https://git.zeptic.in.provider=generic
|
||||||
|
file:C:/Users/Lincpay/.gitconfig credential.http://dev1.lincpay:3000.provider=generic
|
||||||
|
file:.git/config core.repositoryformatversion=0
|
||||||
|
file:.git/config core.filemode=false
|
||||||
|
file:.git/config core.bare=false
|
||||||
|
file:.git/config core.logallrefupdates=true
|
||||||
|
file:.git/config core.symlinks=false
|
||||||
|
file:.git/config core.ignorecase=true
|
||||||
|
file:.git/config remote.origin.url=http://dev1.lincpay:3000/rohit/Procurelinc.git
|
||||||
|
file:.git/config remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
|
||||||
|
file:.git/config branch.dev.remote=origin
|
||||||
|
file:.git/config branch.dev.merge=refs/heads/dev
|
||||||
|
file:.git/config branch.dev.vscode-merge-base=origin/main
|
||||||
|
file:.git/config branch.featureDSc/device.vscode-merge-base=origin/dev
|
||||||
|
|
||||||
|
|
||||||
|
------------
|
||||||
|
Diagnostic: Credential storage
|
||||||
|
Skipped: False
|
||||||
|
Success: True
|
||||||
|
Exception: None
|
||||||
|
Log:
|
||||||
|
ICredentialStore instance is of type: CredentialStore
|
||||||
|
Writing test credential... OK
|
||||||
|
Reading test credential... OK
|
||||||
|
Deleting test credential... OK
|
||||||
|
|
||||||
|
------------
|
||||||
|
Diagnostic: Microsoft authentication (AAD/MSA)
|
||||||
|
Skipped: False
|
||||||
|
Success: True
|
||||||
|
Exception: None
|
||||||
|
Log:
|
||||||
|
Broker is not enabled.
|
||||||
|
Flow type is: Auto
|
||||||
|
Gathering MSAL token cache data... OK
|
||||||
|
CacheDirectory: C:\Users\Lincpay\AppData\Local\.IdentityService
|
||||||
|
CacheFileName: msal.cache
|
||||||
|
CacheFilePath: C:\Users\Lincpay\AppData\Local\.IdentityService\msal.cache
|
||||||
|
Creating cache helper... OK
|
||||||
|
Verifying MSAL token cache persistence... OK
|
||||||
|
|
||||||
|
------------
|
||||||
|
Diagnostic: GitHub API
|
||||||
|
Skipped: False
|
||||||
|
Success: True
|
||||||
|
Exception: None
|
||||||
|
Log:
|
||||||
|
Using 'https://github.com/' as API target.
|
||||||
|
Querying '/meta' endpoint... OK
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ target 'procure' do
|
|||||||
)
|
)
|
||||||
|
|
||||||
post_install do |installer|
|
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(
|
react_native_post_install(
|
||||||
installer,
|
installer,
|
||||||
config[:reactNativePath],
|
config[:reactNativePath],
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<string/>
|
<string>We need your location to securely bind this device for tender bidding verification.</string>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
const path = require("path");
|
||||||
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
|
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
|
||||||
|
|
||||||
const defaultConfig = getDefaultConfig(__dirname);
|
const defaultConfig = getDefaultConfig(__dirname);
|
||||||
@@ -5,7 +6,7 @@ const { assetExts, sourceExts } = defaultConfig.resolver;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Metro configuration
|
* Metro configuration
|
||||||
* https://reactnative.dev/docs/metro
|
* http://reactnative.dev/docs/metro
|
||||||
*
|
*
|
||||||
* @type {import('metro-config').MetroConfig}
|
* @type {import('metro-config').MetroConfig}
|
||||||
*/
|
*/
|
||||||
@@ -17,7 +18,22 @@ const config = {
|
|||||||
},
|
},
|
||||||
resolver: {
|
resolver: {
|
||||||
assetExts: assetExts.filter((ext) => ext !== "svg"),
|
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/**",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,80 +7,89 @@
|
|||||||
"ios": "react-native run-ios",
|
"ios": "react-native run-ios",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"start": "react-native start",
|
"start": "react-native start",
|
||||||
"test": "jest"
|
"test": "jest",
|
||||||
|
"create:users": "node scripts/create-users.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-native-community/blur": "^4.4.1",
|
"@react-native-community/blur": "4.4.1",
|
||||||
"@react-native-documents/picker": "^11.0.3",
|
"@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-native-vector-icons/material-icons": "12.4.0",
|
||||||
"@react-navigation/bottom-tabs": "^7.9.0",
|
"@react-navigation/bottom-tabs": "7.9.0",
|
||||||
"@react-navigation/native": "^7.1.26",
|
"@react-navigation/drawer": "^7.8.1",
|
||||||
"@react-navigation/stack": "^7.6.13",
|
"@react-navigation/native": "^7.1.28",
|
||||||
"@reduxjs/toolkit": "^2.11.2",
|
"@react-navigation/stack": "7.6.13",
|
||||||
"@stomp/stompjs": "^7.2.1",
|
"@reduxjs/toolkit": "2.11.2",
|
||||||
"@tanstack/react-query": "^5.90.12",
|
"@stomp/stompjs": "7.2.1",
|
||||||
"axios": "^1.13.2",
|
"@tanstack/react-query": "5.90.12",
|
||||||
"buffer": "^6.0.3",
|
"axios": "1.13.2",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"buffer": "6.0.3",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"crypto-js": "^4.2.0",
|
||||||
"eslint-plugin-react-native": "^5.0.0",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"exceljs": "^4.4.0",
|
"eslint-plugin-react": "7.37.5",
|
||||||
"formik": "^2.4.9",
|
"eslint-plugin-react-native": "5.0.0",
|
||||||
"lottie-react-native": "^7.3.4",
|
"exceljs": "4.4.0",
|
||||||
"lucide-react-native": "^0.562.0",
|
"formik": "2.4.9",
|
||||||
"moment": "^2.30.1",
|
"lottie-react-native": "7.3.4",
|
||||||
|
"lucide-react-native": "0.562.0",
|
||||||
|
"moment": "2.30.1",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-native": "0.79.0",
|
"react-native": "0.79.0",
|
||||||
"react-native-alert-notification": "^0.4.2",
|
"react-native-alert-notification": "0.4.2",
|
||||||
"react-native-app-intro-slider": "^4.0.4",
|
"react-native-app-intro-slider": "4.0.4",
|
||||||
"react-native-blob-util": "^0.24.6",
|
"react-native-blob-util": "0.24.6",
|
||||||
"react-native-bouncy-checkbox": "^4.1.2",
|
"react-native-bouncy-checkbox": "4.1.2",
|
||||||
"react-native-date-picker": "^5.0.13",
|
"react-native-date-picker": "5.0.13",
|
||||||
"react-native-element-dropdown": "^2.12.4",
|
"react-native-device-info": "^15.0.2",
|
||||||
"react-native-fs": "^2.20.0",
|
"react-native-element-dropdown": "2.12.4",
|
||||||
|
"react-native-fs": "2.20.0",
|
||||||
|
"react-native-geolocation-service": "^5.3.1",
|
||||||
"react-native-gesture-handler": "^2.30.0",
|
"react-native-gesture-handler": "^2.30.0",
|
||||||
"react-native-gifted-charts": "^1.4.70",
|
"react-native-get-random-values": "1.11.0",
|
||||||
"react-native-haptic-feedback": "^2.3.3",
|
"react-native-gifted-charts": "1.4.70",
|
||||||
|
"react-native-haptic-feedback": "2.3.3",
|
||||||
"react-native-linear-gradient": "^2.8.3",
|
"react-native-linear-gradient": "^2.8.3",
|
||||||
"react-native-modal": "^14.0.0-rc.1",
|
"react-native-modal": "^14.0.0-rc.1",
|
||||||
"react-native-permissions": "^5.4.4",
|
"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-responsive-screen": "1.4.2",
|
||||||
"react-native-safe-area-context": "^5.6.2",
|
"react-native-safe-area-context": "5.6.2",
|
||||||
"react-native-screens": "^4.12.0",
|
"react-native-screens": "4.12.0",
|
||||||
"react-native-skeleton-placeholder": "^5.2.4",
|
"react-native-skeleton-placeholder": "5.2.4",
|
||||||
"react-native-svg": "^15.12.0",
|
"react-native-svg": "15.12.0",
|
||||||
"react-native-svg-transformer": "^1.5.1",
|
"react-native-svg-transformer": "1.5.1",
|
||||||
"react-native-webview": "^13.16.0",
|
"react-native-webview": "13.16.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "9.2.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "6.0.0",
|
||||||
"sockjs-client": "^1.6.1",
|
"sockjs-client": "1.6.1",
|
||||||
"text-encoding": "^0.7.0",
|
"text-encoding": "0.7.0",
|
||||||
|
"uuid": "13.0.0",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"yup": "^1.7.1"
|
"yup": "1.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "7.25.2",
|
||||||
"@babel/preset-env": "^7.25.3",
|
"@babel/preset-env": "7.25.3",
|
||||||
"@babel/runtime": "^7.25.0",
|
"@babel/runtime": "7.25.0",
|
||||||
"@react-native-community/cli": "^18.0.1",
|
"@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-community/cli-platform-ios": "18.0.0",
|
||||||
"@react-native/babel-preset": "0.79.0",
|
"@react-native/babel-preset": "0.79.0",
|
||||||
"@react-native/eslint-config": "0.79.0",
|
"@react-native/eslint-config": "0.79.0",
|
||||||
"@react-native/metro-config": "0.79.0",
|
"@react-native/metro-config": "0.79.0",
|
||||||
"@react-native/typescript-config": "0.79.0",
|
"@react-native/typescript-config": "0.79.0",
|
||||||
"@types/jest": "^29.5.13",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/react": "^19.0.0",
|
"@types/jest": "29.5.13",
|
||||||
"@types/react-test-renderer": "^19.0.0",
|
"@types/react": "19.0.0",
|
||||||
"@types/sockjs-client": "^1.5.4",
|
"@types/react-test-renderer": "19.0.0",
|
||||||
"@types/text-encoding": "^0.0.40",
|
"@types/sockjs-client": "1.5.4",
|
||||||
"eslint": "^8.19.0",
|
"@types/text-encoding": "0.0.40",
|
||||||
"jest": "^29.6.3",
|
"eslint": "^8.57.0",
|
||||||
|
"jest": "29.6.3",
|
||||||
|
"jsencrypt": "^3.5.4",
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"react-native-bundle-visualizer": "^3.1.3",
|
"react-native-bundle-visualizer": "3.1.3",
|
||||||
"react-test-renderer": "19.0.0",
|
"react-test-renderer": "19.0.0",
|
||||||
"typescript": "5.0.4"
|
"typescript": "5.0.4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { setToken } from '../Redux/Slices/Token';
|
import { setToken } from '../Redux/Slices/Token';
|
||||||
import { store } from '../Redux/Store';
|
import { store } from '../Redux/Store';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
import { resetAndNavigate } from '../Navigation/NavigationService';
|
||||||
|
import NavigationString from '../Constant/NavigationString';
|
||||||
|
|
||||||
|
|
||||||
export const apiCall = async <R, D = {}>(
|
export const apiCall = async <R, D = {}>(
|
||||||
method: 'get' | 'post' | 'patch' | 'delete' | 'put',
|
method: 'get' | 'post' | 'patch' | 'delete' | 'put',
|
||||||
@@ -9,9 +13,28 @@ export const apiCall = async <R, D = {}>(
|
|||||||
params?: any,
|
params?: any,
|
||||||
contentType?: string
|
contentType?: string
|
||||||
): Promise<R> => {
|
): Promise<R> => {
|
||||||
try {
|
|
||||||
const token: any = store.getState().token.token;
|
const token: any = store.getState().token.token;
|
||||||
|
|
||||||
|
// 🔥 ONE MAIN GROUP FOR ENTIRE API CALL
|
||||||
|
console.group(
|
||||||
|
`%c🚀 API CALL → ${method.toUpperCase()} ${url}`,
|
||||||
|
'color:#0ea5e9;font-weight:bold'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ===== REQUEST =====
|
||||||
|
console.group('📤 REQUEST');
|
||||||
|
console.log('%cMETHOD →', 'color:#22c55e;font-weight:bold', method);
|
||||||
|
console.log('%cURL →', 'color:#22c55e;font-weight:bold', url);
|
||||||
|
console.log('%cPARAMS →', 'color:#f59e0b;font-weight:bold', params);
|
||||||
|
console.log('%cPAYLOAD →', 'color:#a855f7;font-weight:bold', data);
|
||||||
|
console.log(
|
||||||
|
'%cTOKEN →',
|
||||||
|
'color:#ef4444;font-weight:bold',
|
||||||
|
token?.token ? '✔ PRESENT' : '❌ MISSING'
|
||||||
|
);
|
||||||
|
console.groupEnd();
|
||||||
|
|
||||||
|
try {
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method,
|
method,
|
||||||
url,
|
url,
|
||||||
@@ -23,26 +46,69 @@ export const apiCall = async <R, D = {}>(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ===== RESPONSE =====
|
||||||
|
console.group('📥 RESPONSE');
|
||||||
|
console.log(
|
||||||
|
'%cSTATUS →',
|
||||||
|
'color:#22c55e;font-weight:bold',
|
||||||
|
response.status
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
'%cRESPONSE DATA →',
|
||||||
|
'color:#0ea5e9;font-weight:bold',
|
||||||
|
response.data
|
||||||
|
);
|
||||||
|
console.groupEnd();
|
||||||
|
|
||||||
|
console.groupEnd(); // 🔚 MAIN GROUP
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMessage = error?.response?.data?.error;
|
// ===== ERROR =====
|
||||||
// console.log(error?.response?.data.error, '---------------------------api error message');
|
console.group('❌ ERROR');
|
||||||
|
console.error(
|
||||||
|
'%cSTATUS →',
|
||||||
|
'color:#ef4444;font-weight:bold',
|
||||||
|
error?.response?.status
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
'%cMESSAGE →',
|
||||||
|
'color:#f97316;font-weight:bold',
|
||||||
|
error?.response?.data?.message || error.message
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
'%cERROR DATA →',
|
||||||
|
'color:#fb7185;font-weight:bold',
|
||||||
|
error?.response?.data
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
'%cFULL ERROR →',
|
||||||
|
'color:#7c2d12;font-weight:bold',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
console.groupEnd();
|
||||||
|
|
||||||
if (error?.response?.statusCode === 401) {
|
// ===== AUTH ERROR =====
|
||||||
|
if (error?.response?.status === 401) {
|
||||||
|
console.warn(
|
||||||
|
'%c⚠ JWT ERROR → Token expired or malformed',
|
||||||
|
'color:#eab308;font-weight:bold'
|
||||||
|
);
|
||||||
store.dispatch(setToken({}));
|
store.dispatch(setToken({}));
|
||||||
|
console.groupEnd(); // 🔚 MAIN GROUP
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'JWT Error - Token expired or malformed',
|
message: 'JWT Error - Token expired or malformed',
|
||||||
} as R;
|
} as R;
|
||||||
}
|
}
|
||||||
const data = {
|
|
||||||
data: {
|
|
||||||
|
|
||||||
|
console.groupEnd(); // 🔚 MAIN GROUP
|
||||||
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: error?.response?.data?.message || 'No Response From Server',
|
message:
|
||||||
}
|
error?.response?.data?.message || 'No Response From Server',
|
||||||
|
...(error?.response?.data || {})
|
||||||
}
|
} as R;
|
||||||
|
|
||||||
return data as any
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,37 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Text, View, StyleProp, ViewStyle, TextStyle } from 'react-native';
|
import { StyleSheet, Text, View, StyleProp, ViewStyle, TextStyle } from 'react-native';
|
||||||
import { Dropdown } from 'react-native-element-dropdown';
|
import { Dropdown } from 'react-native-element-dropdown';
|
||||||
import { Shield } from 'lucide-react-native';
|
import { Shield, ChevronDown } from 'lucide-react-native';
|
||||||
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
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',
|
|
||||||
};
|
|
||||||
|
|
||||||
type DropdownItem = {
|
type DropdownItem = {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@@ -74,9 +45,22 @@ const MyDropdown: React.FC<MyDropdownProps> = ({
|
|||||||
dropdownStyle,
|
dropdownStyle,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colors, spacing, typography, shadows } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[{ zIndex: 4000 }, containerStyle]}>
|
<View style={[{ zIndex: 4000, marginBottom: spacing.m }, containerStyle]}>
|
||||||
{label && <Text style={styles.label}>{label}</Text>}
|
{label && (
|
||||||
|
<Text style={[styles.label, {
|
||||||
|
color: colors.text,
|
||||||
|
fontSize: typography.size.s,
|
||||||
|
fontWeight: '600',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
marginBottom: spacing.xs
|
||||||
|
}]}>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
data={data}
|
data={data}
|
||||||
labelField={labelField}
|
labelField={labelField}
|
||||||
@@ -86,23 +70,70 @@ const MyDropdown: React.FC<MyDropdownProps> = ({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
style={[
|
style={[
|
||||||
styles.dropdown,
|
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,
|
style,
|
||||||
{ borderColor: focusedField ? colors.primary : '#e0e0e0' },
|
|
||||||
]}
|
]}
|
||||||
containerStyle={[styles.dropdownContainer, dropdownStyle]}
|
containerStyle={[
|
||||||
selectedTextStyle={[styles.selectedText, selectedTextStyle]}
|
styles.dropdownContainer,
|
||||||
placeholderStyle={[styles.placeholderText, placeholderStyle]}
|
{
|
||||||
|
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}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
search
|
search
|
||||||
searchPlaceholder="Search Class"
|
searchPlaceholder="Search..."
|
||||||
|
inputSearchStyle={{
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
borderColor: colors.border,
|
||||||
|
color: colors.text
|
||||||
|
}}
|
||||||
renderLeftIcon={() => (
|
renderLeftIcon={() => (
|
||||||
<Shield
|
<Shield
|
||||||
style={{ marginRight: 10 }}
|
style={{ marginRight: 10 }}
|
||||||
color={focusedField ? colors.primary : '#aaa'}
|
color={focusedField ? colors.primary : colors.warmGray}
|
||||||
size={20}
|
size={20}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
renderRightIcon={() => (
|
||||||
|
<ChevronDown
|
||||||
|
size={20}
|
||||||
|
color={focusedField ? colors.primary : colors.warmGray}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -113,25 +144,19 @@ export default MyDropdown;
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
label: {
|
label: {
|
||||||
marginVertical: 10,
|
// marginVertical: 10, handled inline
|
||||||
color: colors.darkText,
|
|
||||||
},
|
},
|
||||||
dropdown: {
|
dropdown: {
|
||||||
borderWidth: 1,
|
// handled inline with theme
|
||||||
borderRadius: 8,
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
paddingVertical: 12,
|
|
||||||
borderColor: colors.border,
|
|
||||||
backgroundColor: colors.white,
|
|
||||||
},
|
},
|
||||||
dropdownContainer: {
|
dropdownContainer: {
|
||||||
borderRadius: 12,
|
// handled inline
|
||||||
borderColor: colors.border,
|
marginTop: 4,
|
||||||
},
|
},
|
||||||
selectedText: {
|
selectedText: {
|
||||||
color: colors.darkText,
|
// handled inline
|
||||||
},
|
},
|
||||||
placeholderText: {
|
placeholderText: {
|
||||||
color: colors.placeholder,
|
// handled inline
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import React from 'react';
|
|||||||
import { View, Text, TouchableOpacity, Image, StyleSheet, Dimensions } from 'react-native';
|
import { View, Text, TouchableOpacity, Image, StyleSheet, Dimensions } from 'react-native';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { ArrowLeft } from 'lucide-react-native';
|
import { ArrowLeft } from 'lucide-react-native';
|
||||||
import colors from '../../Constant/Color';
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
import ImagePath from '../../Constant/ImagePath';
|
import ImagePath from '../../Constant/ImagePath';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import FontsFamily from '../../Constant/FontsFamily';
|
|
||||||
import NavigationString from '../../Constant/NavigationString';
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
const { width } = Dimensions.get('window');
|
||||||
@@ -18,6 +17,7 @@ interface HeaderProps {
|
|||||||
const InsideHeader: React.FC<HeaderProps> = ({ title, showArrow = false }) => {
|
const InsideHeader: React.FC<HeaderProps> = ({ title, showArrow = false }) => {
|
||||||
const navigation: any = useNavigation();
|
const navigation: any = useNavigation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
const { colors, spacing, typography, shadows } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@@ -25,6 +25,8 @@ const InsideHeader: React.FC<HeaderProps> = ({ title, showArrow = false }) => {
|
|||||||
styles.container,
|
styles.container,
|
||||||
{
|
{
|
||||||
paddingTop: insets.top - 10,
|
paddingTop: insets.top - 10,
|
||||||
|
backgroundColor: colors.background, // Off-white
|
||||||
|
...shadows.light, // Soft shadow
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -36,13 +38,21 @@ const InsideHeader: React.FC<HeaderProps> = ({ title, showArrow = false }) => {
|
|||||||
style={styles.backButton}
|
style={styles.backButton}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<ArrowLeft size={26} color={colors.black} strokeWidth={2.5} />
|
<ArrowLeft size={24} color={colors.primary} strokeWidth={2.5} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Text style={styles.title}>{title}</Text>
|
<Text style={[styles.title, {
|
||||||
|
color: colors.primary,
|
||||||
|
fontSize: typography.size.xl,
|
||||||
|
fontWeight: '600',
|
||||||
|
letterSpacing: 0.5
|
||||||
|
}]}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Optional: Add Logo or Right Action */}
|
||||||
<TouchableOpacity onPress={() => { navigation.navigate(NavigationString.Home) }}>
|
<TouchableOpacity onPress={() => { navigation.navigate(NavigationString.Home) }}>
|
||||||
<Image
|
<Image
|
||||||
source={ImagePath.Icon.Logo2}
|
source={ImagePath.Icon.Logo2}
|
||||||
@@ -60,25 +70,21 @@ export default InsideHeader;
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
width,
|
width,
|
||||||
backgroundColor: colors.white, // IMPORTANT for shadow
|
|
||||||
shadowColor: '#000', // iOS shadow
|
|
||||||
shadowOffset: { width: 0, height: 6 },
|
|
||||||
shadowOpacity: 0.12,
|
|
||||||
shadowRadius: 10,
|
|
||||||
elevation: 6,
|
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
|
paddingBottom: 10,
|
||||||
},
|
},
|
||||||
|
|
||||||
row: {
|
row: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 16,
|
||||||
},
|
},
|
||||||
|
|
||||||
leftRow: {
|
leftRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
backButton: {
|
backButton: {
|
||||||
@@ -88,16 +94,16 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginRight: 8,
|
marginRight: 8,
|
||||||
|
// backgroundColor: 'rgba(0,0,0,0.03)' // Optional touch target bg
|
||||||
},
|
},
|
||||||
|
|
||||||
title: {
|
title: {
|
||||||
fontSize: 20,
|
flex: 1,
|
||||||
color: colors.black,
|
|
||||||
fontFamily: FontsFamily.poppinsSemiBold,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
logo: {
|
logo: {
|
||||||
height: 60,
|
height: 48, // Slightly smaller for cleaner look
|
||||||
width: 60,
|
width: 48,
|
||||||
|
opacity: 0.9,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, StyleSheet, TouchableOpacity, Dimensions, ScrollView, StatusBar } from 'react-native';
|
import { View, Text, StyleSheet, TouchableOpacity, Dimensions, ScrollView } from 'react-native';
|
||||||
import LottieView from 'lottie-react-native';
|
import LottieView from 'lottie-react-native';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import { AlertCircle, Clock, RefreshCw, Activity } from 'lucide-react-native';
|
import { AlertCircle, Clock, RefreshCw, Activity } from 'lucide-react-native';
|
||||||
@@ -10,7 +10,6 @@ import FontsFamily from '../../Constant/FontsFamily';
|
|||||||
const ServerDown = () => {
|
const ServerDown = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StatusBar barStyle="dark-content" />
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
contentContainerStyle={styles.scrollContainer}
|
contentContainerStyle={styles.scrollContainer}
|
||||||
showsVerticalScrollIndicator={false}>
|
showsVerticalScrollIndicator={false}>
|
||||||
|
|||||||
@@ -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 React from 'react';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import colors from '../../Constant/Color';
|
import colors from '../../Constant/Color';
|
||||||
@@ -13,16 +13,8 @@ type PrimaryButtonProps = {
|
|||||||
|
|
||||||
const PrimaryButton: React.FC<PrimaryButtonProps> = ({ title, onPress, style, disabled }) => {
|
const PrimaryButton: React.FC<PrimaryButtonProps> = ({ title, onPress, style, disabled }) => {
|
||||||
return (
|
return (
|
||||||
<LinearGradient
|
<View
|
||||||
colors={[
|
|
||||||
colors.primary, // #F97316 (Orange)
|
|
||||||
colors.primaryLight, // #FDBA74 (OrangeLight)
|
|
||||||
colors.OrangeLight, // #FDBA74 (backup)
|
|
||||||
colors.OrangeSoft // #FFF3E8 (super soft orange end)
|
|
||||||
]}
|
|
||||||
start={{ x: 0, y: 0 }}
|
|
||||||
end={{ x: 1, y: 1 }}
|
|
||||||
locations={[0.0, 0.4, 0.7, 1.0]}
|
|
||||||
style={[styles.primaryButton, style]}
|
style={[styles.primaryButton, style]}
|
||||||
>
|
>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -33,7 +25,7 @@ const PrimaryButton: React.FC<PrimaryButtonProps> = ({ title, onPress, style, di
|
|||||||
>
|
>
|
||||||
<Text style={styles.buttonText}>{title}</Text>
|
<Text style={styles.buttonText}>{title}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</LinearGradient>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Animated, {
|
|||||||
withTiming,
|
withTiming,
|
||||||
interpolateColor,
|
interpolateColor,
|
||||||
} from "react-native-reanimated";
|
} from "react-native-reanimated";
|
||||||
import colors from "../../Constant/Color";
|
import { useTheme } from "../../Hooks/useTheme";
|
||||||
|
|
||||||
export interface SearchType {
|
export interface SearchType {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@@ -15,7 +15,7 @@ export interface SearchType {
|
|||||||
label?: string;
|
label?: string;
|
||||||
filter?: boolean;
|
filter?: boolean;
|
||||||
filterPress?: () => void;
|
filterPress?: () => void;
|
||||||
debounceDelay?: number; // 👈 optional
|
debounceDelay?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AnimatedView = Animated.createAnimatedComponent(View);
|
const AnimatedView = Animated.createAnimatedComponent(View);
|
||||||
@@ -26,8 +26,9 @@ export const SearchInput: React.FC<SearchType> = ({
|
|||||||
label,
|
label,
|
||||||
filter = true,
|
filter = true,
|
||||||
filterPress,
|
filterPress,
|
||||||
debounceDelay = 500, // 👈 default debounce
|
debounceDelay = 500,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colors, shadows } = useTheme();
|
||||||
const isFocused = useSharedValue(0);
|
const isFocused = useSharedValue(0);
|
||||||
const [text, setText] = useState('');
|
const [text, setText] = useState('');
|
||||||
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
@@ -36,18 +37,17 @@ export const SearchInput: React.FC<SearchType> = ({
|
|||||||
const borderColor = interpolateColor(
|
const borderColor = interpolateColor(
|
||||||
isFocused.value,
|
isFocused.value,
|
||||||
[0, 1],
|
[0, 1],
|
||||||
[colors.grayText + '50', colors.grayText]
|
[colors.overlay, colors.primary] // Use theme overlay/primary
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
borderColor,
|
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) }],
|
transform: [{ scale: withTiming(isFocused.value ? 1.01 : 1) }],
|
||||||
shadowOpacity: withTiming(isFocused.value ? 0.1 : 0),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 🔥 Debounce logic
|
// Debounce logic
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debounceRef.current) {
|
if (debounceRef.current) {
|
||||||
clearTimeout(debounceRef.current);
|
clearTimeout(debounceRef.current);
|
||||||
@@ -66,34 +66,47 @@ export const SearchInput: React.FC<SearchType> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{label && <Text style={styles.label}>{label}</Text>}
|
{label && <Text style={[styles.label, { color: colors.text }]}>{label}</Text>}
|
||||||
|
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<AnimatedView style={[styles.inputWrapper, inputContainerStyle]}>
|
<AnimatedView style={[
|
||||||
|
styles.inputWrapper,
|
||||||
|
{
|
||||||
|
backgroundColor: colors.glass,
|
||||||
|
...shadows.light // Use theme shadow
|
||||||
|
},
|
||||||
|
inputContainerStyle
|
||||||
|
]}>
|
||||||
<Search
|
<Search
|
||||||
size={18}
|
size={18}
|
||||||
color={colors.grayText}
|
color={colors.primary} // Theme primary
|
||||||
style={styles.searchIcon}
|
style={styles.searchIcon}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={[styles.input, { color: colors.text }]}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={text}
|
value={text}
|
||||||
onChangeText={setText} // 👈 update local state only
|
onChangeText={setText}
|
||||||
placeholderTextColor={colors.grayText}
|
placeholderTextColor={colors.textSecondary}
|
||||||
onFocus={() => (isFocused.value = withTiming(1))}
|
onFocus={() => (isFocused.value = withTiming(1))}
|
||||||
onBlur={() => (isFocused.value = withTiming(0))}
|
onBlur={() => (isFocused.value = withTiming(0))}
|
||||||
selectionColor={colors.grayText}
|
selectionColor={colors.primary}
|
||||||
/>
|
/>
|
||||||
</AnimatedView>
|
</AnimatedView>
|
||||||
|
|
||||||
{filter && (
|
{filter && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
style={styles.filterButton}
|
style={[
|
||||||
|
styles.filterButton,
|
||||||
|
{
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderColor: colors.overlay
|
||||||
|
}
|
||||||
|
]}
|
||||||
onPress={filterPress}
|
onPress={filterPress}
|
||||||
>
|
>
|
||||||
<Filter size={22} color={colors.black} />
|
<Filter size={20} color={colors.primary} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -110,7 +123,6 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
color: colors.black,
|
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
@@ -122,39 +134,32 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
height: 48,
|
height: 50, // Slightly taller pill
|
||||||
backgroundColor: '#F9FAFB',
|
borderRadius: 25, // Full Pill
|
||||||
borderRadius: 12,
|
paddingHorizontal: 16,
|
||||||
paddingHorizontal: 12,
|
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
shadowColor: "#000",
|
|
||||||
shadowOffset: { width: 0, height: 2 },
|
|
||||||
shadowRadius: 4,
|
|
||||||
elevation: 2,
|
|
||||||
},
|
},
|
||||||
searchIcon: {
|
searchIcon: {
|
||||||
marginRight: 8,
|
marginRight: 10,
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
color: colors.black,
|
fontWeight: '500',
|
||||||
paddingVertical: Platform.OS === 'ios' ? 0 : 8,
|
paddingVertical: Platform.OS === 'ios' ? 0 : 8,
|
||||||
},
|
},
|
||||||
filterButton: {
|
filterButton: {
|
||||||
height: 48,
|
height: 50,
|
||||||
width: 48,
|
width: 50,
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderRadius: 12,
|
borderRadius: 25, // Circle
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.grayText + '30',
|
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
shadowColor: "#000",
|
shadowColor: "#000",
|
||||||
shadowOffset: { width: 0, height: 1 },
|
shadowOffset: { width: 0, height: 2 },
|
||||||
shadowOpacity: 0.1,
|
shadowOpacity: 0.1,
|
||||||
shadowRadius: 2,
|
shadowRadius: 4,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ const SideDrawer: React.FC<Props> = ({ visible, onClose }) => {
|
|||||||
{/* ================= Header / Profile ================= */}
|
{/* ================= Header / Profile ================= */}
|
||||||
<View style={styles.profileHeader}>
|
<View style={styles.profileHeader}>
|
||||||
{/* <TouchableOpacity style={styles.closeBtn} onPress={() => {
|
{/* <TouchableOpacity style={styles.closeBtn} onPress={() => {
|
||||||
console.log('sdfadsfdsf');
|
|
||||||
}}>
|
}}>
|
||||||
<X size={20} color="#111827" />
|
<X size={20} color="#111827" />
|
||||||
</TouchableOpacity> */}
|
</TouchableOpacity> */}
|
||||||
|
|||||||
@@ -1,34 +1,110 @@
|
|||||||
import { View, Dimensions } from 'react-native';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import React from 'react';
|
import { View, Animated, StyleSheet, Dimensions } from 'react-native';
|
||||||
import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
|
|
||||||
const Skeleton = () => {
|
// 1. Create a Reusable "Bone" Component
|
||||||
const { width } = Dimensions.get('screen');
|
const SkeletonBone = ({ width, height, style, borderRadius = 8 }) => {
|
||||||
const cardWidth = width - 30; // full width with some padding
|
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 (
|
return (
|
||||||
<View style={{ padding: 10 }}>
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
backgroundColor: boneColor,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
opacity: opacity,
|
||||||
|
},
|
||||||
|
style,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Main Skeleton Layout
|
||||||
|
const Skeleton = () => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, backgroundColor: colors.background }}>
|
||||||
{[...Array(6)].map((_, index) => (
|
{[...Array(6)].map((_, index) => (
|
||||||
<SkeletonPlaceholder key={index}>
|
<View
|
||||||
<SkeletonPlaceholder.Item
|
key={index}
|
||||||
width={cardWidth}
|
style={{
|
||||||
marginBottom={20}
|
marginBottom: 16,
|
||||||
borderRadius={12}
|
marginHorizontal: 16,
|
||||||
overflow="hidden"
|
borderRadius: 20,
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
padding: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{/* Image */}
|
{/* Header Row: Tag + Price */}
|
||||||
<SkeletonPlaceholder.Item width={cardWidth} height={150} borderRadius={12} />
|
<View style={styles.row}>
|
||||||
|
<SkeletonBone width={80} height={24} borderRadius={12} />
|
||||||
|
<SkeletonBone width={60} height={24} borderRadius={4} />
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Title */}
|
{/* Title (2 lines) */}
|
||||||
<SkeletonPlaceholder.Item marginTop={10} width={cardWidth * 0.7} height={18} borderRadius={6} />
|
<View style={styles.section}>
|
||||||
|
<SkeletonBone width="90%" height={18} borderRadius={4} style={{ marginBottom: 6 }} />
|
||||||
|
<SkeletonBone width="60%" height={18} borderRadius={4} />
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Subtitle / Price */}
|
{/* Info Grid (2 cols) */}
|
||||||
<SkeletonPlaceholder.Item marginTop={6} width={cardWidth * 0.5} height={14} borderRadius={6} />
|
<View style={styles.row}>
|
||||||
</SkeletonPlaceholder.Item>
|
<SkeletonBone width="45%" height={40} borderRadius={8} />
|
||||||
</SkeletonPlaceholder>
|
<SkeletonBone width="45%" height={40} borderRadius={8} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Action Row */}
|
||||||
|
<View style={styles.row}>
|
||||||
|
<SkeletonBone width="35%" height={40} borderRadius={14} />
|
||||||
|
<SkeletonBone width="60%" height={40} borderRadius={14} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default Skeleton;
|
export default Skeleton;
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import React, { useRef, useEffect } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import {
|
import { View, StyleSheet } from 'react-native';
|
||||||
View,
|
import Animated, {
|
||||||
Text,
|
useSharedValue,
|
||||||
TouchableOpacity,
|
useAnimatedStyle,
|
||||||
StyleSheet,
|
withSpring,
|
||||||
Animated,
|
withTiming,
|
||||||
} from 'react-native';
|
} from 'react-native-reanimated';
|
||||||
import colors from '../Constant/Color';
|
import { useTheme } from '../Hooks/useTheme';
|
||||||
|
import AppText from '../components/AppText';
|
||||||
|
import { Check } from 'lucide-react-native';
|
||||||
|
|
||||||
type StepProgressBarProps = {
|
type StepProgressBarProps = {
|
||||||
currentStep: number;
|
currentStep: number;
|
||||||
@@ -14,96 +16,132 @@ type StepProgressBarProps = {
|
|||||||
onStepPress?: (stepIndex: number) => void;
|
onStepPress?: (stepIndex: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const STEP_LABELS = ['Info', 'Register', 'KYC', 'Address', 'Docs'];
|
||||||
|
|
||||||
const StepProgressBar: React.FC<StepProgressBarProps> = ({
|
const StepProgressBar: React.FC<StepProgressBarProps> = ({
|
||||||
currentStep,
|
currentStep,
|
||||||
steps,
|
steps,
|
||||||
onStepPress = () => { },
|
onStepPress = () => { },
|
||||||
}) => {
|
}) => {
|
||||||
const animations = useRef<Animated.Value[]>(
|
const { colors, spacing, typography, shadows } = useTheme();
|
||||||
steps.map(() => new Animated.Value(1))
|
const styles = useMemo(() => makeStyles(colors, spacing), [colors, spacing]);
|
||||||
).current;
|
const totalSteps = steps.length;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
animations.forEach((anim, index) => {
|
|
||||||
Animated.timing(anim, {
|
|
||||||
toValue: index === currentStep ? 1.2 : 1,
|
|
||||||
duration: 300,
|
|
||||||
useNativeDriver: true,
|
|
||||||
}).start();
|
|
||||||
});
|
|
||||||
}, [currentStep]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{steps.map((label, index) => {
|
{/* Progress line background */}
|
||||||
|
<View style={styles.lineContainer}>
|
||||||
|
<View style={[styles.lineBackground, { backgroundColor: colors.border }]} />
|
||||||
|
{/* Animated fill line */}
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.lineFill,
|
||||||
|
{
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
width: `${(currentStep / (totalSteps - 1)) * 100}%`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Step circles */}
|
||||||
|
<View style={styles.stepsRow}>
|
||||||
|
{steps.map((_, index) => {
|
||||||
const isActive = index === currentStep;
|
const isActive = index === currentStep;
|
||||||
const isCompleted = index < currentStep;
|
const isCompleted = index < currentStep;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<View key={index} style={styles.stepItem}>
|
||||||
key={index}
|
<View
|
||||||
onPress={() => onStepPress(index)}
|
|
||||||
style={styles.stepContainer}
|
|
||||||
activeOpacity={0.8}
|
|
||||||
>
|
|
||||||
<Animated.View
|
|
||||||
style={[
|
style={[
|
||||||
styles.circle,
|
styles.circle,
|
||||||
isCompleted && styles.completedCircle,
|
|
||||||
isActive && styles.activeCircle,
|
|
||||||
{
|
{
|
||||||
transform: [{ scale: animations[index] }],
|
backgroundColor: isCompleted
|
||||||
|
? colors.primary
|
||||||
|
: isActive
|
||||||
|
? colors.primary
|
||||||
|
: colors.surface,
|
||||||
|
borderColor: isCompleted || isActive
|
||||||
|
? colors.primary
|
||||||
|
: colors.border,
|
||||||
|
transform: [{ scale: isActive ? 1.15 : 1 }],
|
||||||
},
|
},
|
||||||
]}
|
isActive && { ...shadows.light },
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
styles.label,
|
|
||||||
isCompleted && { color: '#4caf50' },
|
|
||||||
isActive && { color: '#2196f3' },
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{label}
|
{isCompleted ? (
|
||||||
</Text>
|
<Check size={14} color={colors.buttonText} strokeWidth={3} />
|
||||||
</TouchableOpacity>
|
) : (
|
||||||
|
<AppText
|
||||||
|
variant="caption"
|
||||||
|
color={isActive ? colors.buttonText : colors.textSecondary}
|
||||||
|
style={{ fontWeight: '800', fontSize: 11 }}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</AppText>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<AppText
|
||||||
|
variant="caption"
|
||||||
|
color={isActive ? colors.primary : isCompleted ? colors.primary : colors.textSecondary}
|
||||||
|
style={{
|
||||||
|
marginTop: 4,
|
||||||
|
fontWeight: isActive || isCompleted ? '700' : '500',
|
||||||
|
fontSize: typography.size.xs,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{STEP_LABELS[index] || ''}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StepProgressBar;
|
export default StepProgressBar;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const makeStyles = (colors: any, spacing: any) => StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flexDirection: 'row',
|
paddingVertical: spacing.m,
|
||||||
alignItems: 'center',
|
paddingHorizontal: spacing.s,
|
||||||
paddingVertical: 20,
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
},
|
||||||
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',
|
alignItems: 'center',
|
||||||
width: 70,
|
width: 52,
|
||||||
},
|
},
|
||||||
circle: {
|
circle: {
|
||||||
width: 50,
|
width: 28,
|
||||||
height: 5,
|
height: 28,
|
||||||
borderRadius: 25,
|
borderRadius: 14,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: colors.primary,
|
borderWidth: 2,
|
||||||
},
|
|
||||||
completedCircle: {
|
|
||||||
backgroundColor: '#4caf50',
|
|
||||||
},
|
|
||||||
activeCircle: {
|
|
||||||
backgroundColor: '#2196f3',
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
// marginTop: 6,
|
|
||||||
// fontSize: 12,
|
|
||||||
textAlign: 'center',
|
|
||||||
color: '#555',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode, useMemo } from 'react';
|
||||||
import { View, Text, Modal, ScrollView, TouchableOpacity, StyleSheet, Dimensions } from 'react-native';
|
import { View, Modal, ScrollView, TouchableOpacity, StyleSheet } from 'react-native';
|
||||||
import { CheckCircle, X } from 'lucide-react-native'; // Updated icons
|
import { CheckCircle, X, FileText, Shield, Users, Brain, Lock, RefreshCw, Scale } from 'lucide-react-native';
|
||||||
import colors from '../../Constant/Color';
|
import Animated, { FadeInUp, FadeInDown, FadeIn } from 'react-native-reanimated';
|
||||||
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
|
import AppText from '../../components/AppText';
|
||||||
|
|
||||||
type ModalProps = {
|
type ModalProps = {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onChecked: () => void
|
onChecked: () => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
const TermsAndConditionsModal: React.FC<ModalProps> = ({ visible, onClose, onChecked }) => {
|
const TermsAndConditionsModal: React.FC<ModalProps> = ({ visible, onClose, onChecked }) => {
|
||||||
|
const { colors, spacing, shadows, typography } = useTheme();
|
||||||
|
const styles = useMemo(() => makeStyles(colors, spacing, shadows, typography), [colors, spacing, shadows, typography]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
animationType="slide"
|
animationType="slide"
|
||||||
@@ -17,185 +23,282 @@ const TermsAndConditionsModal: React.FC<ModalProps> = ({ visible, onClose, onChe
|
|||||||
onRequestClose={onClose}
|
onRequestClose={onClose}
|
||||||
>
|
>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
{/* ═══ Header ═══ */}
|
||||||
<View style={styles.header}>
|
<Animated.View
|
||||||
<Text style={styles.title}>Terms & Conditions</Text>
|
entering={FadeInDown.duration(400).springify()}
|
||||||
<Text style={styles.subtitle}>Last updated: {new Date().toLocaleDateString()}</Text>
|
style={styles.header}
|
||||||
|
>
|
||||||
|
<View style={styles.headerRow}>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<AppText variant="h3" color={colors.buttonText} style={{ fontWeight: '800' }}>
|
||||||
|
Terms & Conditions
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="caption" color={`${colors.buttonText}CC`} style={{ marginTop: 4 }}>
|
||||||
|
Last updated: {new Date().toLocaleDateString()}
|
||||||
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onClose}
|
||||||
|
style={styles.closeButton}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<X size={20} color={colors.buttonText} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
<View style={styles.content}>
|
{/* ═══ Content ═══ */}
|
||||||
<Section title="1. Acceptance of Terms">
|
<ScrollView
|
||||||
<Text style={styles.paragraph}>
|
contentContainerStyle={styles.scrollContainer}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
>
|
||||||
|
<Section
|
||||||
|
title="1. Acceptance of Terms"
|
||||||
|
icon={<FileText size={18} color={colors.primary} />}
|
||||||
|
styles={styles}
|
||||||
|
colors={colors}
|
||||||
|
spacing={spacing}
|
||||||
|
index={0}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={styles.paragraph}>
|
||||||
By accessing or using our services, you agree to be bound by these Terms and Conditions.
|
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.
|
If you do not agree with any part of these terms, you must not use our services.
|
||||||
</Text>
|
</AppText>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="2. User Responsibilities">
|
<Section
|
||||||
<Text style={styles.paragraph}>
|
title="2. User Responsibilities"
|
||||||
|
icon={<Users size={18} color={colors.primary} />}
|
||||||
|
styles={styles}
|
||||||
|
colors={colors}
|
||||||
|
spacing={spacing}
|
||||||
|
index={1}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={styles.paragraph}>
|
||||||
You are responsible for maintaining the confidentiality of your account information and
|
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
|
for all activities that occur under your account. You agree to notify us immediately
|
||||||
of any unauthorized use of your account.
|
of any unauthorized use of your account.
|
||||||
</Text>
|
</AppText>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="3. Intellectual Property">
|
<Section
|
||||||
<Text style={styles.paragraph}>
|
title="3. Intellectual Property"
|
||||||
|
icon={<Brain size={18} color={colors.primary} />}
|
||||||
|
styles={styles}
|
||||||
|
colors={colors}
|
||||||
|
spacing={spacing}
|
||||||
|
index={2}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={styles.paragraph}>
|
||||||
All content included on our platform, such as text, graphics, logos, and software, is
|
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
|
the property of our company and protected by intellectual property laws. You may not
|
||||||
reproduce, distribute, or create derivative works without our express permission.
|
reproduce, distribute, or create derivative works without our express permission.
|
||||||
</Text>
|
</AppText>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="4. Limitation of Liability">
|
<Section
|
||||||
<Text style={styles.paragraph}>
|
title="4. Limitation of Liability"
|
||||||
|
icon={<Shield size={18} color={colors.primary} />}
|
||||||
|
styles={styles}
|
||||||
|
colors={colors}
|
||||||
|
spacing={spacing}
|
||||||
|
index={3}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={styles.paragraph}>
|
||||||
Our company shall not be liable for any indirect, incidental, special, or consequential
|
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
|
damages resulting from the use or inability to use our services, even if we have been
|
||||||
advised of the possibility of such damages.
|
advised of the possibility of such damages.
|
||||||
</Text>
|
</AppText>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="5. Privacy Policy">
|
<Section
|
||||||
<Text style={styles.paragraph}>
|
title="5. Privacy Policy"
|
||||||
|
icon={<Lock size={18} color={colors.primary} />}
|
||||||
|
styles={styles}
|
||||||
|
colors={colors}
|
||||||
|
spacing={spacing}
|
||||||
|
index={4}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={styles.paragraph}>
|
||||||
Your use of our services is also governed by our Privacy Policy, which explains how we
|
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
|
collect, use, and protect your personal information. Please review our Privacy Policy
|
||||||
to understand our practices.
|
to understand our practices.
|
||||||
</Text>
|
</AppText>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="6. Changes to Terms">
|
<Section
|
||||||
<Text style={styles.paragraph}>
|
title="6. Changes to Terms"
|
||||||
|
icon={<RefreshCw size={18} color={colors.primary} />}
|
||||||
|
styles={styles}
|
||||||
|
colors={colors}
|
||||||
|
spacing={spacing}
|
||||||
|
index={5}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={styles.paragraph}>
|
||||||
We reserve the right to modify these terms at any time. We will notify you of any changes
|
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
|
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.
|
after such modifications constitutes your acceptance of the new terms.
|
||||||
</Text>
|
</AppText>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="7. Governing Law">
|
<Section
|
||||||
<Text style={styles.paragraph}>
|
title="7. Governing Law"
|
||||||
|
icon={<Scale size={18} color={colors.primary} />}
|
||||||
|
styles={styles}
|
||||||
|
colors={colors}
|
||||||
|
spacing={spacing}
|
||||||
|
index={6}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={styles.paragraph}>
|
||||||
These Terms shall be governed by and construed in accordance with the laws of the
|
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.
|
jurisdiction in which our company is registered, without regard to its conflict of law provisions.
|
||||||
</Text>
|
</AppText>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Text style={styles.footerText}>
|
<Animated.View entering={FadeInUp.delay(500).springify()}>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary} style={styles.footerText}>
|
||||||
If you have any questions about these Terms, please contact us at legal@company.com.
|
If you have any questions about these Terms, please contact us at legal@company.com.
|
||||||
</Text>
|
</AppText>
|
||||||
</View>
|
</Animated.View>
|
||||||
|
|
||||||
{/* Accept Button */}
|
{/* Spacer for button */}
|
||||||
|
<View style={{ height: 100 }} />
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
{/* ═══ Accept Button ═══ */}
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeInDown.delay(300).springify()}
|
||||||
|
style={styles.acceptButtonWrapper}
|
||||||
|
>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.acceptButton}
|
style={styles.acceptButton}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
onClose();
|
onClose();
|
||||||
onChecked();
|
onChecked();
|
||||||
}}
|
}}
|
||||||
|
activeOpacity={0.85}
|
||||||
>
|
>
|
||||||
<CheckCircle color={colors.white} size={28} />
|
<CheckCircle color={colors.buttonText} size={22} strokeWidth={2.5} />
|
||||||
<Text style={styles.acceptButtonText}>I Accept</Text>
|
<AppText variant="button" color={colors.buttonText} style={{ marginLeft: 10 }}>
|
||||||
|
I Accept
|
||||||
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</ScrollView>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reusable Section component
|
|
||||||
|
// ── Section sub-component ─────────────────────────────────────────────
|
||||||
type SectionProps = {
|
type SectionProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
icon: ReactNode;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
styles: any;
|
||||||
|
colors: any;
|
||||||
|
spacing: any;
|
||||||
|
index: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Section: React.FC<SectionProps> = ({ title, children }) => (
|
const Section: React.FC<SectionProps> = ({ title, icon, children, styles, colors, spacing, index }) => (
|
||||||
<View style={styles.section}>
|
<Animated.View
|
||||||
<Text style={styles.sectionTitle}>{title}</Text>
|
entering={FadeInUp.delay(100 + index * 60).springify()}
|
||||||
{children}
|
style={styles.section}
|
||||||
|
>
|
||||||
|
<View style={styles.sectionHeader}>
|
||||||
|
{icon}
|
||||||
|
<AppText variant="bodySmall" color={colors.text} style={styles.sectionTitle}>
|
||||||
|
{title}
|
||||||
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
{children}
|
||||||
|
</Animated.View>
|
||||||
);
|
);
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
|
export default TermsAndConditionsModal;
|
||||||
|
|
||||||
|
|
||||||
|
// ── Styles ────────────────────────────────────────────────────────────
|
||||||
|
const makeStyles = (colors: any, spacing: any, shadows: any, typography: any) => StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: colors.lightGray, // Updated with your colors
|
backgroundColor: colors.background,
|
||||||
},
|
|
||||||
scrollContainer: {
|
|
||||||
paddingBottom: 120, // Space for accept button
|
|
||||||
flexGrow: 1,
|
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
backgroundColor: colors.primary, // Orange header
|
backgroundColor: colors.primary,
|
||||||
paddingVertical: 32,
|
paddingTop: spacing.xl,
|
||||||
paddingHorizontal: 24,
|
paddingBottom: spacing.l,
|
||||||
borderBottomLeftRadius: 24,
|
paddingHorizontal: spacing.l,
|
||||||
borderBottomRightRadius: 24,
|
borderBottomLeftRadius: 20,
|
||||||
marginBottom: 24,
|
borderBottomRightRadius: 20,
|
||||||
shadowColor: colors.shadow,
|
|
||||||
shadowOffset: { width: 0, height: 6 },
|
|
||||||
shadowOpacity: 0.25,
|
|
||||||
shadowRadius: 12,
|
|
||||||
elevation: 8,
|
|
||||||
},
|
},
|
||||||
title: {
|
headerRow: {
|
||||||
fontSize: 28,
|
flexDirection: 'row',
|
||||||
fontWeight: '800',
|
alignItems: 'flex-start',
|
||||||
color: colors.white,
|
justifyContent: 'space-between',
|
||||||
marginBottom: 4,
|
|
||||||
},
|
},
|
||||||
subtitle: {
|
closeButton: {
|
||||||
fontSize: 14,
|
width: 36,
|
||||||
color: 'rgba(255,255,255,0.85)',
|
height: 36,
|
||||||
|
borderRadius: 18,
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.2)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
content: {
|
scrollContainer: {
|
||||||
paddingHorizontal: 24,
|
padding: spacing.l,
|
||||||
paddingBottom: 40,
|
paddingBottom: 40,
|
||||||
},
|
},
|
||||||
section: {
|
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: {
|
sectionTitle: {
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: colors.darkText,
|
fontSize: typography.size.s,
|
||||||
marginBottom: 12,
|
|
||||||
},
|
},
|
||||||
paragraph: {
|
paragraph: {
|
||||||
fontSize: 16,
|
|
||||||
lineHeight: 25,
|
|
||||||
color: colors.grayText,
|
|
||||||
},
|
|
||||||
footerText: {
|
|
||||||
fontSize: 15,
|
|
||||||
fontStyle: 'italic',
|
|
||||||
color: colors.lightText,
|
|
||||||
marginTop: 24,
|
|
||||||
textAlign: 'center',
|
|
||||||
lineHeight: 22,
|
lineHeight: 22,
|
||||||
},
|
},
|
||||||
acceptButton: {
|
footerText: {
|
||||||
|
fontStyle: 'italic',
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 20,
|
||||||
|
marginTop: spacing.m,
|
||||||
|
},
|
||||||
|
acceptButtonWrapper: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 40,
|
bottom: 0,
|
||||||
left: 24,
|
left: 0,
|
||||||
right: 24,
|
right: 0,
|
||||||
|
padding: spacing.m,
|
||||||
|
paddingBottom: spacing.xl,
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: colors.border,
|
||||||
|
},
|
||||||
|
acceptButton: {
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
borderRadius: 16,
|
borderRadius: 14,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingVertical: 18,
|
paddingVertical: 16,
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default TermsAndConditionsModal;
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Pressable,
|
Pressable,
|
||||||
|
ActivityIndicator,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import colors from '../Color';
|
import colors from '../Color';
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ type ConfirmDialogProps = {
|
|||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
loading:boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
|
const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
|
||||||
@@ -27,6 +29,7 @@ const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
|
|||||||
cancelText = 'Cancel',
|
cancelText = 'Cancel',
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onClose,
|
onClose,
|
||||||
|
loading,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -59,7 +62,10 @@ const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
|
|||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={styles.confirmText}>{confirmText}</Text>
|
{ !loading ?<Text style={styles.confirmText}>{confirmText}</Text>:<ActivityIndicator
|
||||||
|
color={colors.primary}
|
||||||
|
size={30}
|
||||||
|
/>}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import Diamond from '../Images/diamond.svg'
|
|||||||
import Hill from '../Images/Svg/Hill.svg'
|
import Hill from '../Images/Svg/Hill.svg'
|
||||||
const Logo = require('../Images/logo.png')
|
const Logo = require('../Images/logo.png')
|
||||||
const Logo2 = require('../Images/logo2.png')
|
const Logo2 = require('../Images/logo2.png')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Icon = {
|
const Icon = {
|
||||||
Logo,
|
Logo,
|
||||||
Logo2
|
Logo2
|
||||||
@@ -15,7 +18,11 @@ const ImagePath = {
|
|||||||
OnBoard2,
|
OnBoard2,
|
||||||
OnBoard3,
|
OnBoard3,
|
||||||
Diamond,
|
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;
|
export default ImagePath;
|
||||||
@@ -21,7 +21,11 @@ const NavigationString = {
|
|||||||
LiveTenderDetail:'LiveTenderDetail',
|
LiveTenderDetail:'LiveTenderDetail',
|
||||||
AuctionDetail: 'AuctionDetail',
|
AuctionDetail: 'AuctionDetail',
|
||||||
Profile:'Profile',
|
Profile:'Profile',
|
||||||
ViewTenderDetail:'ViewTenderDetail',
|
ResetPassword:'ResetPassword',
|
||||||
ResetPassword:'ResetPassword'
|
KycWebViewScreen:'KycWebViewScreen',
|
||||||
|
StageThreeScreen:'StageThree',
|
||||||
|
DecryptionScreen:'DecryptionScreen',
|
||||||
|
DeviceBinding: 'DeviceBinding',
|
||||||
|
DscBinding: 'DscBinding'
|
||||||
}
|
}
|
||||||
export default NavigationString
|
export default NavigationString
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export const generateTransactionId = () => {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const dateTime =
|
||||||
|
now.getFullYear().toString() +
|
||||||
|
(now.getMonth() + 1).toString().padStart(2, '0') +
|
||||||
|
now.getDate().toString().padStart(2, '0') +
|
||||||
|
now.getHours().toString().padStart(2, '0') +
|
||||||
|
now.getMinutes().toString().padStart(2, '0') +
|
||||||
|
now.getSeconds().toString().padStart(2, '0');
|
||||||
|
|
||||||
|
const randomPart = Math.floor(1000 + Math.random() * 9000); // 4-digit random
|
||||||
|
|
||||||
|
return `TNX${dateTime}${randomPart}`;
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import apiClient from '../api/client';
|
||||||
|
|
||||||
|
export const useCreate = <T, V>(
|
||||||
|
url: string,
|
||||||
|
options?: UseMutationOptions<T, Error, V>
|
||||||
|
) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<T, Error, V>({
|
||||||
|
mutationFn: async (variables: V) => {
|
||||||
|
const { data } = await apiClient.post<T>(url, variables);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
// Invalidate queries if needed, handled by component or passed in options
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import apiClient from '../api/client';
|
||||||
|
|
||||||
|
export const useDelete = <T>(
|
||||||
|
url: string,
|
||||||
|
options?: UseMutationOptions<T, Error, void>
|
||||||
|
) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<T, Error, void>({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const { data } = await apiClient.delete<T>(url);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useQuery, UseQueryOptions, QueryKey } from '@tanstack/react-query';
|
||||||
|
import apiClient from '../api/client';
|
||||||
|
|
||||||
|
export const useFetch = <T>(
|
||||||
|
key: QueryKey,
|
||||||
|
url: string,
|
||||||
|
options?: UseQueryOptions<T, Error>
|
||||||
|
) => {
|
||||||
|
return useQuery<T, Error>({
|
||||||
|
queryKey: key,
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiClient.get<T>(url);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import apiClient from '../api/client';
|
||||||
|
|
||||||
|
export const useUpdate = <T, V>(
|
||||||
|
url: string,
|
||||||
|
options?: UseMutationOptions<T, Error, V>
|
||||||
|
) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<T, Error, V>({
|
||||||
|
mutationFn: async (variables: V) => {
|
||||||
|
const { data } = await apiClient.put<T>(url, variables);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 6.0 MiB |
|
After Width: | Height: | Size: 6.2 MiB |
|
After Width: | Height: | Size: 5.3 MiB |
|
After Width: | Height: | Size: 5.6 MiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -1,124 +1,173 @@
|
|||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Animated,
|
Platform,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
import * as LucideIcons from 'lucide-react-native';
|
import * as LucideIcons from 'lucide-react-native';
|
||||||
|
import Animated, {
|
||||||
|
useAnimatedStyle,
|
||||||
|
useSharedValue,
|
||||||
|
withSpring,
|
||||||
|
withTiming,
|
||||||
|
interpolate,
|
||||||
|
Extrapolation
|
||||||
|
} from 'react-native-reanimated';
|
||||||
import Screens from '../Screens';
|
import Screens from '../Screens';
|
||||||
import colors from '../Constant/Color';
|
|
||||||
import NavigationString from '../Constant/NavigationString';
|
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 { 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_BUTTON_WIDTH = width / 4;
|
||||||
|
|
||||||
const Tab = createBottomTabNavigator();
|
const Tab = createBottomTabNavigator();
|
||||||
|
|
||||||
const TabButton = ({ route, isFocused, onPress }: any) => {
|
const TabButton = ({ route, isFocused, onPress }: any) => {
|
||||||
const icons: Record<string, { outline: any; filled: any }> = {
|
const { colors } = useTheme();
|
||||||
Home: { outline: "Home", filled: "Home" },
|
const icons: Record<string, { outline: keyof typeof LucideIcons; filled: keyof typeof LucideIcons }> = {
|
||||||
|
Home: { outline: "House", filled: "House" },
|
||||||
Auction: { outline: "Gavel", filled: "Gavel" },
|
Auction: { outline: "Gavel", filled: "Gavel" },
|
||||||
Tender: { outline: "FileText", filled: "FileText" },
|
Tender: { outline: "FileText", filled: "FileText" },
|
||||||
Profile: { outline: "User", filled: "User" },
|
Profile: { outline: "User", filled: "User" },
|
||||||
|
|
||||||
};
|
};
|
||||||
const IconName = LucideIcons[isFocused ? icons[route.name].filled : icons[route.name].outline];
|
// Lucide icons don't always have 'filled' variants in the set, so we might use the same icon
|
||||||
const scaleAnim = useRef(new Animated.Value(1)).current;
|
// or rely on color/strokeWidth to indicate state.
|
||||||
const translateYAnim = useRef(new Animated.Value(0)).current;
|
const iconKey = icons[route.name]?.outline || "HelpCircle"; // Fallback
|
||||||
const opacityAnim = useRef(new Animated.Value(1)).current;
|
const IconName = LucideIcons[iconKey] as React.ElementType;
|
||||||
|
|
||||||
|
const scale = useSharedValue(0);
|
||||||
|
const translateY = useSharedValue(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isFocused) {
|
if (isFocused) {
|
||||||
Animated.parallel([
|
scale.value = withSpring(1, { damping: 12, stiffness: 100 });
|
||||||
Animated.sequence([
|
translateY.value = withSpring(-4, { damping: 12, stiffness: 100 });
|
||||||
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();
|
|
||||||
} else {
|
} else {
|
||||||
Animated.parallel([
|
scale.value = withTiming(0, { duration: 200 });
|
||||||
Animated.spring(scaleAnim, {
|
translateY.value = withSpring(0, { damping: 12, stiffness: 100 });
|
||||||
toValue: 1,
|
|
||||||
friction: 5,
|
|
||||||
tension: 150,
|
|
||||||
useNativeDriver: true,
|
|
||||||
}),
|
|
||||||
Animated.timing(opacityAnim, {
|
|
||||||
toValue: 0.6,
|
|
||||||
duration: 150,
|
|
||||||
useNativeDriver: true,
|
|
||||||
}),
|
|
||||||
]).start();
|
|
||||||
}
|
}
|
||||||
}, [isFocused]);
|
}, [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 (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
style={[styles.tabButton, { width: TAB_BUTTON_WIDTH }]}
|
style={styles.tabButton}
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
>
|
>
|
||||||
<Animated.View
|
<View style={styles.tabContent}>
|
||||||
style={[
|
<Animated.View style={animatedIconStyle}>
|
||||||
styles.iconContainer,
|
|
||||||
{
|
|
||||||
transform: [{ scale: scaleAnim }, { translateY: translateYAnim }],
|
|
||||||
opacity: opacityAnim,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{IconName && (
|
{IconName && (
|
||||||
<IconName
|
<IconName
|
||||||
size={20}
|
size={24}
|
||||||
color={isFocused ? colors.primary : '#888'}
|
color={isFocused ? colors.primary : colors.text}
|
||||||
strokeWidth={2}
|
strokeWidth={isFocused ? 2.5 : 2}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
<Text
|
{isFocused && (
|
||||||
|
<Animated.Text
|
||||||
style={[
|
style={[
|
||||||
styles.label,
|
styles.label,
|
||||||
|
animatedTextStyle,
|
||||||
{
|
{
|
||||||
color: isFocused ? colors.primary : '#888',
|
color: colors.primary,
|
||||||
fontWeight: isFocused ? '600' : '400',
|
fontFamily: typography.fonts.medium,
|
||||||
|
fontWeight: undefined, // Remove fontWeight as we use fontFamily
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
{route.name === 'BrandDay' ? 'Coupons' : route.name}
|
{route.name === 'BrandDay' ? 'Coupons' : route.name}
|
||||||
</Text>
|
</Animated.Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CustomTabBar = ({ state, descriptors, navigation }: any) => {
|
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 (
|
return (
|
||||||
<View style={styles.tabBar}>
|
<View style={[
|
||||||
|
styles.tabBar,
|
||||||
|
{
|
||||||
|
backgroundColor: colors.background, // Use transparent to allow shadow to show
|
||||||
|
shadowColor: colors.shadow, // Dynamic shadow color
|
||||||
|
shadowOffset: { width: 0, height: 10 },
|
||||||
|
shadowOpacity: 0.2, // Consistent opacity
|
||||||
|
shadowRadius: 10,
|
||||||
|
borderColor: colors.border, // Subtle border for definition
|
||||||
|
borderWidth: 3,
|
||||||
|
elevation: 20,
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.slidingIndicator,
|
||||||
|
{
|
||||||
|
width: TAB_WIDTH,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
opacity: 0.3,
|
||||||
|
elevation: 10, // Subtle elevation for the indicator
|
||||||
|
},
|
||||||
|
animatedIndicatorStyle,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
{state.routes.map((route: any, index: number) => {
|
{state.routes.map((route: any, index: number) => {
|
||||||
const isFocused = state.index === index;
|
const isFocused = state.index === index;
|
||||||
|
|
||||||
@@ -136,8 +185,9 @@ const CustomTabBar = ({ state, descriptors, navigation }: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function BottomTab() {
|
export default function BottomTab() {
|
||||||
|
const { colors } = useTheme();
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
tabBar={(props) => <CustomTabBar {...props} />}
|
tabBar={(props) => <CustomTabBar {...props} />}
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
@@ -159,29 +209,54 @@ export default function BottomTab() {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#fff',
|
|
||||||
},
|
},
|
||||||
tabBar: {
|
tabBar: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 20,
|
||||||
|
left: 20,
|
||||||
|
right: 20,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
height: TAB_BAR_HEIGHT,
|
height: 70,
|
||||||
backgroundColor: '#fff',
|
borderRadius: 35,
|
||||||
borderTopWidth: 1,
|
elevation: 20, // stronger Android elevation
|
||||||
borderTopColor: '#eee',
|
shadowColor: '#000', // iOS shadow color
|
||||||
elevation: 5,
|
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: {
|
tabButton: {
|
||||||
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
},
|
},
|
||||||
iconContainer: {
|
tabContent: {
|
||||||
marginBottom: 4,
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
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: {
|
label: {
|
||||||
fontSize: 11,
|
fontSize: 11, // Slightly smaller text
|
||||||
textAlign: 'center',
|
textAlign: 'left',
|
||||||
includeFontPadding: false,
|
textTransform: 'capitalize',
|
||||||
|
letterSpacing: 0.2, // Tighter spacing
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.themeOption,
|
||||||
|
isActive && { backgroundColor: colors.primary + '20' } // 20% opacity
|
||||||
|
]}
|
||||||
|
onPress={() => toggleTheme(mode)}
|
||||||
|
>
|
||||||
|
<Icon size={20} color={isActive ? colors.primary : colors.textSecondary} />
|
||||||
|
<AppText variant="medium" style={{ marginLeft: 10, color: isActive ? colors.primary : colors.textSecondary }}>
|
||||||
|
{label}
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, backgroundColor: colors.background }}>
|
||||||
|
<DrawerContentScrollView {...props} contentContainerStyle={{ paddingTop: 0 }}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={[styles.header, { backgroundColor: colors.surface, borderBottomColor: colors.border }]}>
|
||||||
|
<Image source={ImagePath.Icon.Logo} style={styles.logo} resizeMode="contain" />
|
||||||
|
<AppText variant="bold" size={20} style={{ color: colors.text }}>ProcureLinc</AppText>
|
||||||
|
<AppText variant="regular" size={12} style={{ color: colors.textSecondary }}>Smart Procurement</AppText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ flex: 1, paddingTop: 10 }}>
|
||||||
|
<DrawerItemList {...props} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Theme Switch Section */}
|
||||||
|
<View style={[styles.section, { borderTopColor: colors.border }]}>
|
||||||
|
<AppText variant="bold" style={{ marginLeft: 16, marginBottom: 10, color: colors.text }}>Running Theme</AppText>
|
||||||
|
<View style={styles.themeRow}>
|
||||||
|
{renderThemeOption('light', 'Light', Sun)}
|
||||||
|
{renderThemeOption('dark', 'Dark', Moon)}
|
||||||
|
{renderThemeOption('system', 'System', Monitor)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</DrawerContentScrollView>
|
||||||
|
|
||||||
|
<View style={[styles.footer, { borderTopColor: colors.border }]}>
|
||||||
|
<AppText variant="regular" size={12} style={{ color: colors.textSecondary, textAlign: 'center' }}>
|
||||||
|
Version 1.0.0
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -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 (
|
||||||
|
<Drawer.Navigator
|
||||||
|
drawerContent={(props) => <CustomDrawer {...props} />}
|
||||||
|
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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Drawer.Screen
|
||||||
|
name="MainTabs"
|
||||||
|
component={BottomTab}
|
||||||
|
options={{ title: 'Home' }}
|
||||||
|
/>
|
||||||
|
{/* Add other drawer screens here that are NOT in the bottom tab */}
|
||||||
|
</Drawer.Navigator>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DrawerStack;
|
||||||
@@ -4,35 +4,20 @@ import NavigationString from '../Constant/NavigationString';
|
|||||||
import Screens from '../Screens';
|
import Screens from '../Screens';
|
||||||
import colors from '../Constant/Color';
|
import colors from '../Constant/Color';
|
||||||
import BottomTab from './Bottom';
|
import BottomTab from './Bottom';
|
||||||
|
import Login from '../Screens/Login/Login';
|
||||||
|
|
||||||
export default function MainStack() {
|
export default function MainStack() {
|
||||||
const Stack = createStackNavigator();
|
const Stack = createStackNavigator();
|
||||||
const option = { headerShown: false };
|
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 (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
// ...
|
||||||
cardStyle: { backgroundColor: colors.white },
|
|
||||||
...fadeTransition
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Stack.Screen name={NavigationString.Splash} component={Screens.Splash} options={option} />
|
<Stack.Screen name={NavigationString.Splash} component={Screens.Splash} options={option} />
|
||||||
<Stack.Screen name={NavigationString.Signup} component={Screens.Signup} options={option} />
|
<Stack.Screen name={NavigationString.Signup} component={Screens.Signup} options={option} />
|
||||||
<Stack.Screen name={NavigationString.Onboarding} component={Screens.Onboarding} options={option} />
|
<Stack.Screen name={NavigationString.Onboarding} component={Screens.Onboarding} options={option} />
|
||||||
<Stack.Screen name={NavigationString.Login} component={Screens.Login} options={option} />
|
<Stack.Screen name={NavigationString.Login} component={Login} options={option} />
|
||||||
|
|
||||||
<Stack.Screen name={NavigationString.Home} component={BottomTab} options={option} />
|
<Stack.Screen name={NavigationString.Home} component={BottomTab} options={option} />
|
||||||
<Stack.Screen name={NavigationString.PurchaseList} component={Screens.PurchaseList} options={option} />
|
<Stack.Screen name={NavigationString.PurchaseList} component={Screens.PurchaseList} options={option} />
|
||||||
<Stack.Screen name={NavigationString.Emd} component={Screens.Emd} options={option} />
|
<Stack.Screen name={NavigationString.Emd} component={Screens.Emd} options={option} />
|
||||||
@@ -47,6 +32,11 @@ export default function MainStack() {
|
|||||||
<Stack.Screen name={NavigationString.Partnership} component={Screens.Partnership} options={option} />
|
<Stack.Screen name={NavigationString.Partnership} component={Screens.Partnership} options={option} />
|
||||||
<Stack.Screen name={NavigationString.LiveTenderDetail} component={Screens.LiveTenderDetail} options={option} />
|
<Stack.Screen name={NavigationString.LiveTenderDetail} component={Screens.LiveTenderDetail} options={option} />
|
||||||
<Stack.Screen name={NavigationString.ResetPassword} component={Screens.ResetPassword} options={option} />
|
<Stack.Screen name={NavigationString.ResetPassword} component={Screens.ResetPassword} options={option} />
|
||||||
|
<Stack.Screen name={NavigationString.KycWebViewScreen} component={Screens.KycWebViewScreen} options={option} />
|
||||||
|
<Stack.Screen name={NavigationString.StageThreeScreen} component={Screens.StageThreeScreen} options={option} />
|
||||||
|
<Stack.Screen name={NavigationString.DecryptionScreen} component={Screens.DecryptionScreen} options={option} />
|
||||||
|
<Stack.Screen name={NavigationString.DeviceBinding} component={Screens.DeviceBinding} options={option} />
|
||||||
|
<Stack.Screen name={NavigationString.DscBinding} component={Screens.DscBinding} options={option} />
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { createNavigationContainerRef, CommonActions } from '@react-navigation/native';
|
||||||
|
|
||||||
|
export const navigationRef = createNavigationContainerRef<any>();
|
||||||
|
|
||||||
|
export function navigate(name: string, params?: any) {
|
||||||
|
if (navigationRef.isReady()) {
|
||||||
|
navigationRef.navigate(name, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetAndNavigate(name: string) {
|
||||||
|
if (navigationRef.isReady()) {
|
||||||
|
navigationRef.dispatch(
|
||||||
|
CommonActions.reset({
|
||||||
|
index: 0,
|
||||||
|
routes: [{ name }],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ThemeMode>) => {
|
||||||
|
state.mode = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setThemeMode } = themeSlice.actions;
|
||||||
|
|
||||||
|
export const selectThemeMode = (state: any) => state.theme.mode;
|
||||||
|
|
||||||
|
export default themeSlice.reducer;
|
||||||
@@ -17,4 +17,5 @@ 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;
|
export default tokenSlice.reducer;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {auctionSlice} from './Slices/SelectedAuction';
|
|||||||
import selectPurchaseAuctionReducer from './Slices/selectPurchaseAuction'
|
import selectPurchaseAuctionReducer from './Slices/selectPurchaseAuction'
|
||||||
import selectPurchaseTenderReducer from './Slices/SelectedPurchaseTender';
|
import selectPurchaseTenderReducer from './Slices/SelectedPurchaseTender';
|
||||||
import { tenderSlice } from './Slices/SelectedTender';
|
import { tenderSlice } from './Slices/SelectedTender';
|
||||||
|
import { themeSlice } from './Slices/ThemeSlice';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
token: tokenSlice.reducer,
|
token: tokenSlice.reducer,
|
||||||
@@ -14,11 +15,12 @@ const rootReducer = combineReducers({
|
|||||||
selectPurchaseAuction: selectPurchaseAuctionReducer,
|
selectPurchaseAuction: selectPurchaseAuctionReducer,
|
||||||
selectPurchaseTender: selectPurchaseTenderReducer,
|
selectPurchaseTender: selectPurchaseTenderReducer,
|
||||||
tender: tenderSlice.reducer,
|
tender: tenderSlice.reducer,
|
||||||
|
theme: themeSlice.reducer,
|
||||||
});
|
});
|
||||||
const persistConfig = {
|
const persistConfig = {
|
||||||
key: 'root',
|
key: 'root',
|
||||||
storage: AsyncStorage,
|
storage: AsyncStorage,
|
||||||
whitelist: ['token'],
|
whitelist: ['token', 'theme'],
|
||||||
};
|
};
|
||||||
const persistReducers = persistReducer(persistConfig, rootReducer);
|
const persistReducers = persistReducer(persistConfig, rootReducer);
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
|
|||||||
@@ -1,26 +1,34 @@
|
|||||||
import React, { useState, useRef, useMemo } from 'react';
|
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 { InsideHeader } from '../../Component/Index';
|
||||||
import { useFetchLiveAuction, useFetchPurchaseAuction } from '../../Services/BBPS/Hooks';
|
import { useFetchLiveAuction, useFetchPurchaseAuction } from '../../Services/ApiCall/Hooks';
|
||||||
import colors from '../../Constant/Color';
|
|
||||||
import LiveAuction from './LiveAuction';
|
import LiveAuction from './LiveAuction';
|
||||||
import PurchaseAuction from './PurchaseAuction';
|
import PurchaseAuction from './PurchaseAuction';
|
||||||
import { ShoppingCart } from 'lucide-react-native';
|
import { ShoppingCart, Lock, Radio, Package } from 'lucide-react-native';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import NavigationString from '../../Constant/NavigationString';
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
import FontsFamily from '../../Constant/FontsFamily';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
|
import AppText from '../../components/AppText';
|
||||||
|
|
||||||
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
||||||
const HORIZONTAL_PADDING = 32;
|
const HORIZONTAL_PADDING = 32;
|
||||||
const SWITCH_INSETS = 2;
|
const SWITCH_INSETS = 3;
|
||||||
const TAB_HEIGHT = 44;
|
const TAB_HEIGHT = 48;
|
||||||
|
|
||||||
const Auction = () => {
|
const Auction = () => {
|
||||||
|
const { colors, spacing, shadows } = useTheme();
|
||||||
const Navigation = useNavigation() as any;
|
const Navigation = useNavigation() as any;
|
||||||
const { data: liveAuctionData = {} }: any = useFetchLiveAuction();
|
const { data: liveAuctionData = {} }: any = useFetchLiveAuction();
|
||||||
const { data: purchaseAuctionData = {} } = useFetchPurchaseAuction();
|
const { data: purchaseAuctionData = {} } = useFetchPurchaseAuction();
|
||||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
||||||
|
const reduxToken = useSelector((state: any) => state.token?.token);
|
||||||
|
|
||||||
const liveCount = Array.isArray(liveAuctionData?.result)
|
const liveCount = Array.isArray(liveAuctionData?.result)
|
||||||
? liveAuctionData?.pages?.[0]?.totalCount
|
? liveAuctionData?.pages?.[0]?.totalCount
|
||||||
@@ -30,15 +38,14 @@ const Auction = () => {
|
|||||||
? purchaseAuctionData.data.length
|
? purchaseAuctionData.data.length
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const [selectedTab, setSelectedTab] = useState<'live' | 'purchase'>('live');
|
|
||||||
|
|
||||||
const translateX = useRef(new Animated.Value(0)).current;
|
|
||||||
|
|
||||||
const TAB_WIDTH = useMemo(() => {
|
const TAB_WIDTH = useMemo(() => {
|
||||||
const availableWidth = SCREEN_WIDTH - HORIZONTAL_PADDING - SWITCH_INSETS * 2;
|
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) => {
|
const onTabPress = (tab: 'live' | 'purchase', index: number) => {
|
||||||
setSelectedTab(tab);
|
setSelectedTab(tab);
|
||||||
Animated.spring(translateX, {
|
Animated.spring(translateX, {
|
||||||
@@ -48,147 +55,251 @@ const Auction = () => {
|
|||||||
}).start();
|
}).start();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const cartCount = SelectedPurchaseList.length;
|
||||||
|
|
||||||
|
const styles = makeStyles(colors, spacing, shadows, TAB_WIDTH, TAB_HEIGHT);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<InsideHeader title="Auction" showArrow />
|
<InsideHeader title="Auction" showArrow />
|
||||||
|
|
||||||
|
{/* Tab Row */}
|
||||||
<View style={styles.topRow}>
|
<View style={styles.topRow}>
|
||||||
|
{/* Segmented Control */}
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.switchWrapper,
|
styles.switchWrapper,
|
||||||
{
|
{ width: TAB_WIDTH * 2 + SWITCH_INSETS * 2 },
|
||||||
width: TAB_WIDTH * 2 + SWITCH_INSETS * 2,
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
{/* Sliding pill */}
|
||||||
<Animated.View
|
<Animated.View
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
style={[
|
style={[
|
||||||
styles.activeTab,
|
styles.activeTab,
|
||||||
{
|
{ width: TAB_WIDTH, transform: [{ translateX }] },
|
||||||
width: TAB_WIDTH,
|
|
||||||
transform: [{ translateX }],
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Live Tab */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.tab, { width: TAB_WIDTH }]}
|
style={[styles.tab, { width: TAB_WIDTH }]}
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
onPress={() => onTabPress('live', 0)}
|
onPress={() => onTabPress('live', 0)}
|
||||||
>
|
>
|
||||||
<Text
|
<Radio
|
||||||
|
size={13}
|
||||||
|
color={selectedTab === 'live' ? colors.primary : colors.textSecondary}
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
<AppText
|
||||||
|
variant="caption"
|
||||||
|
color={selectedTab === 'live' ? colors.primary : colors.textSecondary}
|
||||||
style={[
|
style={[
|
||||||
styles.tabText,
|
styles.tabText,
|
||||||
selectedTab === 'live' && styles.activeText,
|
selectedTab === 'live' && (styles.activeTabText as any),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Live ({liveCount})
|
Live {liveCount ? `(${liveCount})` : ''}
|
||||||
</Text>
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Purchase Tab */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.tab, { width: TAB_WIDTH }]}
|
style={[styles.tab, { width: TAB_WIDTH }]}
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
onPress={() => onTabPress('purchase', 1)}
|
onPress={() => onTabPress('purchase', 1)}
|
||||||
>
|
>
|
||||||
<Text
|
<Package
|
||||||
|
size={13}
|
||||||
|
color={selectedTab === 'purchase' ? colors.primary : colors.textSecondary}
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
<AppText
|
||||||
|
variant="caption"
|
||||||
|
color={selectedTab === 'purchase' ? colors.primary : colors.textSecondary}
|
||||||
style={[
|
style={[
|
||||||
styles.tabText,
|
styles.tabText,
|
||||||
selectedTab === 'purchase' && styles.activeText,
|
selectedTab === 'purchase' && (styles.activeTabText as any),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Purchased ({purchaseCount})
|
Purchased {purchaseCount ? `(${purchaseCount})` : ''}
|
||||||
</Text>
|
</AppText>
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View>
|
|
||||||
{SelectedPurchaseList.length ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: colors.white,
|
|
||||||
backgroundColor: colors.primaryLight,
|
|
||||||
position: 'absolute',
|
|
||||||
borderRadius: 50,
|
|
||||||
zIndex: 20,
|
|
||||||
right: 0,
|
|
||||||
top: -10,
|
|
||||||
paddingHorizontal: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={{ fontSize: 12, color: colors.black }}>
|
|
||||||
{SelectedPurchaseList.length}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{
|
|
||||||
padding: 10,
|
|
||||||
backgroundColor: colors.primaryLight,
|
|
||||||
borderRadius: 20,
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
Navigation.navigate(NavigationString.PurchaseList);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ShoppingCart size={22} color={colors.black} strokeWidth={2} />
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
{selectedTab === 'live' ? <LiveAuction /> : <PurchaseAuction />}
|
{selectedTab === 'live' ? <LiveAuction /> : <PurchaseAuction />}
|
||||||
|
|
||||||
|
{/* Cart Floating Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.cartFloatingButton}
|
||||||
|
activeOpacity={0.9}
|
||||||
|
onPress={() => Navigation.navigate(NavigationString.PurchaseList)}
|
||||||
|
>
|
||||||
|
<ShoppingCart size={24} color={colors.buttonText} strokeWidth={2} />
|
||||||
|
{cartCount > 0 && (
|
||||||
|
<View style={styles.cartBadge}>
|
||||||
|
<AppText variant="caption" color={colors.primary} style={styles.cartBadgeText as any}>
|
||||||
|
{cartCount}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Auction;
|
export default Auction;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
/* ─── Styles factory ─── */
|
||||||
|
const makeStyles = (colors: any, spacing: any, shadows: any, TAB_WIDTH: number, TAB_HEIGHT: number) =>
|
||||||
|
StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.background,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* ── Tab Row ── */
|
||||||
topRow: {
|
topRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-around',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingHorizontal: 16,
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: spacing.m,
|
||||||
|
paddingVertical: spacing.s + 2,
|
||||||
},
|
},
|
||||||
switchWrapper: {
|
switchWrapper: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
marginVertical: 14,
|
backgroundColor: colors.surface,
|
||||||
backgroundColor: '#f1f5f9',
|
borderRadius: 16,
|
||||||
borderRadius: 18,
|
padding: SWITCH_INSETS,
|
||||||
padding: 2,
|
|
||||||
height: TAB_HEIGHT,
|
height: TAB_HEIGHT,
|
||||||
|
...shadows.light,
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
height: TAB_HEIGHT - 4,
|
height: TAB_HEIGHT - SWITCH_INSETS * 2,
|
||||||
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
gap: 5,
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
},
|
},
|
||||||
tabText: {
|
tabText: {
|
||||||
fontSize: 13,
|
fontWeight: '600',
|
||||||
color: '#64748b',
|
letterSpacing: 0.3,
|
||||||
lineHeight: 18,
|
} as any,
|
||||||
fontFamily: FontsFamily.poppinsSemiBold,
|
activeTabText: {
|
||||||
},
|
fontWeight: '700',
|
||||||
activeText: {
|
} as any,
|
||||||
color: colors.primary,
|
|
||||||
},
|
|
||||||
activeTab: {
|
activeTab: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 2,
|
top: SWITCH_INSETS,
|
||||||
left: 2,
|
left: SWITCH_INSETS,
|
||||||
height: TAB_HEIGHT - 4,
|
height: TAB_HEIGHT - SWITCH_INSETS * 2,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: colors.background,
|
||||||
borderRadius: 16,
|
borderRadius: 13,
|
||||||
shadowColor: '#000',
|
...shadows.medium,
|
||||||
shadowOpacity: 0.1,
|
|
||||||
shadowRadius: 6,
|
|
||||||
elevation: 3,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* ── 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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,69 +1,76 @@
|
|||||||
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
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 Animated, { FadeInDown, FadeOutUp } from 'react-native-reanimated';
|
||||||
|
import { RefreshCw } from 'lucide-react-native';
|
||||||
import LiveAuctionTile from './LiveAuctionTile';
|
import LiveAuctionTile from './LiveAuctionTile';
|
||||||
import { useFetchLiveAuction } from '../../Services/BBPS/Hooks';
|
import { useFetchLiveAuction } from '../../Services/ApiCall/Hooks';
|
||||||
import { SearchInput } from '../../Component/Serach/SeacrhInput';
|
import { SearchInput } from '../../Component/Serach/SeacrhInput';
|
||||||
import { Skelton } from '../../Component/Index';
|
import { Skelton } from '../../Component/Index';
|
||||||
import colors from '../../Constant/Color';
|
import SkeltonList from '../../Component/Skelton/SkeltonList';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
import { togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
||||||
import { apiCall } from '../../Axios/Axios';
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
import { GET_MY_AUCTION } from '../../Services/BBPS/ApiUrls';
|
import AppText from '../../components/AppText';
|
||||||
import SkeltonList from '../../Component/Skelton/SkeltonList';
|
|
||||||
import Skeleton from '../../Component/Skelton/Skelton';
|
|
||||||
|
|
||||||
const LiveAuction = () => {
|
const LiveAuction = () => {
|
||||||
const Dispatch = useDispatch()
|
const Dispatch = useDispatch();
|
||||||
|
const { colors, spacing, shadows } = useTheme();
|
||||||
|
const user = useSelector((state: any) => state.token?.token);
|
||||||
const onEndReachedCalledDuringMomentum = useRef(true);
|
const onEndReachedCalledDuringMomentum = useRef(true);
|
||||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
||||||
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
|
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 {
|
||||||
|
data,
|
||||||
const getMyAuction = async () => {
|
fetchNextPage,
|
||||||
try {
|
hasNextPage,
|
||||||
setAllBoolean(prev => ({ ...prev, isLoading: true }))
|
isFetchingNextPage,
|
||||||
const res = await apiCall<any>('get', `${GET_MY_AUCTION}`,);
|
isLoading,
|
||||||
if (res?.statusCode === 200) {
|
isRefetching,
|
||||||
const purchasedIds = new Set(res.data.map((item: any) => item.auctionId));
|
refetch,
|
||||||
const filteredLive = data?.result.map((item: any) => ({ ...item, active: !purchasedIds.has(item.auctionId) }));
|
isPending,
|
||||||
setAllBoolean(prev => ({ ...prev, liveAuctionData: filteredLive }));
|
}: any = useFetchLiveAuction();
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('getMyAuction error', error);
|
|
||||||
} finally {
|
|
||||||
setAllBoolean(prev => ({ ...prev, isLoading: false }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({ item, index }: any) => {
|
({ item, index }: any) => {
|
||||||
const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.auctionNumber === item.auctionNumber)
|
const isPurchase = SelectedPurchaseList.findIndex(
|
||||||
return <LiveAuctionTile
|
(prev: any) => prev.auctionNumber === item.auctionNumber,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<LiveAuctionTile
|
||||||
data={item}
|
data={item}
|
||||||
index={index}
|
index={index}
|
||||||
|
isPurchasedByContractor={item.contractorId?.includes(
|
||||||
|
String(user.contractorId),
|
||||||
|
)}
|
||||||
auctionId={item.auctionId}
|
auctionId={item.auctionId}
|
||||||
auctionDispatchDate={item.auctionDispatchDate}
|
auctionDispatchDate={item.auctionDispatchDate}
|
||||||
isPurchased={isPurchase !== -1 ? true : false}
|
isPurchased={isPurchase !== -1}
|
||||||
purchaseEnd={item.purchaseEnd}
|
purchaseEnd={item.purchaseEnd}
|
||||||
auctionOfWork={item.auctionOfWork}
|
auctionOfWork={item.auctionOfWork}
|
||||||
auctionFees={item.auctionFees}
|
auctionFees={item.auctionFees}
|
||||||
auctionNumber={item.auctionNumber}
|
auctionNumber={item.auctionNumber}
|
||||||
onPurchasePress={() => { Dispatch(togglePurchaseAuction(item)) }}
|
onPurchasePress={() => {
|
||||||
|
console.log('[LiveAuction] purchase toggled:', item?.auctionNumber);
|
||||||
|
Dispatch(togglePurchaseAuction(item));
|
||||||
|
}}
|
||||||
onDetailsPress={() => {}}
|
onDetailsPress={() => {}}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[SelectedPurchaseList]
|
[SelectedPurchaseList],
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadMore = useCallback(() => {
|
const loadMore = useCallback(() => {
|
||||||
if (onEndReachedCalledDuringMomentum.current) return;
|
if (onEndReachedCalledDuringMomentum.current) return;
|
||||||
|
|
||||||
if (!hasNextPage || isFetchingNextPage) return;
|
if (!hasNextPage || isFetchingNextPage) return;
|
||||||
|
console.log('[LiveAuction] loadMore: fetching next page');
|
||||||
fetchNextPage();
|
fetchNextPage();
|
||||||
onEndReachedCalledDuringMomentum.current = true;
|
onEndReachedCalledDuringMomentum.current = true;
|
||||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||||
@@ -71,91 +78,80 @@ const LiveAuction = () => {
|
|||||||
const renderFooter = useCallback(() => {
|
const renderFooter = useCallback(() => {
|
||||||
if (!isFetchingNextPage) return null;
|
if (!isFetchingNextPage) return null;
|
||||||
return (
|
return (
|
||||||
<View style={{ paddingVertical: 12 }}>
|
<View style={styles.footerLoader}>
|
||||||
<ActivityIndicator size="small" color={colors.primary} />
|
<ActivityIndicator size="small" color={colors.primary} />
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>
|
||||||
|
Loading more…
|
||||||
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}, [isFetchingNextPage]);
|
}, [isFetchingNextPage, colors]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isRefetching) {
|
if (isRefetching) {
|
||||||
|
console.log('[LiveAuction] refetch started');
|
||||||
setShowRefreshBanner(true);
|
setShowRefreshBanner(true);
|
||||||
} else {
|
} else {
|
||||||
const timer = setTimeout(() => {
|
console.log('[LiveAuction] refetch completed');
|
||||||
setShowRefreshBanner(false);
|
const timer = setTimeout(() => setShowRefreshBanner(false), 1000);
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [isRefetching]);
|
}, [isRefetching]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.result?.length > 0) {
|
console.log('[LiveAuction] data updated:', {
|
||||||
getMyAuction()
|
count: data?.result?.length ?? 0,
|
||||||
}
|
hasNextPage,
|
||||||
}, [data])
|
isFetchingNextPage,
|
||||||
|
isPending,
|
||||||
|
});
|
||||||
|
}, [data, hasNextPage, isFetchingNextPage, isPending]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Skelton />;
|
return <Skelton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={styles.root}>
|
||||||
<SearchInput onChange={() => { }} placeholder="Search...." filter={false} />
|
{/* Search */}
|
||||||
|
<SearchInput onChange={() => {}} placeholder="Search auctions…" filter={false} />
|
||||||
|
|
||||||
|
{/* Refresh Banner */}
|
||||||
{showRefreshBanner && (
|
{showRefreshBanner && (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={FadeInDown.duration(200)}
|
entering={FadeInDown.duration(200)}
|
||||||
exiting={FadeOutUp.duration(500)}
|
exiting={FadeOutUp.duration(400)}
|
||||||
style={{
|
style={[
|
||||||
alignSelf: 'center',
|
styles.refreshBanner,
|
||||||
flexDirection: 'row',
|
{
|
||||||
alignItems: 'center',
|
backgroundColor: colors.surface,
|
||||||
gap: 8,
|
...shadows.medium,
|
||||||
paddingVertical: 8,
|
},
|
||||||
paddingHorizontal: 14,
|
]}
|
||||||
marginTop: 6,
|
|
||||||
borderRadius: 20,
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOpacity: 0.06,
|
|
||||||
shadowRadius: 8,
|
|
||||||
shadowOffset: { width: 0, height: 3 },
|
|
||||||
elevation: 4,
|
|
||||||
zIndex: 10, // Ensure it stays on top
|
|
||||||
position: 'absolute',
|
|
||||||
top: 60, // Adjust based on your SearchInput height
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ActivityIndicator size="small" color={colors.primary} />
|
<ActivityIndicator size="small" color={colors.primary} />
|
||||||
<Text
|
<AppText variant="caption" color={colors.text} style={styles.refreshText as any}>
|
||||||
style={{
|
Refreshing auctions…
|
||||||
fontSize: 13,
|
</AppText>
|
||||||
color: '#374151',
|
|
||||||
fontWeight: '500',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Refreshing auctions
|
|
||||||
</Text>
|
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)}
|
)}
|
||||||
{
|
|
||||||
isPending || allBoolean.isLoading ? <Skeleton /> :
|
|
||||||
<FlatList
|
|
||||||
data={allBoolean.liveAuctionData ?? []}
|
|
||||||
renderItem={renderItem}
|
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
contentContainerStyle={{ paddingBottom: 50, paddingTop: 10 }}
|
|
||||||
|
|
||||||
|
{/* List */}
|
||||||
|
{isPending ? (
|
||||||
|
<SkeltonList />
|
||||||
|
) : (
|
||||||
|
<FlatList
|
||||||
|
data={data?.result ?? []}
|
||||||
|
renderItem={renderItem}
|
||||||
|
keyExtractor={(item: any) => item?.auctionId?.toString()}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={styles.listContent}
|
||||||
onEndReached={loadMore}
|
onEndReached={loadMore}
|
||||||
onEndReachedThreshold={0.1}
|
onEndReachedThreshold={0.1}
|
||||||
|
|
||||||
onMomentumScrollBegin={() => {
|
onMomentumScrollBegin={() => {
|
||||||
onEndReachedCalledDuringMomentum.current = false;
|
onEndReachedCalledDuringMomentum.current = false;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
ListFooterComponent={renderFooter}
|
ListFooterComponent={renderFooter}
|
||||||
|
|
||||||
refreshControl={
|
refreshControl={
|
||||||
<RefreshControl
|
<RefreshControl
|
||||||
refreshing={isRefetching}
|
refreshing={isRefetching}
|
||||||
@@ -164,18 +160,48 @@ const LiveAuction = () => {
|
|||||||
colors={[colors.primary]}
|
colors={[colors.primary]}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
initialNumToRender={6}
|
initialNumToRender={6}
|
||||||
maxToRenderPerBatch={6}
|
maxToRenderPerBatch={6}
|
||||||
windowSize={7}
|
windowSize={7}
|
||||||
removeClippedSubviews
|
removeClippedSubviews
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LiveAuction;
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
import React, { memo } from 'react';
|
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 moment from 'moment';
|
||||||
import colors from '../../Constant/Color';
|
|
||||||
import { LiveAuctionTileProps } from './types/types';
|
import { LiveAuctionTileProps } from './types/types';
|
||||||
import AuctionDetail from './Modal/AuctionDetail';
|
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<LiveAuctionTileProps> = ({
|
const LiveAuctionTile: React.FC<LiveAuctionTileProps & { index?: number }> = ({
|
||||||
data,
|
data,
|
||||||
|
index = 0,
|
||||||
isPurchased = false,
|
isPurchased = false,
|
||||||
onPurchasePress,
|
onPurchasePress,
|
||||||
auctionDispatchDate,
|
auctionDispatchDate,
|
||||||
@@ -15,230 +28,252 @@ const LiveAuctionTile: React.FC<LiveAuctionTileProps> = ({
|
|||||||
purchaseEnd,
|
purchaseEnd,
|
||||||
auctionFees,
|
auctionFees,
|
||||||
auctionNumber,
|
auctionNumber,
|
||||||
|
isPurchasedByContractor,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colors, spacing, shadows } = useTheme();
|
||||||
const [showDetail, setShowDetail] = React.useState(false);
|
const [showDetail, setShowDetail] = React.useState(false);
|
||||||
|
|
||||||
const purchaseEnded = moment().isAfter(moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss'));
|
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 (
|
return (
|
||||||
<View style={styles.cardWrapper}>
|
<View style={styles.card}>
|
||||||
{
|
<AuctionDetail visible={showDetail} data={data} onClose={() => setShowDetail(false)} />
|
||||||
!showDetail ?
|
|
||||||
<>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.statusTag,
|
|
||||||
{ backgroundColor: isPurchased ? colors.error : purchaseEnded ? '#94a3b8' : '#4ade80' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={styles.container}
|
|
||||||
>
|
|
||||||
{/* Header */}
|
|
||||||
<View style={styles.header}>
|
|
||||||
<TouchableOpacity style={styles.badge} onPress={() => setShowDetail(true)}>
|
|
||||||
<Tag size={14} color={colors.primary} />
|
|
||||||
<Text style={styles.badgeText}>#{auctionNumber}</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<Text style={styles.priceText}>₹{auctionFees}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
);
|
||||||
{/* Work Title */}
|
|
||||||
<Text style={styles.workTitle} numberOfLines={2}>
|
|
||||||
{auctionOfWork ?? 'General Auction Work'}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{/* Info Grid */}
|
|
||||||
<View style={styles.infoGrid}>
|
|
||||||
<View style={styles.infoItem}>
|
|
||||||
<Calendar size={16} color="#64748b" />
|
|
||||||
<View style={styles.infoTextGroup}>
|
|
||||||
<Text style={styles.infoLabel}>Start</Text>
|
|
||||||
<Text style={styles.infoValue}>{auctionDispatchDate ?? '-'}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.infoItem}>
|
|
||||||
<Calendar size={16} color={purchaseEnded ? "#ef4444" : "#22c55e"} />
|
|
||||||
<View style={styles.infoTextGroup}>
|
|
||||||
<Text style={styles.infoLabel}>Deadline</Text>
|
|
||||||
<Text style={[styles.infoValue, purchaseEnded && { color: '#ef4444' }]}>
|
|
||||||
{moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss').format('DD MMM, YY')}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Action Bar */}
|
|
||||||
<View style={styles.actionBar}>
|
|
||||||
<TouchableOpacity style={styles.iconBtn} onPress={() => setShowDetail(true)}>
|
|
||||||
<Info size={18} color={colors.primary} />
|
|
||||||
<Text style={styles.iconBtnText}>Details</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[
|
|
||||||
styles.mainButton,
|
|
||||||
purchaseEnded
|
|
||||||
? styles.btnDisabled
|
|
||||||
: !data.active ? { backgroundColor: 'lightgray' } : isPurchased
|
|
||||||
? styles.btnPurchased
|
|
||||||
: styles.btnActive,
|
|
||||||
]}
|
|
||||||
onPress={onPurchasePress}
|
|
||||||
disabled={purchaseEnded ? true : data.active ? false : true}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
styles.mainButtonText,
|
|
||||||
isPurchased && !purchaseEnded ? { color: colors.white } : { color: '#fff' },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{purchaseEnded ? 'Expired' : !data.active ? 'Purchased' : isPurchased ? 'Remove' : 'Add to Cart'}
|
|
||||||
</Text>
|
|
||||||
{!purchaseEnded && (
|
|
||||||
<ArrowRight size={16} color={isPurchased ? colors.white : '#fff'} />
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Auction Detail Modal */}
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
: <AuctionDetail visible={showDetail} data={data} onClose={() => setShowDetail(false)} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeInDown.delay(index * 60).springify()}
|
||||||
|
style={styles.card}
|
||||||
|
>
|
||||||
|
{/* ── Row 1: Number + Fee ── */}
|
||||||
|
<View style={styles.topRow}>
|
||||||
|
<View style={styles.numberRow}>
|
||||||
|
<Tag size={14} color={colors.primary} strokeWidth={2} />
|
||||||
|
<AppText variant="bodySmall" color={colors.primary} style={styles.auctionNo as any}>
|
||||||
|
#{auctionNumber}
|
||||||
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
<AppText variant="h4" color={colors.primary} style={styles.fee as any}>
|
||||||
|
₹{auctionFees}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* ── Row 2: Title ── */}
|
||||||
|
<AppText variant="h4" color={colors.text} numberOfLines={2} style={styles.title as any}>
|
||||||
|
{auctionOfWork ?? 'General Auction Work'}
|
||||||
|
</AppText>
|
||||||
|
|
||||||
|
{/* ── Row 3: Date strip ── */}
|
||||||
|
<View style={[styles.dateStrip, { backgroundColor: colors.overlay }]}>
|
||||||
|
{/* Start */}
|
||||||
|
<View style={styles.dateCol}>
|
||||||
|
<View style={styles.dateHeader}>
|
||||||
|
<Calendar size={13} color={colors.textSecondary} strokeWidth={2} />
|
||||||
|
<AppText variant="caption" color={colors.textSecondary} style={styles.dateLabel as any}>
|
||||||
|
Start Date
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
<AppText variant="caption" color={colors.text} style={styles.dateValue as any}>
|
||||||
|
{auctionDispatchDate ?? '—'}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Separator */}
|
||||||
|
<View style={[styles.separator, { backgroundColor: colors.border }]} />
|
||||||
|
|
||||||
|
{/* Deadline */}
|
||||||
|
<View style={styles.dateCol}>
|
||||||
|
<View style={styles.dateHeader}>
|
||||||
|
<Clock
|
||||||
|
size={13}
|
||||||
|
color={purchaseEnded ? colors.error : colors.success}
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary} style={styles.dateLabel as any}>
|
||||||
|
Deadline
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
<AppText
|
||||||
|
variant="caption"
|
||||||
|
color={purchaseEnded ? colors.error : colors.text}
|
||||||
|
style={styles.dateValue as any}
|
||||||
|
>
|
||||||
|
{deadlineFormatted}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* ── Row 4: Actions ── */}
|
||||||
|
<View style={styles.actionRow}>
|
||||||
|
{/* Ghost Details button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.detailsBtn, { borderColor: colors.border }]}
|
||||||
|
onPress={() => setShowDetail(true)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={styles.detailsText as any}>
|
||||||
|
View Details
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
{status === 'active' && (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.ctaBtn, { backgroundColor: colors.primary }]}
|
||||||
|
onPress={onPurchasePress}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
>
|
||||||
|
<ShoppingCart size={14} color="#fff" strokeWidth={2.5} />
|
||||||
|
<AppText variant="caption" color="#fff" style={styles.ctaText as any}>
|
||||||
|
ADD TO CART
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
{status === 'in_cart' && (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.ctaBtn, { backgroundColor: colors.success }]}
|
||||||
|
onPress={() => { Navigation.navigate(NavigationString.PurchaseList) }}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
>
|
||||||
|
<ShoppingCart size={14} color="#fff" strokeWidth={2.5} />
|
||||||
|
<AppText variant="caption" color="#fff" style={styles.ctaText as any}>
|
||||||
|
GO TO CART
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
{status === 'purchased' && (
|
||||||
|
<View style={[styles.ctaBtn, { backgroundColor: colors.secondary }]}>
|
||||||
|
<CheckCircle2 size={14} color="#fff" strokeWidth={2.5} />
|
||||||
|
<AppText variant="caption" color="#fff" style={styles.ctaText as any}>
|
||||||
|
PURCHASED
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{status === 'expired' && (
|
||||||
|
<View style={[styles.ctaBtn, { backgroundColor: colors.secondary }]}>
|
||||||
|
<XCircle size={14} color="#fff" strokeWidth={2.5} />
|
||||||
|
<AppText variant="caption" color="#fff" style={styles.ctaText as any}>
|
||||||
|
EXPIRED
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(LiveAuctionTile);
|
export default memo(LiveAuctionTile);
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const makeStyles = (colors: any, spacing: any, shadows: any) =>
|
||||||
cardWrapper: {
|
StyleSheet.create({
|
||||||
marginVertical: 10,
|
card: {
|
||||||
marginHorizontal: 16,
|
backgroundColor: colors.surface,
|
||||||
backgroundColor: '#fff',
|
borderRadius: 18,
|
||||||
borderRadius: 24,
|
borderWidth: 1,
|
||||||
flexDirection: 'row',
|
borderColor: colors.border,
|
||||||
overflow: 'hidden',
|
marginVertical: 7,
|
||||||
|
padding: spacing.m,
|
||||||
|
...shadows.medium,
|
||||||
|
},
|
||||||
|
|
||||||
// Modern shadow
|
/* Top row */
|
||||||
shadowColor: '#000',
|
topRow: {
|
||||||
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',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: 12,
|
marginBottom: spacing.s,
|
||||||
},
|
},
|
||||||
badge: {
|
numberRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: colors.primary + '25',
|
gap: 5,
|
||||||
paddingHorizontal: 12,
|
|
||||||
paddingVertical: 5,
|
|
||||||
borderRadius: 12,
|
|
||||||
gap: 6,
|
|
||||||
},
|
},
|
||||||
badgeText: {
|
auctionNo: {
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: colors.primary,
|
} as any,
|
||||||
},
|
fee: {
|
||||||
priceText: {
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
color: '#0f172a',
|
} as any,
|
||||||
},
|
|
||||||
workTitle: {
|
/* Title */
|
||||||
fontSize: 16,
|
title: {
|
||||||
fontWeight: '600',
|
fontWeight: '700',
|
||||||
color: '#334155',
|
|
||||||
lineHeight: 24,
|
lineHeight: 24,
|
||||||
marginBottom: 16,
|
marginBottom: spacing.m,
|
||||||
},
|
} as any,
|
||||||
infoGrid: {
|
|
||||||
flexDirection: 'row',
|
/* Date strip */
|
||||||
justifyContent: 'space-between',
|
dateStrip: {
|
||||||
backgroundColor: '#f0f4f8',
|
|
||||||
paddingVertical: 12,
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
borderRadius: 16,
|
|
||||||
marginBottom: 18,
|
|
||||||
},
|
|
||||||
infoItem: {
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 10,
|
borderRadius: 12,
|
||||||
|
paddingVertical: spacing.s + 2,
|
||||||
|
paddingHorizontal: spacing.m,
|
||||||
|
marginBottom: spacing.m,
|
||||||
},
|
},
|
||||||
infoTextGroup: {
|
dateCol: {
|
||||||
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,
|
flex: 1,
|
||||||
height: 46,
|
},
|
||||||
borderRadius: 16,
|
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',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
gap: 8,
|
gap: 6,
|
||||||
},
|
},
|
||||||
btnActive: {
|
ctaText: {
|
||||||
backgroundColor: '#4ade80', // light green
|
|
||||||
},
|
|
||||||
btnPurchased: {
|
|
||||||
backgroundColor: colors.error,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: colors.error,
|
|
||||||
},
|
|
||||||
btnDisabled: {
|
|
||||||
backgroundColor: '#e2e8f0',
|
|
||||||
},
|
|
||||||
mainButtonText: {
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
},
|
letterSpacing: 0.8,
|
||||||
|
} as any,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,63 +1,66 @@
|
|||||||
import React, { memo, useEffect, useState } from 'react';
|
import React, { memo, useEffect, useState } from 'react';
|
||||||
import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Platform, StatusBar, Modal } from 'react-native';
|
import {
|
||||||
import { Briefcase, Calendar, FileText, Download, Eye, Layers, ArrowLeft, Clock } from 'lucide-react-native';
|
View,
|
||||||
import { useAuctionItem, useGetAuciton, useGetAucitonDocument } from '../../../Services/BBPS/Hooks';
|
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 { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { AUCTION_DOCUMENT_DOWNLOAD } from '../../../Services/BBPS/ApiUrls';
|
import { AUCTION_DOCUMENT_DOWNLOAD } from '../../../Services/ApiCall/ApiUrls';
|
||||||
import ReactNativeBlobUtil from 'react-native-blob-util';
|
import ReactNativeBlobUtil from 'react-native-blob-util';
|
||||||
import AuctionItemDetail from './AuctionItemDetail';
|
import AuctionItemDetail from './AuctionItemDetail';
|
||||||
import { Loader } from '../../../Component/Index';
|
import { useTheme } from '../../../Hooks/useTheme';
|
||||||
import colors from '../../../Constant/Color';
|
import AppText from '../../../components/AppText';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
interface Props { visible: boolean; onClose: () => void; data?: any }
|
interface Props { visible: boolean; onClose: () => void; data?: any }
|
||||||
|
|
||||||
const AuctionFullDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
const AuctionFullDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
||||||
const inset = useSafeAreaInsets()
|
const inset = useSafeAreaInsets();
|
||||||
const { data: auctionData, isLoading } = useGetAuciton({ auctionId: data?.auctionId })
|
const { colors, spacing, shadows } = useTheme();
|
||||||
const [aucitonDetail, setAuciotnDetail] = useState<any>(auctionData)
|
|
||||||
const [allBoolean, setAllBoolean] = useState({ showItemDetail: false, selectedItem: {}, fileLoading: false });
|
const { data: auctionData } = useGetAuciton({ auctionId: data?.auctionId });
|
||||||
const { data: auctionDocuments } = useGetAucitonDocument({ auctionId: data?.auctionId })
|
const [aucitonDetail, setAuciotnDetail] = useState<any>(auctionData);
|
||||||
const { mutate } = useAuctionItem()
|
const [allBoolean, setAllBoolean] = useState({
|
||||||
|
showItemDetail: false,
|
||||||
|
selectedItem: {},
|
||||||
|
fileLoading: false,
|
||||||
|
});
|
||||||
|
const { data: auctionDocuments } = useGetAucitonDocument({ auctionId: data?.auctionId });
|
||||||
|
const { mutate } = useAuctionItem();
|
||||||
|
|
||||||
const formatDate = (date?: string) => {
|
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');
|
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 isLive = data?.purchaseEnd
|
||||||
|
? moment().isBefore(moment(data.purchaseEnd, 'DD-MM-YYYY HH:mm:ss'))
|
||||||
const Section = ({ title, icon: Icon, children }: any) => (
|
: false;
|
||||||
<View style={styles.sectionContainer}>
|
|
||||||
<View style={styles.sectionHeader}>
|
|
||||||
<Icon size={20} color={colors.primary} />
|
|
||||||
<Text style={styles.sectionTitle}>{title}</Text>
|
|
||||||
</View>
|
|
||||||
{children}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
const InfoBox = ({ label, value, full = false }: any) => (
|
|
||||||
<View style={[styles.infoBox, full ? { width: '100%' } : { width: '48%' }]}>
|
|
||||||
<Text style={styles.label}>{label}</Text>
|
|
||||||
<Text style={styles.value}>{value || '-'}</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
const handleDownload = async (id: number, fileName: string) => {
|
const handleDownload = async (id: number, fileName: string) => {
|
||||||
try {
|
try {
|
||||||
setAllBoolean((prev: any) => ({ ...prev, fileLoading: true }));
|
setAllBoolean(prev => ({ ...prev, fileLoading: true }));
|
||||||
const { dirs } = ReactNativeBlobUtil.fs;
|
const { dirs } = ReactNativeBlobUtil.fs;
|
||||||
|
|
||||||
const path = `${dirs.DownloadDir}/${fileName}.pdf`;
|
const path = `${dirs.DownloadDir}/${fileName}.pdf`;
|
||||||
|
|
||||||
// console.log('Saving to:', path);
|
|
||||||
|
|
||||||
const downloadUrl = `${AUCTION_DOCUMENT_DOWNLOAD}?documentId=${id}&isAmendment=false`;
|
const downloadUrl = `${AUCTION_DOCUMENT_DOWNLOAD}?documentId=${id}&isAmendment=false`;
|
||||||
// console.log('Download URL:', downloadUrl);
|
await ReactNativeBlobUtil.config({
|
||||||
|
|
||||||
const response = await ReactNativeBlobUtil.config({
|
|
||||||
fileCache: false,
|
fileCache: false,
|
||||||
addAndroidDownloads: {
|
addAndroidDownloads: {
|
||||||
useDownloadManager: true,
|
useDownloadManager: true,
|
||||||
@@ -68,292 +71,340 @@ const AuctionFullDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
|||||||
description: 'Downloading document',
|
description: 'Downloading document',
|
||||||
mediaScannable: true,
|
mediaScannable: true,
|
||||||
},
|
},
|
||||||
}).fetch('GET', downloadUrl, {
|
}).fetch('GET', downloadUrl, { Accept: 'application/pdf' });
|
||||||
Accept: 'application/pdf',
|
|
||||||
});
|
|
||||||
|
|
||||||
const stat = await ReactNativeBlobUtil.fs.stat(path);
|
|
||||||
console.log('File size:', stat.size);
|
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log('Error:', error?.message || error);
|
console.log('Error:', error?.message || error);
|
||||||
} finally {
|
} finally {
|
||||||
setAllBoolean((prev: any) => ({ ...prev, fileLoading: false }));
|
setAllBoolean(prev => ({ ...prev, fileLoading: false }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mutate({ auctionNumber: data?.auctionNumber, status: data?.auctionPattern }, {
|
mutate(
|
||||||
onSuccess: (res) => {
|
{ auctionNumber: data?.auctionNumber, status: data?.auctionPattern },
|
||||||
setAuciotnDetail((prev: any) => ({ ...prev, auctionItems: res.data, auctionDocuments }))
|
{
|
||||||
}, onError: (err) => {
|
onSuccess: (res: any) => {
|
||||||
|
setAuciotnDetail((prev: any) => ({ ...prev, auctionItems: res.data, auctionDocuments }));
|
||||||
|
},
|
||||||
|
onError: (err: any) => {
|
||||||
console.log(err, '-----auction item error');
|
console.log(err, '-----auction item error');
|
||||||
}
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}, [data.auctionNumber, auctionDocuments]);
|
}, [data.auctionNumber, auctionDocuments]);
|
||||||
|
|
||||||
|
const isAmended = auctionData?.auctionStatus === 'Amended';
|
||||||
|
const kd = (key: any, amended: any) => isAmended ? amended : key;
|
||||||
|
|
||||||
|
const styles = makeStyles(colors, spacing, shadows);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
visible={visible}
|
||||||
transparent={false}
|
transparent={false}
|
||||||
animationType="none"
|
animationType="slide"
|
||||||
statusBarTranslucent
|
statusBarTranslucent
|
||||||
onRequestClose={onClose}
|
onRequestClose={onClose}
|
||||||
>
|
>
|
||||||
<View style={styles.safeArea}>
|
<View style={styles.root}>
|
||||||
<StatusBar barStyle="dark-content" />
|
{/* ── Header ── */}
|
||||||
|
<View style={[styles.header, { paddingTop: inset.top + 6 }]}>
|
||||||
{/* Full Header */}
|
<TouchableOpacity onPress={onClose} style={styles.backBtn} activeOpacity={0.7}>
|
||||||
<View style={[styles.header, { paddingTop: inset.top }]}>
|
<ArrowLeft size={22} color={colors.text} strokeWidth={2.5} />
|
||||||
<TouchableOpacity onPress={onClose} style={styles.backBtn}>
|
|
||||||
<ArrowLeft size={24} color="#1e293b" />
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<View style={styles.headerTitleContainer}>
|
|
||||||
<Text style={styles.headerTitleText}>Auction Overview</Text>
|
<View style={styles.headerCenter}>
|
||||||
<Text style={styles.headerIdText}>Ref: {data?.auctionNumber || 'N/A'}</Text>
|
<AppText variant="h4" color={colors.text} style={styles.headerTitle as any}>
|
||||||
|
Auction Overview
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>
|
||||||
|
Ref: {data?.auctionNumber ?? 'N/A'}
|
||||||
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.liveIndicator}>
|
|
||||||
<View style={[styles.dot, { backgroundColor: isLive ? '#22c55e' : '#94a3b8' }]} />
|
<View style={[styles.statusChip, { borderColor: isLive ? `${colors.success}50` : colors.border }]}>
|
||||||
<Text style={styles.liveText}>{isLive ? 'LIVE' : 'CLOSED'}</Text>
|
<View style={[styles.statusDot, { backgroundColor: isLive ? colors.success : colors.textSecondary }]} />
|
||||||
|
<AppText variant="caption" color={isLive ? colors.success : colors.textSecondary} style={styles.statusText as any}>
|
||||||
|
{isLive ? 'LIVE' : 'CLOSED'}
|
||||||
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Scrollable Body */}
|
{/* ── Body ── */}
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
showsVerticalScrollIndicator={false}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
showsVerticalScrollIndicator={true}
|
style={{ backgroundColor: colors.background }}
|
||||||
>
|
>
|
||||||
{/* 1. Basic Info Grid */}
|
|
||||||
<Section title="Details" icon={Layers}>
|
|
||||||
<View style={styles.grid}>
|
|
||||||
<InfoBox label="Auction No." value={data?.auctionNumber} />
|
|
||||||
<InfoBox label="Pattern" value={data?.auctionPattern || 'Forward'} />
|
|
||||||
<InfoBox label="Mode" value={data?.auctionMode || 'Open'} />
|
|
||||||
<InfoBox label="Category" value={data?.categoryOfAuction || 'General'} />
|
|
||||||
<InfoBox label="EMD Type" value={data?.emd || 'Online'} />
|
|
||||||
<InfoBox label="EMD Figure" value={data?.emdFigure || 'Item Wise'} />
|
|
||||||
</View>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
{/* 2. Work Description */}
|
{/* BASIC DETAILS */}
|
||||||
<Section title="Auction Details" icon={Briefcase}>
|
<SectionCard title="Basic Details" icon={Layers} colors={colors} spacing={spacing} shadows={shadows}>
|
||||||
<View style={styles.grid}>
|
<InfoRow label="Auction No." value={data?.auctionNumber} colors={colors} />
|
||||||
<InfoBox label="Auction Name" value={auctionData?.auctionOfWork} />
|
<InfoRow label="Pattern" value={data?.auctionPattern || 'Forward'} colors={colors} />
|
||||||
<InfoBox label="Category Of Auction " value={auctionData?.auctionCategories} />
|
<InfoRow label="Mode" value={data?.auctionMode || 'Open'} colors={colors} />
|
||||||
<InfoBox label="Auction Fees (In Figure) " value={auctionData?.auctionFees} />
|
<InfoRow label="Category" value={data?.categoryOfAuction || 'General'} colors={colors} />
|
||||||
<InfoBox label="Auction Fees (In Words)" value={auctionData?.auctionFeesInWord} />
|
<InfoRow label="EMD Type" value={data?.emd || 'Online'} colors={colors} last />
|
||||||
<InfoBox label="Auction Keyword" value={auctionData?.auctionKeywordName} />
|
</SectionCard>
|
||||||
</View>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
{/* 3. Timeline Section */}
|
{/* AUCTION DETAILS */}
|
||||||
<Section title="Key Dates" icon={Calendar}>
|
<SectionCard title="Auction Details" icon={Briefcase} colors={colors} spacing={spacing} shadows={shadows}>
|
||||||
<View style={styles.timelineContainer}>
|
<InfoRow label="Name" value={auctionData?.auctionOfWork} colors={colors} multiline />
|
||||||
<View style={styles.timelineRow}>
|
<InfoRow label="Category" value={auctionData?.auctionCategories} colors={colors} />
|
||||||
<View style={styles.iconCircle}><Clock size={16} color="#64748b" /></View>
|
<InfoRow label="Fees" value={auctionData?.auctionFees ? `₹ ${auctionData.auctionFees}` : '—'} colors={colors} valueColor={colors.primary} />
|
||||||
<View>
|
<InfoRow label="Fees (Words)" value={auctionData?.auctionFeesInWord} colors={colors} multiline />
|
||||||
<Text style={styles.label}>Auction Purchase (Start)</Text>
|
<InfoRow label="Keyword" value={auctionData?.auctionKeywordName} colors={colors} last />
|
||||||
<Text style={styles.value}>{formatDate(data?.auctionDispatchDate)}</Text>
|
</SectionCard>
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={styles.timelineConnector} />
|
|
||||||
<View style={styles.timelineRow}>
|
|
||||||
<View style={[styles.iconCircle, { backgroundColor: '#fee2e2' }]}><Clock size={16} color="#ef4444" /></View>
|
|
||||||
<View>
|
|
||||||
<Text style={styles.label}>Purchase Deadline (End)</Text>
|
|
||||||
<Text style={styles.value}>{formatDate(data?.purchaseEnd)}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
{/* 4. Item Table Section */}
|
{/* KEY DATES */}
|
||||||
<Section title="Auction Items" icon={FileText}>
|
<SectionCard title="Key Dates" icon={Calendar} colors={colors} spacing={spacing} shadows={shadows}>
|
||||||
<View style={styles.tableWrapper}>
|
<InfoRow label="Purchase Start" value={formatDate(kd(auctionData?.keyDates?.auctionPurchase?.startDateTime, auctionData?.amendedkeyDates?.auctionPurchase?.startDateTime))} colors={colors} />
|
||||||
<View style={styles.tableHead}>
|
<InfoRow label="Purchase End" value={formatDate(kd(auctionData?.keyDates?.auctionPurchase?.endDateTime, auctionData?.amendedkeyDates?.auctionPurchase?.endDateTime))} colors={colors} valueColor={colors.error} />
|
||||||
<Text style={[styles.tableLabel, { flex: 2 }]}>Item</Text>
|
<InfoRow label="Bidding Start" value={formatDate(kd(auctionData?.keyDates?.auctionBidding?.startDateTime, auctionData?.amendedkeyDates?.auctionBidding?.startDateTime))} colors={colors} />
|
||||||
<Text style={styles.tableLabel}>Time</Text>
|
<InfoRow label="Bidding End" value={formatDate(kd(auctionData?.keyDates?.auctionBidding?.endDateTime, auctionData?.amendedkeyDates?.auctionBidding?.endDateTime))} colors={colors} valueColor={colors.error} last />
|
||||||
<Text style={[styles.tableLabel, { textAlign: 'right' }]}>View</Text>
|
</SectionCard>
|
||||||
|
|
||||||
|
{/* AUCTION ITEMS */}
|
||||||
|
{aucitonDetail?.auctionItems?.length > 0 && (
|
||||||
|
<SectionCard title="Auction Items" icon={FileText} colors={colors} spacing={spacing} shadows={shadows}>
|
||||||
|
{aucitonDetail.auctionItems.map((item: any, index: number) => (
|
||||||
|
<View
|
||||||
|
key={index}
|
||||||
|
style={[
|
||||||
|
styles.itemRow,
|
||||||
|
{ borderBottomColor: colors.border },
|
||||||
|
index === aucitonDetail.auctionItems.length - 1 && { borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View style={styles.itemLeft}>
|
||||||
|
<AppText variant="bodySmall" color={colors.text} style={styles.itemName as any}>
|
||||||
|
{item.productName}
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>
|
||||||
|
{item?.categoryName} · {item.timeDurationType}
|
||||||
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
{/* Dummy Item Row */}
|
<TouchableOpacity
|
||||||
{aucitonDetail?.auctionItems?.map((item: any, index: number) => (
|
style={[styles.eyeBtn, { backgroundColor: `${colors.primary}15` }]}
|
||||||
<View style={styles.tableRow}>
|
onPress={() => setAllBoolean(prev => ({ ...prev, showItemDetail: true, selectedItem: item }))}
|
||||||
<View style={{ flex: 2 }}>
|
>
|
||||||
<Text style={styles.itemMain}>{item.productName}</Text>
|
<Eye size={16} color={colors.primary} strokeWidth={2} />
|
||||||
<Text style={styles.itemSub}>Category: {item?.categoryName}</Text>
|
|
||||||
</View>
|
|
||||||
<Text style={styles.itemQty}>{item.timeDurationType}</Text>
|
|
||||||
<TouchableOpacity style={styles.actionIcon} onPress={() => { setAllBoolean((prev: any) => ({ ...prev, showItemDetail: true, selectedItem: item })) }}>
|
|
||||||
<Eye size={18} color={colors.primary} />
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</SectionCard>
|
||||||
</Section>
|
|
||||||
|
|
||||||
{/* 5. Downloads */}
|
|
||||||
<Section title="Downloads" icon={Download}>
|
|
||||||
{aucitonDetail?.auctionDocuments?.map((doc: any, index: number) => (
|
|
||||||
<TouchableOpacity style={styles.fileCard} onPress={() => {
|
|
||||||
handleDownload(
|
|
||||||
doc.auctionDocumentId,
|
|
||||||
doc.fileName.replace('.pdf', '')
|
|
||||||
)
|
|
||||||
}}>
|
|
||||||
<View style={styles.fileIcon}><FileText size={20} color="#ef4444" /></View>
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<Text style={styles.fileName}>{doc.fileName}</Text>
|
|
||||||
</View>
|
|
||||||
{allBoolean.fileLoading ? (
|
|
||||||
<View >
|
|
||||||
<Loader size='small' color='#94a3b8' />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<Download size={20} color="#94a3b8" />
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* DOCUMENTS */}
|
||||||
|
{aucitonDetail?.auctionDocuments?.length > 0 && (
|
||||||
|
<SectionCard title="Documents" icon={Download} colors={colors} spacing={spacing} shadows={shadows}>
|
||||||
|
{aucitonDetail.auctionDocuments.map((doc: any, index: number) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={index}
|
||||||
|
style={[
|
||||||
|
styles.docRow,
|
||||||
|
{ borderBottomColor: colors.border },
|
||||||
|
index === aucitonDetail.auctionDocuments.length - 1 && { borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
onPress={() => handleDownload(doc.auctionDocumentId, doc.fileName.replace('.pdf', ''))}
|
||||||
|
>
|
||||||
|
<View style={[styles.docIcon, { backgroundColor: `${colors.error}15` }]}>
|
||||||
|
<FileText size={16} color={colors.error} strokeWidth={2} />
|
||||||
|
</View>
|
||||||
|
<AppText variant="bodySmall" color={colors.text} style={styles.docName as any} numberOfLines={1}>
|
||||||
|
{doc.fileName}
|
||||||
|
</AppText>
|
||||||
|
{allBoolean.fileLoading
|
||||||
|
? <ActivityIndicator size="small" color={colors.primary} />
|
||||||
|
: <Download size={18} color={colors.textSecondary} strokeWidth={2} />
|
||||||
|
}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
</Section>
|
</SectionCard>
|
||||||
|
)}
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
{allBoolean.showItemDetail && <AuctionItemDetail data={allBoolean.selectedItem} onClose={() => { setAllBoolean((prev: any) => ({ ...prev, showItemDetail: false })) }} visible={allBoolean.showItemDetail} />}
|
{allBoolean.showItemDetail && (
|
||||||
|
<AuctionItemDetail
|
||||||
|
data={allBoolean.selectedItem}
|
||||||
|
onClose={() => setAllBoolean(prev => ({ ...prev, showItemDetail: false }))}
|
||||||
|
visible={allBoolean.showItemDetail}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
export default memo(AuctionFullDetail);
|
||||||
fullModal: {
|
|
||||||
margin: 0,
|
/* ─── Sub-components ─── */
|
||||||
backgroundColor: '#fff',
|
|
||||||
|
const SectionCard = ({ title, icon: Icon, children, colors, spacing, shadows }: any) => {
|
||||||
|
const styles = makeStyles(colors, spacing, shadows);
|
||||||
|
return (
|
||||||
|
<View style={styles.card}>
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<Icon size={18} color={colors.primary} strokeWidth={2} />
|
||||||
|
<AppText variant="caption" color={colors.text} style={styles.cardTitle as any}>
|
||||||
|
{title.toUpperCase()}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.cardDivider, { backgroundColor: colors.border }]} />
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const InfoRow = ({ label, value, colors, last = false, multiline = false, valueColor }: any) => (
|
||||||
|
<View style={[
|
||||||
|
{
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: multiline ? 'flex-start' : 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingVertical: 13,
|
||||||
|
borderBottomWidth: last ? 0 : StyleSheet.hairlineWidth,
|
||||||
|
borderBottomColor: 'rgba(0,0,0,0.06)',
|
||||||
|
gap: 12,
|
||||||
},
|
},
|
||||||
safeArea: {
|
]}>
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={{ flex: 1 } as any}>
|
||||||
|
{label}
|
||||||
|
</AppText>
|
||||||
|
<AppText
|
||||||
|
variant="bodySmall"
|
||||||
|
color={valueColor || colors.text}
|
||||||
|
style={{ flex: 1, textAlign: 'right', fontWeight: '600' } as any}
|
||||||
|
numberOfLines={multiline ? 4 : 1}
|
||||||
|
>
|
||||||
|
{value || '—'}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
/* ─── Styles factory ─── */
|
||||||
|
const makeStyles = (colors: any, spacing: any, shadows: any) =>
|
||||||
|
StyleSheet.create({
|
||||||
|
root: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: colors.background,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* Header */
|
||||||
header: {
|
header: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: 16,
|
paddingHorizontal: spacing.m,
|
||||||
borderBottomWidth: 1,
|
paddingBottom: spacing.m,
|
||||||
borderBottomColor: '#f1f5f9',
|
backgroundColor: colors.surface,
|
||||||
backgroundColor: '#fff',
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderBottomColor: colors.border,
|
||||||
|
gap: spacing.s,
|
||||||
},
|
},
|
||||||
backBtn: {
|
backBtn: {
|
||||||
padding: 8,
|
width: 36,
|
||||||
marginRight: 8,
|
height: 36,
|
||||||
},
|
borderRadius: 10,
|
||||||
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' },
|
|
||||||
|
|
||||||
scrollView: { flex: 1, backgroundColor: '#f8fafc' },
|
|
||||||
scrollContent: { padding: 16, paddingBottom: 30 },
|
|
||||||
|
|
||||||
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 },
|
|
||||||
|
|
||||||
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 },
|
|
||||||
|
|
||||||
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 },
|
|
||||||
|
|
||||||
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' },
|
|
||||||
|
|
||||||
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',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
shadowColor: colors.primary,
|
|
||||||
shadowOffset: { width: 0, height: 4 },
|
|
||||||
shadowOpacity: 0.2,
|
|
||||||
shadowRadius: 8,
|
|
||||||
elevation: 4,
|
|
||||||
},
|
},
|
||||||
btnDisabled: { backgroundColor: '#cbd5e1' },
|
headerCenter: {
|
||||||
mainBtnText: { color: '#fff', fontSize: 16, fontWeight: '800' },
|
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,
|
||||||
|
|
||||||
export default memo(AuctionFullDetail);
|
scrollContent: {
|
||||||
|
padding: spacing.m,
|
||||||
|
paddingBottom: 50,
|
||||||
|
gap: spacing.m,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* 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,
|
||||||
|
});
|
||||||
|
|||||||
@@ -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 { View, Text, StyleSheet, TouchableOpacity, ScrollView, Platform, Modal } from 'react-native';
|
||||||
import { Package, IndianRupee, FileText, Download, ArrowLeft, Info, } from 'lucide-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 { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { useAucitonItemDetail } from '../../../Services/BBPS/Hooks';
|
import { useAucitonItemDetail } from '../../../Services/ApiCall/Hooks';
|
||||||
import ReactNativeBlobUtil from 'react-native-blob-util';
|
import ReactNativeBlobUtil from 'react-native-blob-util';
|
||||||
import { AUCTION_ITEM_DOCUMENT_DOWNLOAD } from '../../../Services/BBPS/ApiUrls';
|
import { AUCTION_ITEM_DOCUMENT_DOWNLOAD } from '../../../Services/ApiCall/ApiUrls';
|
||||||
import { Loader } from '../../../Component/Index';
|
import { Loader } from '../../../Component/Index';
|
||||||
|
import { useTheme } from '../../../Hooks/useTheme';
|
||||||
|
import AppText from '../../../components/AppText';
|
||||||
|
|
||||||
interface Props { visible: boolean; onClose: () => void; data?: any; }
|
interface Props { visible: boolean; onClose: () => void; data?: any; }
|
||||||
|
|
||||||
const Section = ({ title, icon: Icon, children }: any) => (
|
const Section = ({ title, icon: Icon, children, styles, colors }: any) => (
|
||||||
<View style={styles.sectionCard}>
|
<View style={styles.sectionCard}>
|
||||||
<View style={styles.sectionHeader}>
|
<View style={styles.sectionHeader}>
|
||||||
<View style={styles.iconCircle}>
|
<View style={styles.iconCircle}>
|
||||||
<Icon size={18} color={colors.primary} strokeWidth={2.5} />
|
<Icon size={18} color={colors.primary} strokeWidth={2.5} />
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.sectionTitle}>{title}</Text>
|
<AppText variant="caption" bold style={styles.sectionTitle}>{title}</AppText>
|
||||||
</View>
|
</View>
|
||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
const PriceTile = ({ label, fig, words, full = false }: any) => (
|
const PriceTile = ({ label, fig, words, full = false, styles }: any) => (
|
||||||
<View style={[styles.priceTile, full ? { width: '100%' } : { width: '48%' }]}>
|
<View style={[styles.priceTile, full ? { width: '100%' } : { width: '48%' }]}>
|
||||||
<Text style={styles.label}>{label}</Text>
|
<AppText variant="caption" color={styles.label.color} bold style={styles.label}>{label}</AppText>
|
||||||
<Text style={styles.priceFig}>₹{Number(fig).toLocaleString('en-IN')}</Text>
|
<AppText variant="h3" bold style={styles.priceFig}>₹{Number(fig).toLocaleString('en-IN')}</AppText>
|
||||||
<Text style={styles.priceWords}>{words}</Text>
|
<AppText variant="caption" style={styles.priceWords}>{words}</AppText>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
||||||
const inset = useSafeAreaInsets()
|
const inset = useSafeAreaInsets()
|
||||||
|
const { colors, typography } = useTheme();
|
||||||
|
const styles = useMemo(() => createStyles(colors, typography), [colors, typography]);
|
||||||
|
|
||||||
const [allBoolean, setAllBoolean] = useState({ fileLoading: false });
|
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) => {
|
const handleDownload = async (id: number, fileName: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -41,10 +46,7 @@ const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
|||||||
|
|
||||||
const path = `${dirs.DownloadDir}/${fileName}.pdf`;
|
const path = `${dirs.DownloadDir}/${fileName}.pdf`;
|
||||||
|
|
||||||
// console.log('Saving to:', path);
|
|
||||||
|
|
||||||
const downloadUrl = `${AUCTION_ITEM_DOCUMENT_DOWNLOAD}?auctionItemId=${id}`;
|
const downloadUrl = `${AUCTION_ITEM_DOCUMENT_DOWNLOAD}?auctionItemId=${id}`;
|
||||||
// console.log('Download URL:', downloadUrl);
|
|
||||||
|
|
||||||
const response = await ReactNativeBlobUtil.config({
|
const response = await ReactNativeBlobUtil.config({
|
||||||
fileCache: false,
|
fileCache: false,
|
||||||
@@ -62,7 +64,6 @@ const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const stat = await ReactNativeBlobUtil.fs.stat(path);
|
const stat = await ReactNativeBlobUtil.fs.stat(path);
|
||||||
console.log(' File size:', stat.size);
|
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log('Error:', error?.message || error);
|
console.log('Error:', error?.message || error);
|
||||||
@@ -84,11 +85,11 @@ const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
|||||||
{/* Navbar */}
|
{/* Navbar */}
|
||||||
<View style={[styles.header, { paddingTop: inset.top }]}>
|
<View style={[styles.header, { paddingTop: inset.top }]}>
|
||||||
<TouchableOpacity onPress={onClose} style={styles.backBtn}>
|
<TouchableOpacity onPress={onClose} style={styles.backBtn}>
|
||||||
<ArrowLeft size={24} color="#1e293b" />
|
<ArrowLeft size={24} color={colors.text} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<View>
|
<View>
|
||||||
<Text style={styles.headerTitle}>Item Information</Text>
|
<AppText variant="body" bold style={styles.headerTitle}>Item Information</AppText>
|
||||||
<Text style={styles.headerSub}>Detail View</Text>
|
<AppText variant="caption" style={styles.headerSub}>Detail View</AppText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -97,69 +98,66 @@ const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
|||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
>
|
>
|
||||||
<Section title="Product Specification" icon={Package}>
|
<Section title="Product Specification" icon={Package} styles={styles} colors={colors}>
|
||||||
<View style={styles.identityHeader}>
|
<View style={styles.identityHeader}>
|
||||||
<Text style={styles.productNameText}>{data.productName.toUpperCase()}</Text>
|
<AppText variant="h4" bold style={styles.productNameText}>{data?.productName?.toUpperCase()}</AppText>
|
||||||
<View style={styles.categoryBadge}>
|
<View style={styles.categoryBadge}>
|
||||||
<Text style={styles.categoryText}>{data.categoryName}</Text>
|
<AppText variant="caption" bold style={styles.categoryText}>{data?.categoryName}</AppText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.specGrid}>
|
<View style={styles.specGrid}>
|
||||||
<View style={styles.specItem}>
|
<View style={styles.specItem}>
|
||||||
<Text style={styles.label}>Sub Category</Text>
|
<AppText variant="caption" bold style={styles.label}>Sub Category</AppText>
|
||||||
<Text style={styles.specValue}>{data.subCategoryName}</Text>
|
<AppText variant="bodySmall" bold style={styles.specValue}>{data?.subCategoryName}</AppText>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.specItem}>
|
<View style={styles.specItem}>
|
||||||
<Text style={styles.label}>Duration</Text>
|
<AppText variant="caption" bold style={styles.label}>Duration</AppText>
|
||||||
<Text style={styles.specValue}>{data.timeDurationType}</Text>
|
<AppText variant="bodySmall" bold style={styles.specValue}>{data?.timeDurationType}</AppText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Reserve & Bid Values" icon={IndianRupee}>
|
<Section title="Reserve & Bid Values" icon={IndianRupee} styles={styles} colors={colors}>
|
||||||
<PriceTile
|
<PriceTile
|
||||||
label="Bidding Price"
|
label="Bidding Price"
|
||||||
fig={data.reservePrice || data.emdAmount}
|
fig={data?.reservePrice || data?.emdAmount}
|
||||||
words={data.reservePriceInWords || data.emdAmountInWords}
|
words={data?.reservePriceInWords || data?.emdAmountInWords}
|
||||||
full
|
full
|
||||||
|
styles={styles}
|
||||||
/>
|
/>
|
||||||
<View style={styles.hDivider} />
|
<View style={styles.hDivider} />
|
||||||
<PriceTile
|
<PriceTile
|
||||||
label="Auction Start Value"
|
label="Auction Start Value"
|
||||||
fig={data.reservePrice || data.auctionStartValueFigure}
|
fig={data?.reservePrice || data?.auctionStartValueFigure}
|
||||||
words={data.reservePriceInWords || data.auctionStartValueWords}
|
words={data?.reservePriceInWords || data?.auctionStartValueWords}
|
||||||
full
|
full
|
||||||
|
styles={styles}
|
||||||
/>
|
/>
|
||||||
<View style={styles.hDivider} />
|
<View style={styles.hDivider} />
|
||||||
{/* <View style={styles.row}>
|
|
||||||
<PriceTile label="Bid Variation" fig={data.bidVariationFig} words={data.bidVariationWords} />
|
|
||||||
<PriceTile label="Auction Start Value" fig={data.auctionStartValue} words={data.auctionStartValueInWords
|
|
||||||
} />
|
|
||||||
</View> */}
|
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Brief Description" icon={Info}>
|
<Section title="Brief Description" icon={Info} styles={styles} colors={colors}>
|
||||||
<Text style={styles.descContent}>{data.briefDescription}</Text>
|
<AppText variant="bodySmall" style={styles.descContent}>{data?.briefDescription}</AppText>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{itemData?.length && <Section title="View Documents" icon={FileText}>
|
{itemData?.length && <Section title="View Documents" icon={FileText} styles={styles} colors={colors}>
|
||||||
<View style={styles.docTableHead}>
|
<View style={styles.docTableHead}>
|
||||||
<Text style={styles.tableLabel}>Sr No.</Text>
|
<AppText variant="caption" bold style={styles.tableLabel}>Sr No.</AppText>
|
||||||
<Text style={[styles.tableLabel, { flex: 2 }]}>Document Name</Text>
|
<AppText variant="caption" bold style={[styles.tableLabel, { flex: 2 }]}>Document Name</AppText>
|
||||||
<Text style={styles.tableLabel}>Action</Text>
|
<AppText variant="caption" bold style={styles.tableLabel}>Action</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{itemData?.map((doc: any, index: any) => (
|
{itemData?.map((doc: any, index: any) => (
|
||||||
<TouchableOpacity style={styles.docRow} activeOpacity={0.7} onPress={() => { handleDownload(doc.assetId, doc.fileName) }} key={doc.assetId}>
|
<TouchableOpacity style={styles.docRow} activeOpacity={0.7} onPress={() => { handleDownload(doc.assetId, doc.fileName) }} key={doc.assetId}>
|
||||||
<Text style={styles.docIndex}>{index + 1}</Text>
|
<AppText variant="bodySmall" bold style={styles.docIndex}>{index + 1}</AppText>
|
||||||
<View style={{ flex: 2 }}>
|
<View style={{ flex: 2 }}>
|
||||||
<Text style={styles.docTitle} numberOfLines={1}>{doc.fileName}</Text>
|
<AppText variant="bodySmall" bold style={styles.docTitle} numberOfLines={1}>{doc.fileName}</AppText>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.downloadIconBox}>
|
<View style={styles.downloadIconBox}>
|
||||||
{allBoolean.fileLoading ? (
|
{allBoolean.fileLoading ? (
|
||||||
<View >
|
<View >
|
||||||
<Loader size='small' color='#94a3b8' />
|
<Loader size='small' color={colors.textSecondary} />
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<Download size={20} color={colors.primary} />
|
<Download size={20} color={colors.primary} />
|
||||||
@@ -174,26 +172,26 @@ const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const createStyles = (colors: any, typography: any) => StyleSheet.create({
|
||||||
fullModal: { margin: 0, backgroundColor: '#f8fafc' },
|
fullModal: { margin: 0, backgroundColor: colors.background },
|
||||||
safeArea: { flex: 1 },
|
safeArea: { flex: 1, backgroundColor: colors.background },
|
||||||
header: {
|
header: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: 16,
|
padding: 16,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: colors.surface,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: '#f1f5f9',
|
borderBottomColor: colors.border,
|
||||||
},
|
},
|
||||||
backBtn: { padding: 8, marginRight: 8 },
|
backBtn: { padding: 8, marginRight: 8 },
|
||||||
headerTitle: { fontSize: 18, fontWeight: '800', color: '#0f172a' },
|
headerTitle: { fontSize: 18, color: colors.text },
|
||||||
headerSub: { fontSize: 12, color: '#94a3b8', fontWeight: '600', textTransform: 'uppercase' },
|
headerSub: { fontSize: 12, color: colors.textSecondary, textTransform: 'uppercase' },
|
||||||
|
|
||||||
scrollView: { flex: 1 },
|
scrollView: { flex: 1 },
|
||||||
scrollContent: { padding: 16, paddingBottom: 50 },
|
scrollContent: { padding: 16, paddingBottom: 50 },
|
||||||
|
|
||||||
sectionCard: {
|
sectionCard: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: colors.surface,
|
||||||
borderRadius: 24,
|
borderRadius: 24,
|
||||||
padding: 20,
|
padding: 20,
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
@@ -218,55 +216,54 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '800',
|
color: colors.text,
|
||||||
color: '#334155',
|
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Product Identity
|
// Product Identity
|
||||||
identityHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 },
|
identityHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 },
|
||||||
productNameText: { fontSize: 20, fontWeight: '900', color: '#1e293b', flex: 1 },
|
productNameText: { fontSize: 20, color: colors.text, flex: 1 },
|
||||||
categoryBadge: { backgroundColor: '#f1f5f9', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20 },
|
categoryBadge: { backgroundColor: colors.border, paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20 },
|
||||||
categoryText: { fontSize: 11, fontWeight: '800', color: '#64748b', textTransform: 'uppercase' },
|
categoryText: { fontSize: 11, color: colors.textSecondary, textTransform: 'uppercase' },
|
||||||
specGrid: { flexDirection: 'row', gap: 24, marginTop: 5 },
|
specGrid: { flexDirection: 'row', gap: 24, marginTop: 5 },
|
||||||
specItem: { flex: 1 },
|
specItem: { flex: 1 },
|
||||||
specValue: { fontSize: 15, fontWeight: '700', color: '#334155', marginTop: 2 },
|
specValue: { fontSize: 15, color: colors.text, marginTop: 2 },
|
||||||
|
|
||||||
// Pricing
|
// Pricing
|
||||||
priceTile: { marginBottom: 16 },
|
priceTile: { marginBottom: 16 },
|
||||||
label: { fontSize: 11, color: '#94a3b8', fontWeight: '700', textTransform: 'uppercase', marginBottom: 4 },
|
label: { fontSize: 11, color: colors.textSecondary, textTransform: 'uppercase', marginBottom: 4 },
|
||||||
priceFig: { fontSize: 22, fontWeight: '900', color: colors.black },
|
priceFig: { fontSize: 22, color: colors.text },
|
||||||
priceWords: { fontSize: 13, color: '#64748b', fontStyle: 'italic', marginTop: 2 },
|
priceWords: { fontSize: 13, color: colors.textSecondary, fontStyle: 'italic', marginTop: 2 },
|
||||||
hDivider: { height: 1, backgroundColor: '#f1f5f9', marginBottom: 16 },
|
hDivider: { height: 1, backgroundColor: colors.border, marginBottom: 16 },
|
||||||
row: { flexDirection: 'row', justifyContent: 'space-between' },
|
row: { flexDirection: 'row', justifyContent: 'space-between' },
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
descContent: { fontSize: 15, color: '#475569', lineHeight: 22, fontWeight: '500' },
|
descContent: { fontSize: 15, color: colors.textSecondary, lineHeight: 22, fontFamily: typography.fonts.medium },
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
docTableHead: { flexDirection: 'row', paddingBottom: 10, borderBottomWidth: 1, borderBottomColor: '#f1f5f9', marginBottom: 10 },
|
docTableHead: { flexDirection: 'row', paddingBottom: 10, borderBottomWidth: 1, borderBottomColor: colors.border, marginBottom: 10 },
|
||||||
tableLabel: { fontSize: 11, fontWeight: '800', color: '#94a3b8', flex: 1 },
|
tableLabel: { fontSize: 11, color: colors.textSecondary, flex: 1 },
|
||||||
docRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#f8fafc', padding: 12, borderRadius: 12 },
|
docRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.background, padding: 12, borderRadius: 12 },
|
||||||
docIndex: { flex: 1, fontSize: 14, fontWeight: '700', color: '#64748b' },
|
docIndex: { flex: 1, fontSize: 14, color: colors.textSecondary },
|
||||||
docTitle: { fontSize: 14, fontWeight: '700', color: '#1e293b' },
|
docTitle: { fontSize: 14, color: colors.text },
|
||||||
downloadIconBox: { backgroundColor: '#fff', padding: 8, borderRadius: 10, borderWidth: 1, borderColor: '#e2e8f0' },
|
downloadIconBox: { backgroundColor: colors.surface, padding: 8, borderRadius: 10, borderWidth: 1, borderColor: colors.border },
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
footer: {
|
footer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 0, width: '100%',
|
bottom: 0, width: '100%',
|
||||||
backgroundColor: '#fff',
|
backgroundColor: colors.surface,
|
||||||
padding: 20,
|
padding: 20,
|
||||||
paddingBottom: Platform.OS === 'ios' ? 34 : 20,
|
paddingBottom: Platform.OS === 'ios' ? 34 : 20,
|
||||||
borderTopWidth: 1, borderTopColor: '#f1f5f9'
|
borderTopWidth: 1, borderTopColor: colors.border
|
||||||
},
|
},
|
||||||
primaryBtn: {
|
primaryBtn: {
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
height: 56, borderRadius: 18,
|
height: 56, borderRadius: 18,
|
||||||
flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10
|
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);
|
export default memo(AuctionItemDetail);
|
||||||
@@ -25,10 +25,8 @@ const FilterModal: React.FC<Props> = ({ visible, onClose, setFilter, filter }) =
|
|||||||
|
|
||||||
if (pickerType === 'FROM') {
|
if (pickerType === 'FROM') {
|
||||||
setFromDate(date);
|
setFromDate(date);
|
||||||
console.log('FROM date set');
|
|
||||||
} else {
|
} else {
|
||||||
setToDate(date);
|
setToDate(date);
|
||||||
console.log('TO date set');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setOpenPicker(false);
|
setOpenPicker(false);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { View, Text, StyleSheet, FlatList, RefreshControl, ActivityIndicator } f
|
|||||||
import LiveAuctionTile from './LiveAuctionTile';
|
import LiveAuctionTile from './LiveAuctionTile';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { useFetchLiveAuctionPage, useFetchPurchaseAuction } from '../../Services/BBPS/Hooks';
|
import { useFetchLiveAuctionPage, useFetchPurchaseAuction } from '../../Services/ApiCall/Hooks';
|
||||||
import PurchaseAuctionTile from './PurchaseAuctionTile';
|
import PurchaseAuctionTile from './PurchaseAuctionTile';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { setAuction } from '../../Redux/Slices/SelectedAuction';
|
import { setAuction } from '../../Redux/Slices/SelectedAuction';
|
||||||
@@ -36,7 +36,7 @@ const PurchaseAuction = () => {
|
|||||||
|
|
||||||
function isAuctionEnd(item: any): boolean {
|
function isAuctionEnd(item: any): boolean {
|
||||||
const now = moment();
|
const now = moment();
|
||||||
const end = moment(item?.keyDates?.auctionBidding?.endDateTime, format);
|
const end = moment(item.auctionStatus === "Amended" ? item?.amendedkeyDates?.auctionBidding?.endDateTime : item?.keyDates?.auctionBidding?.endDateTime, format);
|
||||||
|
|
||||||
if (!end.isValid()) return false;
|
if (!end.isValid()) return false;
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ const PurchaseAuction = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<SearchInput onChange={(val) => { setFilter(prev => ({ ...prev, auctionNumber: val })) }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} />
|
<SearchInput onChange={(val) => { setFilter(prev => ({ ...prev, auctionNumber: val })) }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} filter={false} />
|
||||||
{isPending ? <Skelton />
|
{isPending ? <Skelton />
|
||||||
:
|
:
|
||||||
<FlatList
|
<FlatList
|
||||||
|
|||||||
@@ -1,51 +1,58 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { View, Text, FlatList, StyleSheet, TouchableOpacity, Alert } from 'react-native';
|
import {
|
||||||
import { InsideHeader, Loader } from '../../Component/Index';
|
View,
|
||||||
import colors from '../../Constant/Color';
|
FlatList,
|
||||||
import { CreditCard, Calendar, User, DollarSign, ArrowRight } from 'lucide-react-native';
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
ActivityIndicator,
|
||||||
|
} from 'react-native';
|
||||||
|
import Animated, { FadeInDown, FadeIn } from 'react-native-reanimated';
|
||||||
|
import {
|
||||||
|
Calendar,
|
||||||
|
Hash,
|
||||||
|
Wifi,
|
||||||
|
WifiOff,
|
||||||
|
Receipt,
|
||||||
|
CheckCircle2,
|
||||||
|
Gavel,
|
||||||
|
Building2,
|
||||||
|
BadgeIndianRupee,
|
||||||
|
} from 'lucide-react-native';
|
||||||
|
import { InsideHeader } from '../../Component/Index';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useOfflineAuctionPurchase, usePurchaseAuctionList } from '../../Services/BBPS/Hooks';
|
import { useOfflineAuctionPurchase, usePurchaseAuctionList } from '../../Services/ApiCall/Hooks';
|
||||||
import { showSuccessAlert } from '../../Constant/ShowDailog';
|
import { showSuccessAlert } from '../../Constant/ShowDailog';
|
||||||
import { clearPurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
import { clearPurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
||||||
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
|
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||||
import NavigationString from '../../Constant/NavigationString';
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
import Skeleton from '../../Component/Skelton/Skelton';
|
import Skeleton from '../../Component/Skelton/Skelton';
|
||||||
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
const dummyAuctionList = [
|
import AppText from '../../components/AppText';
|
||||||
{
|
|
||||||
auctionId: 1,
|
|
||||||
auctionNumber: 'AUC-2025-001',
|
|
||||||
auctionPattern: 'Single Stage',
|
|
||||||
auctionDispatchDate: '25 Feb 2025',
|
|
||||||
auctionDispatcherNumber: 'DISP-001',
|
|
||||||
auctionFees: 2500,
|
|
||||||
gst: 18,
|
|
||||||
gstAmount: 450,
|
|
||||||
portalCharge: 100,
|
|
||||||
payableAmount: 3050,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const PurchaseAuctionList = () => {
|
const PurchaseAuctionList = () => {
|
||||||
const Navigation = useNavigation()
|
const Navigation: any = useNavigation();
|
||||||
const Dispatch = useDispatch()
|
const Dispatch = useDispatch();
|
||||||
|
const { colors, spacing, shadows } = useTheme();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [list, setList] = useState([]);
|
const [list, setList] = useState<any[]>([]);
|
||||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
|
||||||
|
|
||||||
|
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
||||||
const LoginUser = useSelector((state: any) => state.token.token);
|
const LoginUser = useSelector((state: any) => state.token.token);
|
||||||
const { mutate, isPending } = useOfflineAuctionPurchase()
|
|
||||||
|
const { mutate, isPending } = useOfflineAuctionPurchase();
|
||||||
const { mutate: purchaseListMutation, isPending: purchaseListPending } = usePurchaseAuctionList();
|
const { mutate: purchaseListMutation, isPending: purchaseListPending } = usePurchaseAuctionList();
|
||||||
|
|
||||||
const calculateTotalAmount = (item: any) => {
|
const calculateTotalAmount = (item: any) => {
|
||||||
const gstAmount =
|
const gstAmount =
|
||||||
(Number(item.auctionFees) + Number(item.portalCharge)) *
|
(Number(item.auctionFees) + Number(item.portalCharge)) *
|
||||||
(Number(item.gst) / 100);
|
(Number(item.gst) / 100);
|
||||||
|
return Number(item.auctionFees) + Number(item.portalCharge) + gstAmount;
|
||||||
|
};
|
||||||
|
|
||||||
const payableAmount =
|
const grandTotal = list.reduce(
|
||||||
Number(item.auctionFees) + Number(item.portalCharge) + gstAmount;
|
(sum: number, item: any) => sum + calculateTotalAmount(item),
|
||||||
return payableAmount
|
0,
|
||||||
}
|
);
|
||||||
|
|
||||||
const handleOfflinePurchase = async () => {
|
const handleOfflinePurchase = async () => {
|
||||||
const body = {
|
const body = {
|
||||||
@@ -59,233 +66,426 @@ const PurchaseAuctionList = () => {
|
|||||||
gst: b?.gst,
|
gst: b?.gst,
|
||||||
gstAmount: b?.gstAmount,
|
gstAmount: b?.gstAmount,
|
||||||
payableAmount: calculateTotalAmount(b),
|
payableAmount: calculateTotalAmount(b),
|
||||||
portalCharge: b?.portalCharge
|
portalCharge: b?.portalCharge,
|
||||||
})),
|
})),
|
||||||
orderNo: ''
|
orderNo: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mutate(body, {
|
mutate(body, {
|
||||||
onSuccess: (res) => {
|
onSuccess: res => {
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode === 200) {
|
||||||
showSuccessAlert(res.message)
|
showSuccessAlert(res.message);
|
||||||
Dispatch(clearPurchaseAuction())
|
Dispatch(clearPurchaseAuction());
|
||||||
Navigation.dispatch(
|
Navigation.dispatch(
|
||||||
CommonActions.reset({
|
CommonActions.reset({ index: 0, routes: [{ name: NavigationString.Home }] }),
|
||||||
index: 0,
|
|
||||||
routes: [{ name: NavigationString.Home }],
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: error => console.log(error, 'purchase Error'),
|
||||||
console.log(error, "================purchse Error");
|
});
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("purchase offline error", error);
|
console.log('purchase offline error', error);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handlePurchase = (type: 'online' | 'offline') => {
|
const handlePurchase = (type: 'online' | 'offline') => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
type === 'online' ? Alert.alert('Online Purchase Successful!') : handleOfflinePurchase();
|
type === 'online' ? undefined : handleOfflinePurchase();
|
||||||
|
|
||||||
}, 1200);
|
}, 1200);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = ({ item }: { item: typeof dummyAuctionList[0] }) => (
|
|
||||||
<View style={styles.card}>
|
|
||||||
<View style={styles.cardHeader}>
|
|
||||||
<Text style={styles.title}>{item.auctionNumber}</Text>
|
|
||||||
<Text style={styles.pattern}>{item.auctionPattern}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.cardRow}>
|
|
||||||
<Calendar size={18} color={colors.primary} />
|
|
||||||
<Text style={styles.text}>Dispatch Date: {item.auctionDispatchDate}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.cardRow}>
|
|
||||||
<User size={18} color={colors.primary} />
|
|
||||||
<Text style={styles.text}>Dispatcher No: {item.auctionDispatcherNumber}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.cardRow}>
|
|
||||||
<DollarSign size={18} color={colors.primary} />
|
|
||||||
<Text style={styles.text}>Auction Fees: ₹{item.auctionFees}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.cardRow}>
|
|
||||||
<DollarSign size={18} color={colors.primary} />
|
|
||||||
<Text style={styles.text}>GST ({item.gst}%): ₹{item.gstAmount}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.cardRow}>
|
|
||||||
<DollarSign size={18} color={colors.primary} />
|
|
||||||
<Text style={styles.text}>Portal Charge: ₹{item.portalCharge}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.totalRow}>
|
|
||||||
<CreditCard size={20} color={colors.white} />
|
|
||||||
<Text style={styles.total}>Total: ₹{item.payableAmount}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
const getPurchaseList = () => {
|
const getPurchaseList = () => {
|
||||||
const auctionNumbers: string[] = SelectedPurchaseList.map(
|
const auctionNumbers: string[] = SelectedPurchaseList.map(
|
||||||
(item: { auctionNumber: string }) => item.auctionNumber
|
(item: { auctionNumber: string }) => item.auctionNumber,
|
||||||
);
|
);
|
||||||
purchaseListMutation({ auctionNumbers, contractorId: LoginUser.contractorId }, {
|
purchaseListMutation(
|
||||||
onSuccess: (res) => {
|
{ auctionNumbers, contractorId: LoginUser.contractorId },
|
||||||
if (res.statusCode == 200) {
|
{
|
||||||
setList(res.data)
|
onSuccess: res => {
|
||||||
}
|
if (res.statusCode === 200) setList(res.data);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: error => console.log(error, 'purchase list error'),
|
||||||
console.log(error, "=================purchase list error");
|
},
|
||||||
}
|
);
|
||||||
})
|
};
|
||||||
}
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
React.useCallback(() => {
|
React.useCallback(() => {
|
||||||
getPurchaseList()
|
getPurchaseList();
|
||||||
}, [SelectedPurchaseList])
|
}, [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 (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeInDown.delay(index * 90).springify()}
|
||||||
|
style={styles.card}
|
||||||
|
>
|
||||||
|
{/* Top accent stripe */}
|
||||||
|
<View style={styles.cardAccent} />
|
||||||
|
|
||||||
|
<View style={styles.cardBody}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<View style={[styles.indexBadge, { backgroundColor: colors.primary }]}>
|
||||||
|
<AppText variant="caption" color={colors.buttonText} style={{ fontWeight: '800' } as any}>
|
||||||
|
{String(index + 1).padStart(2, '0')}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
<View style={{ flex: 1, marginLeft: spacing.s }}>
|
||||||
|
<AppText variant="h4" color={colors.text} numberOfLines={2}>
|
||||||
|
{item.auctionNumber}
|
||||||
|
</AppText>
|
||||||
|
{item.auctionPattern ? (
|
||||||
|
<View style={[styles.patternBadge, { backgroundColor: `${colors.primary}15` }]}>
|
||||||
|
<Gavel size={11} color={colors.primary} strokeWidth={2} />
|
||||||
|
<AppText variant="caption" color={colors.primary} style={{ fontWeight: '600' } as any}>
|
||||||
|
{item.auctionPattern}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.divider} />
|
||||||
|
|
||||||
|
{/* Info Rows */}
|
||||||
|
<InfoRow
|
||||||
|
icon={<Hash size={15} color={colors.primary} />}
|
||||||
|
label="Dispatcher No"
|
||||||
|
value={item.auctionDispatcherNumber ?? '—'}
|
||||||
|
colors={colors}
|
||||||
|
/>
|
||||||
|
<InfoRow
|
||||||
|
icon={<Calendar size={15} color={colors.primary} />}
|
||||||
|
label="Dispatch Date"
|
||||||
|
value={item.auctionDispatchDate ?? '—'}
|
||||||
|
colors={colors}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={[styles.divider, { marginVertical: spacing.s }]} />
|
||||||
|
|
||||||
|
{/* Fee Breakdown */}
|
||||||
|
<View style={[styles.feeSection, { backgroundColor: colors.overlay }]}>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary} style={styles.feeHeader as any}>
|
||||||
|
FEE BREAKDOWN
|
||||||
|
</AppText>
|
||||||
|
<FeeRow label="Auction Fees" value={`₹ ${Number(item.auctionFees).toFixed(2)}`} colors={colors} />
|
||||||
|
<FeeRow label={`GST (${item.gst}%)`} value={`₹ ${gstAmount.toFixed(2)}`} colors={colors} />
|
||||||
|
<FeeRow label="Portal Charge" value={`₹ ${Number(item.portalCharge).toFixed(2)}`} colors={colors} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Total Banner */}
|
||||||
|
<View style={[styles.totalBanner, { backgroundColor: colors.primary }]}>
|
||||||
|
<View style={styles.totalLeft}>
|
||||||
|
<Receipt size={18} color={colors.buttonText} />
|
||||||
|
<AppText variant="bodySmall" color={colors.buttonText} style={{ fontWeight: '600' } as any}>
|
||||||
|
Total Payable
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
<AppText variant="h4" color={colors.buttonText}>
|
||||||
|
₹ {total.toFixed(2)}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, backgroundColor: colors.white }}>
|
|
||||||
<InsideHeader title="Purchase List" showArrow />
|
|
||||||
<View style={styles.screen}>
|
<View style={styles.screen}>
|
||||||
{purchaseListPending ? <Skeleton /> : <FlatList
|
<InsideHeader title="Auction Purchase" showArrow />
|
||||||
|
|
||||||
|
{/* Grand Total Strip */}
|
||||||
|
<Animated.View entering={FadeInDown.springify()} style={styles.grandTotalStrip}>
|
||||||
|
<View>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>
|
||||||
|
{list.length} auction{list.length !== 1 ? 's' : ''}
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="h3" color={colors.text}>
|
||||||
|
₹ {grandTotal.toFixed(2)}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.grandTotalRight, { backgroundColor: `${colors.success}15` }]}>
|
||||||
|
<CheckCircle2 size={18} color={colors.success} />
|
||||||
|
<AppText variant="caption" color={colors.success} style={{ fontWeight: '600' } as any}>
|
||||||
|
Ready to pay
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
|
{/* List */}
|
||||||
|
{purchaseListPending ? (
|
||||||
|
<Skeleton />
|
||||||
|
) : (
|
||||||
|
<FlatList
|
||||||
data={list}
|
data={list}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={(item) => item.auctionId.toString()}
|
keyExtractor={item => item.auctionId?.toString()}
|
||||||
contentContainerStyle={styles.container}
|
contentContainerStyle={styles.container}
|
||||||
/>}
|
showsVerticalScrollIndicator={false}
|
||||||
</View>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<View style={styles.bottomButtonContainer}>
|
{/* Bottom Buttons */}
|
||||||
|
<View style={styles.footer}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.button, { marginRight: 8 }]}
|
style={[styles.button, styles.onlineButton, { borderColor: colors.primary }]}
|
||||||
|
activeOpacity={0.85}
|
||||||
onPress={() => handlePurchase('online')}
|
onPress={() => handlePurchase('online')}
|
||||||
>
|
>
|
||||||
<Text style={styles.buttonText}>Purchase Online</Text>
|
<Wifi size={18} color={colors.primary} />
|
||||||
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
|
<AppText variant="button" color={colors.primary} style={styles.btnText as any}>
|
||||||
|
Online
|
||||||
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.button, { marginLeft: 8 }]}
|
style={[styles.button, styles.offlineButton, { backgroundColor: colors.buttonPrimary }]}
|
||||||
|
activeOpacity={0.85}
|
||||||
onPress={() => handlePurchase('offline')}
|
onPress={() => handlePurchase('offline')}
|
||||||
>
|
>
|
||||||
{
|
{isPending ? (
|
||||||
isPending ? <Loader size='small' color={colors.white} /> : <>
|
<ActivityIndicator size="small" color="#fff" />
|
||||||
<Text style={styles.buttonText}>Purchase Offline</Text>
|
) : (
|
||||||
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
|
<>
|
||||||
|
<WifiOff size={18} color={colors.buttonText} />
|
||||||
|
<AppText variant="button" color={colors.buttonText} style={styles.btnText as any}>
|
||||||
|
Offline
|
||||||
|
</AppText>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Loading Overlay */}
|
||||||
|
{(loading || isPending) && (
|
||||||
|
<Animated.View entering={FadeIn.duration(200)} style={styles.loadingOverlay}>
|
||||||
|
<View style={[styles.loadingCard, { backgroundColor: colors.surface }]}>
|
||||||
|
<ActivityIndicator size="large" color={colors.primary} />
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={{ marginTop: spacing.s } as any}>
|
||||||
|
Processing purchase…
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PurchaseAuctionList;
|
export default PurchaseAuctionList;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
/* ─── Sub-components ─── */
|
||||||
|
|
||||||
|
const InfoRow = ({
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
colors,
|
||||||
|
}: {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
colors: any;
|
||||||
|
}) => (
|
||||||
|
<View style={infoRowStyles.row}>
|
||||||
|
<View style={infoRowStyles.iconWrap}>{icon}</View>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary} style={infoRowStyles.label as any}>
|
||||||
|
{label}
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="bodySmall" color={colors.text} style={infoRowStyles.value as any}>
|
||||||
|
{value}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}) => (
|
||||||
|
<View style={feeRowStyles.row}>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>{label}</AppText>
|
||||||
|
<AppText variant="caption" color={colors.text} style={{ fontWeight: '600' } as any}>{value}</AppText>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
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: {
|
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: {
|
container: {
|
||||||
padding: 16,
|
paddingHorizontal: spacing.m,
|
||||||
|
paddingTop: spacing.s,
|
||||||
paddingBottom: 120,
|
paddingBottom: 120,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* Card */
|
||||||
card: {
|
card: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: colors.surface,
|
||||||
borderRadius: 16,
|
borderRadius: 20,
|
||||||
padding: 16,
|
marginBottom: spacing.m,
|
||||||
marginBottom: 16,
|
overflow: 'hidden',
|
||||||
elevation: 5,
|
...shadows.medium,
|
||||||
shadowColor: '#000',
|
},
|
||||||
shadowOpacity: 0.12,
|
cardAccent: {
|
||||||
shadowOffset: { width: 0, height: 3 },
|
height: 4,
|
||||||
shadowRadius: 8,
|
backgroundColor: colors.primary,
|
||||||
|
},
|
||||||
|
cardBody: {
|
||||||
|
padding: spacing.m,
|
||||||
},
|
},
|
||||||
cardHeader: {
|
cardHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
alignItems: 'flex-start',
|
||||||
marginBottom: 12,
|
marginBottom: spacing.s,
|
||||||
},
|
},
|
||||||
title: {
|
indexBadge: {
|
||||||
fontSize: 16,
|
width: 32,
|
||||||
fontWeight: '700',
|
height: 32,
|
||||||
color: '#0F172A',
|
borderRadius: 10,
|
||||||
},
|
|
||||||
pattern: {
|
|
||||||
fontSize: 14,
|
|
||||||
color: colors.primary,
|
|
||||||
fontWeight: '600',
|
|
||||||
},
|
|
||||||
cardRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 6,
|
|
||||||
marginBottom: 4,
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
fontSize: 14,
|
|
||||||
color: '#555',
|
|
||||||
},
|
|
||||||
totalRow: {
|
|
||||||
marginTop: 10,
|
|
||||||
backgroundColor: colors.primary,
|
|
||||||
borderRadius: 12,
|
|
||||||
paddingVertical: 10,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
patternBadge: {
|
||||||
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 8,
|
gap: 4,
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
paddingHorizontal: spacing.s,
|
||||||
|
paddingVertical: 2,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginTop: 4,
|
||||||
},
|
},
|
||||||
total: {
|
divider: {
|
||||||
fontSize: 15,
|
height: StyleSheet.hairlineWidth,
|
||||||
|
backgroundColor: colors.border,
|
||||||
|
marginVertical: spacing.s,
|
||||||
|
},
|
||||||
|
feeSection: {
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: spacing.s + 2,
|
||||||
|
marginBottom: spacing.s,
|
||||||
|
},
|
||||||
|
feeHeader: {
|
||||||
|
letterSpacing: 1.2,
|
||||||
|
marginBottom: spacing.xs + 2,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: '#fff',
|
} as any,
|
||||||
|
totalBanner: {
|
||||||
|
borderRadius: 14,
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: spacing.m,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
},
|
},
|
||||||
bottomButtonContainer: {
|
totalLeft: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: spacing.s,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
footer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
right: 0,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
backgroundColor: colors.white,
|
paddingHorizontal: spacing.m,
|
||||||
width: '100%',
|
paddingTop: spacing.s + 4,
|
||||||
paddingVertical: 20,
|
paddingBottom: spacing.m + 4,
|
||||||
paddingHorizontal: 10,
|
backgroundColor: colors.surface,
|
||||||
elevation: 5
|
gap: spacing.s,
|
||||||
|
...shadows.strong,
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: colors.primary,
|
|
||||||
paddingVertical: 14,
|
|
||||||
borderRadius: 12,
|
|
||||||
alignItems: 'center',
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 14,
|
||||||
|
borderRadius: 16,
|
||||||
|
gap: spacing.s,
|
||||||
},
|
},
|
||||||
buttonText: {
|
onlineButton: {
|
||||||
color: '#fff',
|
backgroundColor: colors.surface,
|
||||||
fontWeight: '700',
|
borderWidth: 1.5,
|
||||||
fontSize: 14,
|
|
||||||
},
|
},
|
||||||
|
offlineButton: {
|
||||||
|
...shadows.medium,
|
||||||
|
},
|
||||||
|
btnText: {
|
||||||
|
letterSpacing: 1.2,
|
||||||
|
} as any,
|
||||||
|
|
||||||
|
/* Loading overlay */
|
||||||
loadingOverlay: {
|
loadingOverlay: {
|
||||||
...StyleSheet.absoluteFillObject,
|
...StyleSheet.absoluteFillObject,
|
||||||
backgroundColor: 'rgba(0,0,0,0.2)',
|
backgroundColor: 'rgba(0,0,0,0.3)',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
},
|
},
|
||||||
|
loadingCard: {
|
||||||
|
borderRadius: 20,
|
||||||
|
padding: spacing.l,
|
||||||
|
alignItems: 'center',
|
||||||
|
...shadows.strong,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,263 +1,298 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { View, Text, StyleSheet, TouchableOpacity, Platform } from 'react-native';
|
import { View, StyleSheet, TouchableOpacity } from 'react-native';
|
||||||
import Animated, { FadeInUp, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
import Animated, { FadeInUp, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import CountdownTimer from '../../Component/Counter/CountdownTimer';
|
import CountdownTimer from '../../Component/Counter/CountdownTimer';
|
||||||
import colors from '../../Constant/Color';
|
|
||||||
import AuctionDetail from './Modal/AuctionDetail';
|
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 [showDetail, setShowDetail] = React.useState(false);
|
||||||
const scale = useSharedValue(1);
|
const scale = useSharedValue(1);
|
||||||
const elevation = useSharedValue(8);
|
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => {
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
return {
|
|
||||||
transform: [{ scale: scale.value }],
|
transform: [{ scale: scale.value }],
|
||||||
elevation: elevation.value, // Android-safe
|
}));
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handlePressIn = () => {
|
const handlePressIn = () => scale.value = withTiming(0.98, { duration: 140 });
|
||||||
scale.value = withTiming(0.97, { duration: 120 });
|
const handlePressOut = () => scale.value = withTiming(1, { duration: 160 });
|
||||||
elevation.value = withTiming(3, { duration: 120 });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePressOut = () => {
|
const isAmended = item?.auctionStatus === 'Amended';
|
||||||
scale.value = withTiming(1, { duration: 160 });
|
const biddingEnd = isAmended
|
||||||
elevation.value = withTiming(8, { duration: 160 });
|
? 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 statusColor = isAuctionEnd ? '#EF4444' : buttonEnable ? colors.primary : '#94A3B8';
|
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 lightStatusBg = isAuctionEnd ? '#FEF2F2' : buttonEnable ? '#EFF6FF' : '#F8FAFC';
|
const styles = makeStyles(colors, spacing);
|
||||||
|
|
||||||
|
if (showDetail) {
|
||||||
|
return <AuctionDetail visible={showDetail} data={item} onClose={() => setShowDetail(false)} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<Animated.View
|
||||||
{!showDetail ? <Animated.View
|
entering={FadeInUp.duration(450).springify()}
|
||||||
entering={FadeInUp.duration(400)}
|
style={[styles.container, shadows.medium, animatedStyle]}
|
||||||
style={[styles.shadowContainer, animatedStyle]}
|
|
||||||
>
|
>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={1}
|
activeOpacity={1}
|
||||||
onPress={() => setShowDetail(true)}
|
onPress={() => setShowDetail(true)}
|
||||||
onPressIn={handlePressIn}
|
onPressIn={handlePressIn}
|
||||||
onPressOut={handlePressOut}
|
onPressOut={handlePressOut}
|
||||||
style={styles.cardInternal}
|
style={styles.card}
|
||||||
>
|
>
|
||||||
<View style={[styles.statusAccent, { backgroundColor: statusColor }]} />
|
{/* ── Header ── */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<View style={styles.headerTop}>
|
||||||
|
{/* Status pill */}
|
||||||
|
<View style={[styles.statusBadge, { backgroundColor: statusConfig.bg }]}>
|
||||||
|
<statusConfig.Icon size={11} color={statusConfig.color} strokeWidth={3} />
|
||||||
|
<AppText variant="caption" color={statusConfig.color} style={styles.statusText as any}>
|
||||||
|
{statusConfig.label}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
{/* Auction number */}
|
||||||
|
<View style={styles.numberRow}>
|
||||||
|
<Tag size={12} color={colors.primary} strokeWidth={2} />
|
||||||
|
<AppText variant="caption" color={colors.textSecondary} style={styles.numberText as any}>
|
||||||
|
{item?.auctionNumber}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.mainInfo}>
|
{/* Work title */}
|
||||||
{/* Header */}
|
<AppText variant="bodySmall" color={colors.text} numberOfLines={2} style={styles.workTitle as any}>
|
||||||
<View style={styles.headerRow}>
|
{item?.auctionOfWork ?? item?.auctionNumber ?? 'Auction Details'}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.divider} />
|
||||||
|
|
||||||
|
{/* ── Info Grid ── */}
|
||||||
|
<View style={styles.infoGrid}>
|
||||||
|
{/* Fee */}
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<View style={[styles.iconBox, { backgroundColor: `${colors.primary}15` }]}>
|
||||||
|
<IndianRupee size={17} color={colors.primary} strokeWidth={2} />
|
||||||
|
</View>
|
||||||
<View>
|
<View>
|
||||||
<View style={[styles.statusBadge, { backgroundColor: lightStatusBg }]}>
|
<AppText variant="caption" color={colors.textSecondary}>Auction Fee</AppText>
|
||||||
<View style={[styles.statusDot, { backgroundColor: statusColor }]} />
|
<AppText variant="caption" color={colors.text} style={styles.infoValue as any}>
|
||||||
<Text style={[styles.statusText, { color: statusColor }]}>
|
₹{Number(item?.auctionFees ?? 0).toLocaleString('en-IN')}
|
||||||
{isAuctionEnd ? 'ENDED' : buttonEnable ? 'LIVE' : 'PENDING'}
|
</AppText>
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<Text style={styles.title}>{item.auctionNumber}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.amountContainer}>
|
|
||||||
<Text style={styles.amount}>
|
|
||||||
₹{item.auctionFees?.toLocaleString()}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.feeLabel}>Auction Fee</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Date */}
|
{/* Bidding End */}
|
||||||
<View style={styles.dateRow}>
|
<View style={styles.infoRow}>
|
||||||
<Text style={styles.dateText}>
|
<View style={[styles.iconBox, { backgroundColor: `${colors.primary}15` }]}>
|
||||||
<Text style={styles.dateLabel}>Bidding Start: </Text>
|
<Calendar size={17} color={colors.primary} strokeWidth={2} />
|
||||||
{moment(
|
</View>
|
||||||
item?.keyDates?.auctionBidding?.startDateTime?.replace(' kk', ''),
|
<View>
|
||||||
'DD-MM-YYYY HH:mm:ss'
|
<AppText variant="caption" color={colors.textSecondary}>Bidding Ends</AppText>
|
||||||
).format('DD MMM YYYY, hh:mm A')}
|
<AppText variant="caption" color={colors.text} style={styles.infoValue as any}>
|
||||||
</Text>
|
{biddingEnd
|
||||||
<Text style={styles.dateText}>
|
? moment(biddingEnd, 'DD-MM-YYYY HH:mm:ss').format('DD MMM, hh:mm A')
|
||||||
<Text style={styles.dateLabel}>Bidding Ends: </Text>
|
: '—'}
|
||||||
{moment(
|
</AppText>
|
||||||
item?.keyDates?.auctionBidding?.endDateTime?.replace(' kk', ''),
|
</View>
|
||||||
'DD-MM-YYYY HH:mm:ss'
|
</View>
|
||||||
).format('DD MMM YYYY, hh:mm A')}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* ── Footer ── */}
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<View style={styles.footerTop}>
|
<AppText variant="caption" color={colors.textSecondary} style={{ marginBottom: 4, alignSelf: 'flex-start' } as any}>
|
||||||
<Text style={styles.footerLabel}>Remaining Time</Text>
|
Time Remaining
|
||||||
|
</AppText>
|
||||||
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
||||||
|
{/* Timer */}
|
||||||
|
<View style={styles.timerSection}>
|
||||||
|
|
||||||
{buttonEnable && !isAuctionEnd ? (
|
{buttonEnable && !isAuctionEnd ? (
|
||||||
<CountdownTimer
|
<CountdownTimer endDate={biddingEnd} />
|
||||||
endDate={item.keyDates.auctionBidding.endDateTime}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.endedText}>--:--:--</Text>
|
<AppText variant="bodySmall" color={colors.textSecondary} style={{ fontWeight: '700' } as any}>
|
||||||
|
-- : -- : --
|
||||||
|
</AppText>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* CTA Button */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
disabled={!buttonEnable}
|
disabled={!buttonEnable || isAuctionEnd}
|
||||||
onPress={onStartBidding}
|
onPress={onStartBidding}
|
||||||
activeOpacity={0.85}
|
activeOpacity={0.85}
|
||||||
style={[
|
style={[
|
||||||
styles.actionButton,
|
styles.ctaBtn,
|
||||||
{ backgroundColor: statusColor },
|
{
|
||||||
!buttonEnable && styles.disabledButton,
|
backgroundColor:
|
||||||
|
isAuctionEnd
|
||||||
|
? colors.error
|
||||||
|
: buttonEnable
|
||||||
|
? colors.primary
|
||||||
|
: colors.border,
|
||||||
|
opacity: (!buttonEnable || isAuctionEnd) ? 0.8 : 1,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.addText}>
|
<AppText
|
||||||
{isAuctionEnd
|
variant="button"
|
||||||
? 'Bidding Closed'
|
color={colors.buttonText}
|
||||||
: !buttonEnable
|
style={styles.ctaText as any}
|
||||||
? 'Pending Bid'
|
>
|
||||||
: 'Proceed to Start Bidding'}
|
{isAuctionEnd ? 'Ended' : !buttonEnable ? 'Pending' : 'Bid Now'}
|
||||||
</Text>
|
</AppText>
|
||||||
|
{buttonEnable && !isAuctionEnd && (
|
||||||
|
<ArrowRight size={15} color={colors.buttonText} strokeWidth={2.5} />
|
||||||
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Animated.View> :
|
</Animated.View>
|
||||||
<AuctionDetail visible={showDetail} data={item} onClose={() => setShowDetail(false)} />
|
|
||||||
}
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PurchaseAuctionTile;
|
export default PurchaseAuctionTile;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const makeStyles = (colors: any, spacing: any) =>
|
||||||
shadowContainer: {
|
StyleSheet.create({
|
||||||
marginHorizontal: 5,
|
container: {
|
||||||
marginTop: 14,
|
marginHorizontal: spacing.s,
|
||||||
marginBottom: 6,
|
marginBottom: spacing.m,
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
backgroundColor: '#fff',
|
borderWidth: 1,
|
||||||
elevation: 8,
|
borderColor: colors.border,
|
||||||
|
backgroundColor: colors.surface,
|
||||||
},
|
},
|
||||||
cardInternal: {
|
card: {
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
overflow: 'hidden',
|
backgroundColor: colors.surface,
|
||||||
flexDirection: 'row',
|
padding: spacing.m,
|
||||||
backgroundColor: '#fff',
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(0,0,0,0.03)',
|
||||||
},
|
},
|
||||||
statusAccent: {
|
|
||||||
width: 5,
|
/* Header */
|
||||||
height: '100%',
|
header: {
|
||||||
|
marginBottom: spacing.m,
|
||||||
},
|
},
|
||||||
mainInfo: {
|
headerTop: {
|
||||||
flex: 1,
|
|
||||||
padding: 16,
|
|
||||||
},
|
|
||||||
headerRow: {
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'center',
|
||||||
marginBottom: 12,
|
marginBottom: spacing.s,
|
||||||
},
|
},
|
||||||
statusBadge: {
|
statusBadge: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 10,
|
||||||
paddingVertical: 3,
|
paddingVertical: 5,
|
||||||
borderRadius: 6,
|
borderRadius: 20,
|
||||||
marginBottom: 6,
|
gap: 5,
|
||||||
alignSelf: 'flex-start',
|
|
||||||
},
|
|
||||||
statusDot: {
|
|
||||||
width: 6,
|
|
||||||
height: 6,
|
|
||||||
borderRadius: 3,
|
|
||||||
marginRight: 5,
|
|
||||||
},
|
},
|
||||||
statusText: {
|
statusText: {
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
|
fontSize: 10,
|
||||||
|
textTransform: 'uppercase',
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
|
} as any,
|
||||||
|
numberRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
},
|
},
|
||||||
title: {
|
numberText: {
|
||||||
fontWeight: '800',
|
letterSpacing: 0.4,
|
||||||
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',
|
fontWeight: '600',
|
||||||
|
} as any,
|
||||||
|
workTitle: {
|
||||||
|
fontWeight: '600',
|
||||||
|
lineHeight: 20,
|
||||||
|
} as any,
|
||||||
|
|
||||||
|
divider: {
|
||||||
|
height: StyleSheet.hairlineWidth,
|
||||||
|
backgroundColor: colors.border,
|
||||||
|
marginBottom: spacing.m,
|
||||||
|
opacity: 0.6,
|
||||||
},
|
},
|
||||||
dateLabel: {
|
|
||||||
fontWeight: '400',
|
/* Info grid */
|
||||||
color: '#64748B',
|
infoGrid: {
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
borderTopWidth: 1,
|
|
||||||
borderTopColor: '#F1F5F9',
|
|
||||||
paddingTop: 12,
|
|
||||||
},
|
|
||||||
footerTop: {
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: spacing.l,
|
||||||
|
gap: spacing.s,
|
||||||
|
},
|
||||||
|
infoRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: 12,
|
gap: 10,
|
||||||
|
flex: 1,
|
||||||
},
|
},
|
||||||
footerLabel: {
|
iconBox: {
|
||||||
fontSize: 11,
|
width: 36,
|
||||||
fontWeight: '600',
|
height: 36,
|
||||||
color: '#94A3B8',
|
borderRadius: 10,
|
||||||
textTransform: 'uppercase',
|
alignItems: 'center',
|
||||||
},
|
|
||||||
endedText: {
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: '700',
|
|
||||||
color: '#CBD5E1',
|
|
||||||
},
|
|
||||||
actionButton: {
|
|
||||||
width: '100%',
|
|
||||||
height: 48,
|
|
||||||
borderRadius: 12,
|
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
infoValue: {
|
||||||
|
fontWeight: '700',
|
||||||
|
} as any,
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
footer: {
|
||||||
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
...Platform.select({
|
backgroundColor: colors.background,
|
||||||
ios: {
|
marginHorizontal: -spacing.m,
|
||||||
shadowColor: '#000',
|
marginBottom: -spacing.m,
|
||||||
shadowOffset: { width: 0, height: 2 },
|
padding: spacing.m,
|
||||||
shadowOpacity: 0.1,
|
borderBottomLeftRadius: 16,
|
||||||
shadowRadius: 4,
|
borderBottomRightRadius: 16,
|
||||||
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderTopColor: colors.border,
|
||||||
},
|
},
|
||||||
}),
|
timerSection: {
|
||||||
|
flex: 1,
|
||||||
},
|
},
|
||||||
disabledButton: {
|
ctaBtn: {
|
||||||
backgroundColor: '#E2E8F0',
|
flexDirection: 'row',
|
||||||
},
|
alignItems: 'center',
|
||||||
addText: {
|
justifyContent: 'center',
|
||||||
color: '#fff',
|
paddingVertical: 10,
|
||||||
fontSize: 14,
|
paddingHorizontal: 18,
|
||||||
fontWeight: '800',
|
borderRadius: 12,
|
||||||
letterSpacing: 0.2,
|
gap: 5,
|
||||||
},
|
},
|
||||||
|
ctaText: {
|
||||||
|
letterSpacing: 0.8,
|
||||||
|
} as any,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,220 +1,387 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
|
import {
|
||||||
import Animated, { FadeIn, FadeOut, Layout, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
View,
|
||||||
import { ChevronDown, Trash2, Info } from 'lucide-react-native';
|
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 { InsideHeader } from '../../Component/Index';
|
||||||
import colors from '../../Constant/Color';
|
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import NavigationString from '../../Constant/NavigationString';
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
import { togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
||||||
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
|
import AppText from '../../components/AppText';
|
||||||
|
|
||||||
const PurchaseList = () => {
|
const PurchaseList = () => {
|
||||||
const Navigation: any = useNavigation()
|
const Navigation: any = useNavigation();
|
||||||
const Dispatch = useDispatch()
|
const Dispatch = useDispatch();
|
||||||
|
const { colors, spacing, shadows } = useTheme();
|
||||||
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
|
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
|
||||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
||||||
|
|
||||||
const toggleExpand = (index: number) => {
|
const toggleExpand = (index: number) => {
|
||||||
setExpandedIndex(prev => (prev === index ? null : index));
|
setExpandedIndex(prev => (prev === index ? null : index));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!SelectedPurchaseList || SelectedPurchaseList.length < 1) {
|
||||||
|
Navigation.goBack();
|
||||||
|
}
|
||||||
|
}, [SelectedPurchaseList]);
|
||||||
|
|
||||||
|
const styles = makeStyles(colors, spacing, shadows);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.screen}>
|
<View style={styles.screen}>
|
||||||
<InsideHeader title="Purchase" showArrow />
|
<InsideHeader title="Purchase Auctions" showArrow />
|
||||||
|
|
||||||
<ScrollView contentContainerStyle={styles.container}>
|
{/* Summary Strip */}
|
||||||
{/* Header */}
|
<View style={styles.summaryStrip}>
|
||||||
<View style={styles.header}>
|
<View style={styles.summaryIcon}>
|
||||||
<Text style={styles.heading}>Purchase List</Text>
|
<ShoppingBag size={16} color={colors.primary} />
|
||||||
{/* <Info size={20} color="#0F172A" /> */}
|
</View>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>
|
||||||
|
<AppText variant="bodySmall" color={colors.primary} style={{ fontWeight: '700' } as any}>
|
||||||
|
{SelectedPurchaseList.length}
|
||||||
|
</AppText>
|
||||||
|
{' '}auction{SelectedPurchaseList.length !== 1 ? 's' : ''} selected for purchase
|
||||||
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Cards */}
|
<ScrollView
|
||||||
{SelectedPurchaseList.map((item: any, index: any) => {
|
contentContainerStyle={styles.container}
|
||||||
const expanded = expandedIndex === index;
|
showsVerticalScrollIndicator={false}
|
||||||
return (
|
>
|
||||||
<Animated.View
|
{SelectedPurchaseList.map((item: any, index: number) => (
|
||||||
|
<PurchaseListItem
|
||||||
key={item.auctionId}
|
key={item.auctionId}
|
||||||
layout={Layout.springify()}
|
item={item}
|
||||||
style={styles.card}
|
index={index}
|
||||||
>
|
expandedIndex={expandedIndex}
|
||||||
<TouchableOpacity
|
toggleExpand={toggleExpand}
|
||||||
activeOpacity={0.9}
|
colors={colors}
|
||||||
onPress={() => toggleExpand(index)}
|
styles={styles}
|
||||||
>
|
Dispatch={Dispatch}
|
||||||
<CardHeader
|
|
||||||
title={item.auctionOfWork}
|
|
||||||
expanded={expanded}
|
|
||||||
/>
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
<Text style={styles.subTitle}>
|
<View style={{ height: 90 }} />
|
||||||
Auction No: {item.auctionNumber}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text style={styles.subTitle}>
|
|
||||||
Status:{' '}
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: item.status ? '#16A34A' : '#DC2626',
|
|
||||||
fontWeight: '700',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.status ? 'Open' : 'Closed'}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
{/* Expanded Content */}
|
|
||||||
{expanded && (
|
|
||||||
<Animated.View
|
|
||||||
entering={FadeIn.duration(220)}
|
|
||||||
exiting={FadeOut.duration(180)}
|
|
||||||
layout={Layout.springify()}
|
|
||||||
style={styles.details}
|
|
||||||
>
|
|
||||||
{renderRow('Auction Mode', item.auctionMode)}
|
|
||||||
{renderRow('Pattern', item.auctionPattern)}
|
|
||||||
{renderRow('Fees', `₹ ${item.auctionFees}`)}
|
|
||||||
{renderRow('Department', item.deptName)}
|
|
||||||
{renderRow('Purchase End', item.purchaseEnd)}
|
|
||||||
|
|
||||||
<TouchableOpacity style={styles.removeButton} onPress={() => { Dispatch(togglePurchaseAuction(item)) }}>
|
|
||||||
<Trash2 size={18} color="#fff" />
|
|
||||||
<Text style={styles.removeText}>Remove</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</Animated.View>
|
|
||||||
)}
|
|
||||||
</Animated.View>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Floating Proceed Button */}
|
||||||
<TouchableOpacity disabled={Boolean(!SelectedPurchaseList.length)} style={[styles.proceedButton, { backgroundColor: Boolean(!SelectedPurchaseList.length) ? colors.lightText : colors.primary }]} onPress={() => { Navigation.navigate(NavigationString.PurchaseAuctionList) }}>
|
<View style={styles.footerContainer}>
|
||||||
<Text style={styles.proceedText}>Proceed to Purchase</Text>
|
<TouchableOpacity
|
||||||
|
activeOpacity={0.9}
|
||||||
|
disabled={!SelectedPurchaseList.length}
|
||||||
|
style={[
|
||||||
|
styles.proceedButton,
|
||||||
|
{
|
||||||
|
backgroundColor: SelectedPurchaseList.length
|
||||||
|
? colors.buttonPrimary
|
||||||
|
: colors.secondary,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onPress={() => Navigation.navigate(NavigationString.PurchaseAuctionList)}
|
||||||
|
>
|
||||||
|
<AppText variant="button" color={colors.buttonText} style={styles.proceedText as any}>
|
||||||
|
Proceed to Purchase
|
||||||
|
</AppText>
|
||||||
|
<View style={styles.countBadge}>
|
||||||
|
<AppText variant="caption" color={colors.buttonPrimary} style={{ fontWeight: '800' } as any}>
|
||||||
|
{SelectedPurchaseList.length}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
<ChevronRight size={20} color={colors.buttonText} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PurchaseList;
|
export default PurchaseList;
|
||||||
|
|
||||||
const CardHeader = ({ title, expanded }: {
|
/* ─── Sub-components ─── */
|
||||||
title: string;
|
|
||||||
expanded: boolean;
|
const PurchaseListItem = ({ item, index, expandedIndex, toggleExpand, colors, styles, Dispatch }: any) => {
|
||||||
}) => {
|
const expanded = expandedIndex === index;
|
||||||
const rotation = useSharedValue(expanded ? 1 : 0);
|
const isActive = item.status;
|
||||||
rotation.value = withTiming(expanded ? 1 : 0, { duration: 220 });
|
const chevronStyle = useChevronStyle(expanded);
|
||||||
const arrowStyle = useAnimatedStyle(() => ({
|
|
||||||
transform: [{ rotate: `${rotation.value * 180}deg` }],
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.cardHeader}>
|
<Animated.View
|
||||||
<Text style={styles.title}>{title}</Text>
|
entering={FadeInDown.delay(index * 80).springify()}
|
||||||
<Animated.View style={arrowStyle}>
|
layout={Layout.springify()}
|
||||||
<ChevronDown size={22} color={colors.black} />
|
style={[styles.card, expanded && styles.cardExpanded]}
|
||||||
|
>
|
||||||
|
{/* Left Accent Bar */}
|
||||||
|
<View style={[styles.accentBar, { backgroundColor: isActive ? colors.success : colors.primary }]} />
|
||||||
|
|
||||||
|
<View style={styles.cardInner}>
|
||||||
|
<TouchableOpacity
|
||||||
|
activeOpacity={0.85}
|
||||||
|
onPress={() => toggleExpand(index)}
|
||||||
|
>
|
||||||
|
{/* Badge Row */}
|
||||||
|
<View style={styles.badgeRow}>
|
||||||
|
<View style={styles.auctionBadge}>
|
||||||
|
<Gavel size={11} color={colors.primary} strokeWidth={2} />
|
||||||
|
<AppText variant="caption" color={colors.primary} style={styles.badgeText as any}>
|
||||||
|
{item.auctionNumber}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Title + Chevron */}
|
||||||
|
<View style={styles.titleRow}>
|
||||||
|
<AppText variant="h4" style={styles.title as any} numberOfLines={2}>
|
||||||
|
{item.auctionOfWork ?? item.auctionNumber}
|
||||||
|
</AppText>
|
||||||
|
<Animated.View style={chevronStyle}>
|
||||||
|
<ChevronDown size={20} color={colors.textSecondary} />
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Dept Tag */}
|
||||||
|
<View style={styles.deptRow}>
|
||||||
|
<Tag size={12} color={colors.textSecondary} />
|
||||||
|
<AppText variant="caption" color={colors.textSecondary} style={styles.deptText as any}>
|
||||||
|
{item.deptName || 'Department'}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Expanded Section */}
|
||||||
|
{expanded && (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeIn.duration(220)}
|
||||||
|
exiting={FadeOut.duration(160)}
|
||||||
|
layout={Layout.springify()}
|
||||||
|
style={styles.detailsContainer}
|
||||||
|
>
|
||||||
|
<View style={styles.divider} />
|
||||||
|
|
||||||
|
<DetailRow label="Auction Mode" value={item.auctionMode} colors={colors} />
|
||||||
|
<DetailRow label="Pattern" value={item.auctionPattern} colors={colors} />
|
||||||
|
<DetailRow label="Auction Fees" value={`₹ ${item.auctionFees}`} colors={colors} valueColor={colors.primary} />
|
||||||
|
<DetailRow label="Department" value={item.deptName} colors={colors} />
|
||||||
|
<DetailRow label="Purchase End" value={item.purchaseEnd} colors={colors} />
|
||||||
|
|
||||||
|
{/* Remove Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.removeButton}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
onPress={() => Dispatch(togglePurchaseAuction(item))}
|
||||||
|
>
|
||||||
|
<Trash2 size={16} color="#fff" />
|
||||||
|
<AppText variant="button" color="#fff" style={styles.removeText as any}>
|
||||||
|
Remove
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Animated.View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderRow = (label: string, value: string) => (
|
const DetailRow = ({
|
||||||
<View style={styles.row}>
|
label,
|
||||||
<Text style={styles.label}>{label}</Text>
|
value,
|
||||||
<Text style={styles.value}>{value}</Text>
|
colors,
|
||||||
|
valueColor,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
colors: any;
|
||||||
|
valueColor?: string;
|
||||||
|
}) => (
|
||||||
|
<View style={detailRowStyles.row}>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary} style={detailRowStyles.label as any}>
|
||||||
|
{label}
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="caption" color={valueColor || colors.text} style={detailRowStyles.value as any}>
|
||||||
|
{value ?? '—'}
|
||||||
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const detailRowStyles = StyleSheet.create({
|
||||||
const styles = StyleSheet.create({
|
row: {
|
||||||
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: {
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
paddingVertical: 8,
|
||||||
title: {
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
fontSize: 16,
|
borderBottomColor: 'rgba(0,0,0,0.06)',
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
fontSize: 13,
|
flex: 1,
|
||||||
color: '#64748B',
|
fontWeight: '500',
|
||||||
},
|
} as any,
|
||||||
value: {
|
value: {
|
||||||
fontSize: 15,
|
flex: 1,
|
||||||
|
textAlign: 'right',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: '#0F172A',
|
} 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: {
|
removeButton: {
|
||||||
marginTop: 18,
|
marginTop: spacing.m,
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.error,
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
gap: 8,
|
alignItems: 'center',
|
||||||
|
gap: spacing.s,
|
||||||
},
|
},
|
||||||
removeText: {
|
removeText: {
|
||||||
color: '#fff',
|
letterSpacing: 1,
|
||||||
fontWeight: '700',
|
} as any,
|
||||||
|
footerContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 20,
|
||||||
|
left: spacing.m,
|
||||||
|
right: spacing.m,
|
||||||
},
|
},
|
||||||
proceedButton: {
|
proceedButton: {
|
||||||
position: 'absolute',
|
flexDirection: 'row',
|
||||||
bottom: 16,
|
|
||||||
left: 16,
|
|
||||||
right: 16,
|
|
||||||
backgroundColor: colors.primary,
|
|
||||||
paddingVertical: 16,
|
|
||||||
borderRadius: 18,
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 16,
|
||||||
|
borderRadius: 20,
|
||||||
|
gap: spacing.s,
|
||||||
|
...shadows.strong,
|
||||||
},
|
},
|
||||||
proceedText: {
|
proceedText: {
|
||||||
color: '#fff',
|
letterSpacing: 1.5,
|
||||||
fontSize: 16,
|
} as any,
|
||||||
fontWeight: '800',
|
countBadge: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 12,
|
||||||
|
minWidth: 24,
|
||||||
|
height: 24,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: 6,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface LiveAuctionTileProps {
|
|||||||
isPurchased?: boolean;
|
isPurchased?: boolean;
|
||||||
auctionId?: any;
|
auctionId?: any;
|
||||||
auctionNumber?: any;
|
auctionNumber?: any;
|
||||||
|
isPurchasedByContractor?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface allBooleanProps {
|
export interface allBooleanProps {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import React, { memo, useCallback, useRef, useState } from 'react';
|
import React, { memo, useCallback, useRef, useState } from 'react';
|
||||||
import { View, Text, TouchableOpacity, StyleSheet, Platform, TextInput, Alert } from 'react-native';
|
import { View, Text, TouchableOpacity, StyleSheet, Platform, TextInput, Alert } from 'react-native';
|
||||||
import Animated, { FadeInUp, Layout, interpolate, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
|
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 AuctionItemDetail from '../../Auction/Modal/AuctionItemDetail';
|
||||||
import MainStyle from '../../../Styles/MainStyle';
|
import MainStyle from '../../../Styles/MainStyle';
|
||||||
|
import { useTheme } from '../../../Hooks/useTheme';
|
||||||
|
import AppText from '../../../components/AppText';
|
||||||
import { Loader } from '../../../Component/Index';
|
import { Loader } from '../../../Component/Index';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
@@ -11,6 +13,8 @@ import Variables from '../../../Constant/Variable';
|
|||||||
import { showErrorAlert, showSuccessAlert } from '../../../Constant/ShowDailog';
|
import { showErrorAlert, showSuccessAlert } from '../../../Constant/ShowDailog';
|
||||||
|
|
||||||
const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, SelectedAuction, allBoolean, setAllBoolean }: any) => {
|
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 Token = useSelector((state: any) => state.token.token)
|
||||||
const [showDetail, setShowDetail] = useState(false);
|
const [showDetail, setShowDetail] = useState(false);
|
||||||
const [value, setValue] = useState(`${item.bidAmount}`);
|
const [value, setValue] = useState(`${item.bidAmount}`);
|
||||||
@@ -93,7 +97,6 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec
|
|||||||
setAllState((prev: any) => ({ ...prev, isLoading: false }))
|
setAllState((prev: any) => ({ ...prev, isLoading: false }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// console.log(item, 'jkjjj');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
@@ -101,7 +104,7 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec
|
|||||||
!showDetail ? <Animated.View
|
!showDetail ? <Animated.View
|
||||||
layout={Layout.springify()}
|
layout={Layout.springify()}
|
||||||
entering={FadeInUp.delay(100)}
|
entering={FadeInUp.delay(100)}
|
||||||
style={[styles.card, styles.leadingCard, { borderColor: item.me ? '#22C55E' : 'red', backgroundColor: item.me ? "#F0FDF4" : '#fdf0f0ff' }]}
|
style={[styles.card, item.me ? styles.leadingCard : undefined, { borderColor: item.me ? colors.success : colors.error, backgroundColor: item.me ? `${colors.success}10` : `${colors.error}10` }]}
|
||||||
>
|
>
|
||||||
{/* HEADER */}
|
{/* HEADER */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -110,28 +113,28 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec
|
|||||||
style={styles.cardHeader}
|
style={styles.cardHeader}
|
||||||
>
|
>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<Text style={styles.productName}>{item.productName}</Text>
|
<AppText variant="h4" color={colors.text} bold style={styles.productName as any}>{item.productName}</AppText>
|
||||||
{/* auctionStartValueFigure */}
|
{/* auctionStartValueFigure */}
|
||||||
<Text style={styles.itemIdText}>
|
<AppText variant="caption" color={colors.textSecondary} style={styles.itemIdText as any}>
|
||||||
Initial Amt : {item?.auctionStartValueFigure}
|
Initial Amt : {item?.auctionStartValueFigure}
|
||||||
</Text>
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<TouchableOpacity style={styles.headerRight} onPress={toggleExpand}>
|
<TouchableOpacity style={styles.headerRight} onPress={toggleExpand}>
|
||||||
<View style={[MainStyle.flexBetween, { gap: 10 }]}>
|
<View style={[MainStyle.flexBetween, { gap: 10 }]}>
|
||||||
<View style={[styles.badge, item.me ? styles.meBadge : styles.liveBadge]}>
|
<View style={[styles.badge, item.me ? styles.meBadge : styles.liveBadge]}>
|
||||||
<Text style={[styles.badgeText, item.me ? styles.meText : styles.liveText]}>
|
<AppText variant="caption" bold style={[styles.badgeText as any, item.me ? styles.meText : styles.liveText]}>
|
||||||
{item.me ? 'LEADING' : 'LIVE'}
|
{item.me ? 'LEADING' : 'LIVE'}
|
||||||
</Text>
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Animated.Text style={[styles.arrow, arrowStyle]}>
|
<Animated.Text style={[styles.arrow, { color: colors.textSecondary }, arrowStyle]}>
|
||||||
▼
|
▼
|
||||||
</Animated.Text>
|
</Animated.Text>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.itemIdText}>
|
<AppText variant="caption" color={colors.textSecondary} style={styles.itemIdText as any}>
|
||||||
{SelectedAuction?.auctionNumber}
|
{SelectedAuction?.auctionNumber}
|
||||||
</Text>
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -139,15 +142,15 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec
|
|||||||
{/* PRICE INFO */}
|
{/* PRICE INFO */}
|
||||||
<View style={styles.priceRow}>
|
<View style={styles.priceRow}>
|
||||||
<View>
|
<View>
|
||||||
<Text style={styles.label}>Current Bid</Text>
|
<AppText variant="caption" color={colors.textSecondary} semiBold style={styles.label as any}>Current Bid</AppText>
|
||||||
<Text style={styles.mainPrice}>₹{item?.bidAmount || item.auctionStartValueFigure}</Text>
|
<AppText variant="h2" color={colors.text} bold style={styles.mainPrice as any}>₹{item?.bidAmount || item.auctionStartValueFigure}</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={{ alignItems: 'flex-end' }}>
|
<View style={{ alignItems: 'flex-end' }}>
|
||||||
<Text style={styles.label}>Increment</Text>
|
<AppText variant="caption" color={colors.textSecondary} semiBold style={styles.label as any}>Increment</AppText>
|
||||||
<Text style={styles.incrementText}>
|
<AppText variant="body" color={colors.primary} bold style={styles.incrementText as any}>
|
||||||
+₹{item.bidVariationValue}
|
+₹{item.bidVariationValue}
|
||||||
</Text>
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -155,7 +158,7 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec
|
|||||||
<Animated.View style={[styles.collapsibleContainer, (!allBoolean.rebid && item.contractorId == undefined) ? bodyStyle : bodyStyle2]}>
|
<Animated.View style={[styles.collapsibleContainer, (!allBoolean.rebid && item.contractorId == undefined) ? bodyStyle : bodyStyle2]}>
|
||||||
<View style={styles.divider} />
|
<View style={styles.divider} />
|
||||||
|
|
||||||
<Text style={styles.bidLabel}>Set Your Bid</Text>
|
<AppText variant="bodySmall" color={colors.textSecondary} semiBold style={styles.bidLabel as any}>Set Your Bid</AppText>
|
||||||
|
|
||||||
{!allBoolean.oneTimeBid && <View style={styles.stepperContainer}>
|
{!allBoolean.oneTimeBid && <View style={styles.stepperContainer}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -163,29 +166,29 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec
|
|||||||
disabled={isMin}
|
disabled={isMin}
|
||||||
style={[styles.stepBtn, isMin && styles.disabledBtn]}
|
style={[styles.stepBtn, isMin && styles.disabledBtn]}
|
||||||
>
|
>
|
||||||
<Text style={styles.stepText}>−</Text>
|
<AppText variant="h3" color="#FFF" bold style={styles.stepText as any}>−</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<View style={styles.inputBox}>
|
<View style={styles.inputBox}>
|
||||||
<Text style={styles.currency}>₹</Text>
|
<AppText variant="body" color={colors.textSecondary} bold style={styles.currency as any}>₹</AppText>
|
||||||
<Text style={styles.bidValueText}>{bidValue}</Text>
|
<AppText variant="h4" color={colors.text} bold style={styles.bidValueText as any}>{bidValue}</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<TouchableOpacity onPress={onIncrease} style={styles.stepBtn}>
|
<TouchableOpacity onPress={onIncrease} style={styles.stepBtn}>
|
||||||
<Text style={styles.stepText}>+</Text>
|
<AppText variant="h3" color="#FFF" bold style={styles.stepText as any}>+</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>}
|
</View>}
|
||||||
{
|
{
|
||||||
(allBoolean.oneTimeBid || allBoolean.rebid) && <View style={styles.inputBox2}>
|
(allBoolean.oneTimeBid || allBoolean.rebid) && <View style={styles.inputBox2}>
|
||||||
<Text style={styles.currency2}>₹</Text>
|
<AppText variant="body" color={colors.textSecondary} bold style={styles.currency2 as any}>₹</AppText>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
value={value}
|
value={value}
|
||||||
onChangeText={handleChange}
|
onChangeText={handleChange}
|
||||||
keyboardType="decimal-pad"
|
keyboardType="decimal-pad"
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
style={styles.input}
|
style={[styles.input, { color: colors.text }]}
|
||||||
placeholderTextColor="#94A3B8"
|
placeholderTextColor={colors.textSecondary}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
@@ -195,7 +198,7 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec
|
|||||||
onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid}
|
onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
allState.isLoading ? <Loader size='small' color={colors.white} /> : <Text style={styles.submitBtnText}>Confirm Bid</Text>
|
allState.isLoading ? <Loader size='small' color="#FFF" /> : <AppText variant="button" color="#FFF" bold style={styles.submitBtnText as any}>Confirm Bid</AppText>
|
||||||
}
|
}
|
||||||
</TouchableOpacity> : null}
|
</TouchableOpacity> : null}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
@@ -210,11 +213,11 @@ const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, Selec
|
|||||||
|
|
||||||
export default memo(BiddingCard);
|
export default memo(BiddingCard);
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const makeStyles = (colors: any) => StyleSheet.create({
|
||||||
inputBox2: {
|
inputBox2: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: '#F1F5F9',
|
backgroundColor: colors.background,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
height: 45,
|
height: 45,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
@@ -222,39 +225,23 @@ const styles = StyleSheet.create({
|
|||||||
marginBottom: 20
|
marginBottom: 20
|
||||||
},
|
},
|
||||||
currency2: {
|
currency2: {
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#64748B',
|
|
||||||
marginRight: 4,
|
marginRight: 4,
|
||||||
},
|
} as any,
|
||||||
input: {
|
input: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: '#0F172A',
|
|
||||||
width: '100%'
|
width: '100%'
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
backgroundColor: '#FFF',
|
|
||||||
borderRadius: 24,
|
borderRadius: 24,
|
||||||
padding: 20,
|
padding: 20,
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#E2E8F0',
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
...Platform.select({
|
|
||||||
ios: {
|
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOpacity: 0.05,
|
|
||||||
shadowRadius: 10,
|
|
||||||
},
|
|
||||||
android: { elevation: 2 },
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
leadingCard: {
|
leadingCard: {
|
||||||
borderColor: '#22C55E',
|
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
backgroundColor: '#F0FDF4',
|
|
||||||
},
|
},
|
||||||
cardHeader: {
|
cardHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -262,27 +249,24 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
},
|
},
|
||||||
headerRight: { flexDirection: 'column', alignItems: 'center', gap: 8 },
|
headerRight: { flexDirection: 'column', alignItems: 'center', gap: 8 },
|
||||||
productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' },
|
productName: {} as any,
|
||||||
itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 },
|
itemIdText: { marginTop: 2 } as any,
|
||||||
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
|
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
|
||||||
liveBadge: { backgroundColor: '#FEE2E2' },
|
liveBadge: { backgroundColor: `${colors.error}20` },
|
||||||
meBadge: { backgroundColor: '#BBF7D0' },
|
meBadge: { backgroundColor: `${colors.success}20` },
|
||||||
badgeText: { fontSize: 10, fontWeight: '900' },
|
badgeText: { fontSize: 10 } as any,
|
||||||
liveText: { color: '#EF4444' },
|
liveText: { color: colors.error },
|
||||||
meText: { color: '#15803D' },
|
meText: { color: colors.success },
|
||||||
arrow: { fontSize: 10, color: '#94A3B8' },
|
arrow: { fontSize: 10 },
|
||||||
priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
|
priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
|
||||||
label: {
|
label: {
|
||||||
fontSize: 11,
|
|
||||||
color: '#94A3B8',
|
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
fontWeight: '600',
|
} as any,
|
||||||
},
|
mainPrice: {} as any,
|
||||||
mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' },
|
incrementText: {} as any,
|
||||||
incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' },
|
|
||||||
collapsibleContainer: { overflow: 'hidden' },
|
collapsibleContainer: { overflow: 'hidden' },
|
||||||
divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 },
|
divider: { height: 1, backgroundColor: colors.border, marginVertical: 10 },
|
||||||
bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 },
|
bidLabel: { marginBottom: 10 } as any,
|
||||||
stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
|
stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
|
||||||
stepBtn: {
|
stepBtn: {
|
||||||
width: 45,
|
width: 45,
|
||||||
@@ -292,19 +276,19 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
disabledBtn: { backgroundColor: '#CBD5E1' },
|
disabledBtn: { backgroundColor: colors.border },
|
||||||
stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' },
|
stepText: {} as any,
|
||||||
inputBox: {
|
inputBox: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
height: 45,
|
height: 45,
|
||||||
backgroundColor: '#F1F5F9',
|
backgroundColor: colors.background,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 },
|
currency: { marginRight: 4 } as any,
|
||||||
bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' },
|
bidValueText: {} as any,
|
||||||
submitBtn: {
|
submitBtn: {
|
||||||
height: 50,
|
height: 50,
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
@@ -312,5 +296,5 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' },
|
submitBtnText: {} as any,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<View style={styles.overlay}>
|
||||||
|
<View style={[styles.modalCard, { backgroundColor: colors.surface, ...shadows.large }]}>
|
||||||
|
|
||||||
|
<TouchableOpacity style={[styles.closeBtn, { backgroundColor: colors.background }]} onPress={onClose} disabled={isLoading}>
|
||||||
|
<X size={18} color={colors.textSecondary} strokeWidth={2.5} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={[styles.iconBox, { backgroundColor: `${colors.primary}15` }]}>
|
||||||
|
<ShieldCheck size={32} color={colors.primary} strokeWidth={2} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<AppText variant="h3" color={colors.text} bold centered style={styles.title}>
|
||||||
|
Confirm Your Bid
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="body" color={colors.textSecondary} centered style={styles.subtitle}>
|
||||||
|
Are you sure you want to place this bid? This action cannot be undone.
|
||||||
|
</AppText>
|
||||||
|
|
||||||
|
<View style={[styles.amountBox, { backgroundColor: `${colors.primary}10`, borderColor: `${colors.primary}30` }]}>
|
||||||
|
<AppText variant="caption" color={colors.primary} bold>
|
||||||
|
BID AMOUNT
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="h1" color={colors.primary} bold>
|
||||||
|
₹{bidAmount?.toLocaleString('en-IN') || 0}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.btnRow}>
|
||||||
|
<TouchableOpacity style={[styles.btn, styles.cancelBtn, { backgroundColor: colors.background }]} onPress={onClose} disabled={isLoading}>
|
||||||
|
<AppText variant="button" color={colors.text} bold>Cancel</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity style={[styles.btn, styles.confirmBtn, { backgroundColor: colors.primary }]} onPress={onConfirm} disabled={isLoading}>
|
||||||
|
{isLoading ? (
|
||||||
|
<ActivityIndicator color="#fff" size="small" />
|
||||||
|
) : (
|
||||||
|
<AppText variant="button" color="#fff" bold>Submit Bid</AppText>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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: {},
|
||||||
|
});
|
||||||
@@ -1,105 +1,209 @@
|
|||||||
import React from 'react';
|
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 LottieView from 'lottie-react-native';
|
||||||
import Animated, { FadeInUp } from 'react-native-reanimated';
|
import Animated, { FadeInDown, FadeInUp, ZoomIn } from 'react-native-reanimated';
|
||||||
import colors from '../../../Constant/Color';
|
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 }) => {
|
const EmdComplete = ({ onNext }: { onNext?: () => void }) => {
|
||||||
return (
|
const { colors, spacing, shadows } = useTheme();
|
||||||
<View style={styles.container}>
|
|
||||||
<LottieView
|
|
||||||
source={require('../../../lottie/confetti.json')}
|
|
||||||
autoPlay
|
|
||||||
loop={true}
|
|
||||||
style={styles.confetti}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.root, { backgroundColor: colors.background }]}>
|
||||||
|
|
||||||
|
|
||||||
|
{/* ── Center content ── */}
|
||||||
|
<View style={styles.centerContent}>
|
||||||
|
|
||||||
|
{/* Success animation bubble */}
|
||||||
|
<Animated.View entering={ZoomIn.delay(100).springify()} style={[styles.lottieBubble, { backgroundColor: `${colors.primary}12` }]}>
|
||||||
<LottieView
|
<LottieView
|
||||||
source={require('../../../lottie/Success.json')}
|
source={require('../../../lottie/Success.json')}
|
||||||
autoPlay
|
autoPlay
|
||||||
loop={true}
|
loop={false}
|
||||||
style={styles.success}
|
style={styles.lottie}
|
||||||
/>
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
<Animated.Text entering={FadeInUp.delay(400).duration(700)} style={styles.title}>
|
{/* Title */}
|
||||||
EMD Complete
|
<Animated.View entering={FadeInUp.delay(400).springify()} style={styles.titleBlock}>
|
||||||
</Animated.Text>
|
<AppText variant="h2" color={colors.text} bold centered>
|
||||||
|
EMD Submitted!
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="body" color={colors.textSecondary} centered style={styles.subtitle as any}>
|
||||||
|
Your Earnest Money Deposit has been{'\n'}successfully recorded.
|
||||||
|
</AppText>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
<Animated.Text entering={FadeInUp.delay(700).duration(700)} style={styles.subText} >
|
{/* ── Step Completion Chips ── */}
|
||||||
Your submission was successful
|
<Animated.View entering={FadeInDown.delay(600).springify()} style={[styles.stepsCard,{backgroundColor: colors.surface, shadowColor: colors.shadow}]}>
|
||||||
</Animated.Text>
|
{STEPS.map((step, index) => (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<View style={styles.stepRow}>
|
||||||
|
<View style={[styles.stepIconBox, { backgroundColor: `${colors.success}18` }]}>
|
||||||
|
<step.icon size={16} color={colors.success} strokeWidth={2} />
|
||||||
|
</View>
|
||||||
|
<AppText variant="bodySmall" color={colors.text} semiBold style={styles.stepLabel as any}>
|
||||||
|
{step.label}
|
||||||
|
</AppText>
|
||||||
|
<CheckCircle2 size={18} color={colors.success} strokeWidth={2.5} />
|
||||||
|
</View>
|
||||||
|
{index < STEPS.length - 1 && (
|
||||||
|
<View style={[styles.stepConnector, { backgroundColor: `${colors.success}30` }]} />
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
<Animated.View entering={FadeInUp.delay(900).duration(700)} style={styles.nextWrapper} >
|
{/* ── Info badge ── */}
|
||||||
<TouchableOpacity activeOpacity={0.85} style={styles.nextBtn} onPress={onNext} >
|
<Animated.View
|
||||||
<Text style={styles.nextText}>Next</Text>
|
entering={FadeInUp.delay(900).springify()}
|
||||||
|
style={[styles.infoBadge, { backgroundColor: `${colors.primary}10`, borderColor: `${colors.primary}30` }]}
|
||||||
|
>
|
||||||
|
<AppText variant="caption" color={colors.primary} semiBold centered>
|
||||||
|
🎉 You are now eligible to participate in the bidding process
|
||||||
|
</AppText>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* ── Bottom CTA ── */}
|
||||||
|
<Animated.View entering={FadeInUp.delay(1100).springify()} style={styles.bottomArea}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.ctaBtn, { backgroundColor: colors.primary, ...shadows.medium }]}
|
||||||
|
onPress={onNext}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
>
|
||||||
|
<AppText variant="button" color="#fff" bold style={styles.ctaText as any}>
|
||||||
|
Continue to Bidding
|
||||||
|
</AppText>
|
||||||
|
<ArrowRight size={18} color="#fff" strokeWidth={2.5} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EmdComplete;
|
export default EmdComplete;
|
||||||
|
|
||||||
/* ---------------- Styles ---------------- */
|
/* ─── Styles ─── */
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
root: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#F8FAFC',
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingVertical: 40,
|
||||||
|
},
|
||||||
|
|
||||||
|
centerContent: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
gap: 24,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Lottie bubble */
|
||||||
|
lottieBubble: {
|
||||||
|
width: 160,
|
||||||
|
height: 160,
|
||||||
|
borderRadius: 80,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
|
lottie: {
|
||||||
confetti: {
|
width: 130,
|
||||||
position: 'absolute',
|
height: 130,
|
||||||
top: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
success: {
|
/* Title */
|
||||||
width: 220,
|
titleBlock: {
|
||||||
height: 220,
|
alignItems: 'center',
|
||||||
marginTop: -100
|
gap: 8,
|
||||||
},
|
},
|
||||||
|
|
||||||
title: {
|
title: {
|
||||||
marginTop: 16,
|
letterSpacing: 0.3,
|
||||||
fontSize: 24,
|
} as any,
|
||||||
fontWeight: '900',
|
subtitle: {
|
||||||
color: '#0F172A',
|
lineHeight: 22,
|
||||||
|
opacity: 0.75,
|
||||||
|
} as any,
|
||||||
|
|
||||||
|
/* 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,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Info badge */
|
||||||
|
infoBadge: {
|
||||||
|
width: '100%',
|
||||||
|
borderRadius: 16,
|
||||||
|
borderWidth: 1.2,
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
lineHeight: 20,
|
||||||
|
} as any,
|
||||||
|
|
||||||
|
/* CTA */
|
||||||
|
bottomArea: {
|
||||||
|
width: '100%',
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
},
|
||||||
|
ctaBtn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
paddingVertical: 16,
|
||||||
|
borderRadius: 18,
|
||||||
|
},
|
||||||
|
ctaText: {
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
},
|
} as any,
|
||||||
|
|
||||||
subText: {
|
|
||||||
marginTop: 8,
|
|
||||||
fontSize: 14,
|
|
||||||
color: '#475569',
|
|
||||||
},
|
|
||||||
|
|
||||||
/* ---------- Floating Next Button ---------- */
|
|
||||||
nextWrapper: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: 20,
|
|
||||||
bottom: 30,
|
|
||||||
},
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<View style={styles.overlay}>
|
||||||
|
<View style={[styles.modalCard, { backgroundColor: colors.surface, ...shadows.large }]}>
|
||||||
|
|
||||||
|
<TouchableOpacity style={[styles.closeBtn, { backgroundColor: colors.background }]} onPress={onClose}>
|
||||||
|
<X size={18} color={colors.textSecondary} strokeWidth={2.5} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={[styles.iconBox, { backgroundColor: `${colors.error}15` }]}>
|
||||||
|
<LogOut size={32} color={colors.error} strokeWidth={2} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<AppText variant="h3" color={colors.text} bold centered style={styles.title as any}>
|
||||||
|
Leave Bidding?
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="body" color={colors.textSecondary} centered style={styles.subtitle as any}>
|
||||||
|
Are you sure you want to go back to the Dashboard? You can always return to this panel later.
|
||||||
|
</AppText>
|
||||||
|
|
||||||
|
<View style={styles.btnRow}>
|
||||||
|
<TouchableOpacity style={[styles.btn, styles.cancelBtn, { backgroundColor: colors.background }]} onPress={onClose}>
|
||||||
|
<AppText variant="button" color={colors.text} bold>Stay</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity style={[styles.btn, styles.confirmBtn, { backgroundColor: colors.error }]} onPress={onConfirm}>
|
||||||
|
<AppText variant="button" color="#fff" bold>Leave</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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: {},
|
||||||
|
});
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import React, { memo, useCallback, useRef, useState } from 'react';
|
import React, { memo, useCallback, useRef, useState } from 'react';
|
||||||
import { View, Text, TouchableOpacity, StyleSheet, Platform, TextInput } from 'react-native';
|
import { View, Text, TouchableOpacity, StyleSheet, Platform, TextInput } from 'react-native';
|
||||||
import Animated, { FadeInUp, Layout, interpolate, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
|
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 AuctionItemDetail from '../../Auction/Modal/AuctionItemDetail';
|
||||||
import MainStyle from '../../../Styles/MainStyle';
|
import MainStyle from '../../../Styles/MainStyle';
|
||||||
|
import { useTheme } from '../../../Hooks/useTheme';
|
||||||
|
import AppText from '../../../components/AppText';
|
||||||
import axios, { all } from 'axios';
|
import axios, { all } from 'axios';
|
||||||
import Variables from '../../../Constant/Variable';
|
import Variables from '../../../Constant/Variable';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
@@ -11,6 +13,8 @@ import { showErrorAlert, showSuccessAlert } from '../../../Constant/ShowDailog';
|
|||||||
import { Loader } from '../../../Component/Index';
|
import { Loader } from '../../../Component/Index';
|
||||||
|
|
||||||
const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, SelectedAuction, allBoolean, setAllBoolean }: any) => {
|
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 Token = useSelector((state: any) => state.token.token)
|
||||||
const [allState, setAllState] = useState({ isLoading: false })
|
const [allState, setAllState] = useState({ isLoading: false })
|
||||||
const [showDetail, setShowDetail] = useState(false);
|
const [showDetail, setShowDetail] = useState(false);
|
||||||
@@ -108,9 +112,10 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid
|
|||||||
entering={FadeInUp.delay(100)}
|
entering={FadeInUp.delay(100)}
|
||||||
style={[
|
style={[
|
||||||
styles.card,
|
styles.card,
|
||||||
|
item.me ? styles.leadingCard : undefined,
|
||||||
{
|
{
|
||||||
borderColor: item.me ? '#22C55E' : '#EF4444',
|
borderColor: item.me ? colors.success : colors.error,
|
||||||
backgroundColor: item.me ? '#F0FDF4' : '#FDF0F0',
|
backgroundColor: item.me ? `${colors.success}10` : `${colors.error}10`,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -121,10 +126,10 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid
|
|||||||
style={styles.cardHeader}
|
style={styles.cardHeader}
|
||||||
>
|
>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<Text style={styles.productName}>{item.productName}</Text>
|
<AppText variant="h4" color={colors.text} bold style={styles.productName as any}>{item.productName}</AppText>
|
||||||
<Text style={styles.itemIdText}>
|
<AppText variant="caption" color={colors.textSecondary} style={styles.itemIdText as any}>
|
||||||
Initial Amt : ₹{item.auctionStartValue}
|
Initial Amt : ₹{item.auctionStartValue}
|
||||||
</Text>
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<TouchableOpacity style={styles.headerRight} onPress={toggleExpand}>
|
<TouchableOpacity style={styles.headerRight} onPress={toggleExpand}>
|
||||||
@@ -135,38 +140,40 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid
|
|||||||
item.me ? styles.meBadge : styles.liveBadge,
|
item.me ? styles.meBadge : styles.liveBadge,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text
|
<AppText
|
||||||
|
variant="caption"
|
||||||
|
bold
|
||||||
style={[
|
style={[
|
||||||
styles.badgeText,
|
styles.badgeText as any,
|
||||||
item.me ? styles.meText : styles.liveText,
|
item.me ? styles.meText : styles.liveText,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{item.me ? 'LEADING' : 'LIVE'}
|
{item.me ? 'LEADING' : 'LIVE'}
|
||||||
</Text>
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Animated.Text style={[styles.arrow, arrowStyle]}>
|
<Animated.Text style={[styles.arrow, { color: colors.textSecondary }, arrowStyle]}>
|
||||||
▼
|
▼
|
||||||
</Animated.Text>
|
</Animated.Text>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.itemIdText}>
|
<AppText variant="caption" color={colors.textSecondary} style={styles.itemIdText as any}>
|
||||||
{SelectedAuction?.auctionNumber}
|
{SelectedAuction?.auctionNumber}
|
||||||
</Text>
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
{/* PRICE INFO */}
|
{/* PRICE INFO */}
|
||||||
<View style={styles.priceRow}>
|
<View style={styles.priceRow}>
|
||||||
<View>
|
<View>
|
||||||
<Text style={styles.label}>Current Bid</Text>
|
<AppText variant="caption" color={colors.textSecondary} semiBold style={styles.label as any}>Current Bid</AppText>
|
||||||
<Text style={styles.mainPrice}>₹{item.bidAmount || item.auctionStartValue}</Text>
|
<AppText variant="h2" color={colors.text} bold style={styles.mainPrice as any}>₹{item.bidAmount || item.auctionStartValue}</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={{ alignItems: 'flex-end' }}>
|
<View style={{ alignItems: 'flex-end' }}>
|
||||||
<Text style={styles.label}>Decrement</Text>
|
<AppText variant="caption" color={colors.textSecondary} semiBold style={styles.label as any}>Decrement</AppText>
|
||||||
<Text style={styles.incrementText}>
|
<AppText variant="body" color={colors.primary} bold style={styles.incrementText as any}>
|
||||||
-₹{item.emdAmount}
|
-₹{item.emdAmount}
|
||||||
</Text>
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -175,7 +182,7 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid
|
|||||||
<Animated.View style={[styles.collapsibleContainer, bodyStyle]}>
|
<Animated.View style={[styles.collapsibleContainer, bodyStyle]}>
|
||||||
<View style={styles.divider} />
|
<View style={styles.divider} />
|
||||||
|
|
||||||
<Text style={styles.bidLabel}>Set Your Bid</Text>
|
<AppText variant="bodySmall" color={colors.textSecondary} semiBold style={styles.bidLabel as any}>Set Your Bid</AppText>
|
||||||
|
|
||||||
<View style={styles.stepperContainer}>
|
<View style={styles.stepperContainer}>
|
||||||
{/* DECREASE */}
|
{/* DECREASE */}
|
||||||
@@ -187,25 +194,25 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid
|
|||||||
!canDecrease && styles.disabledBtn,
|
!canDecrease && styles.disabledBtn,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.stepText}>−</Text>
|
<AppText variant="h3" color="#FFF" bold style={styles.stepText as any}>−</AppText>
|
||||||
</TouchableOpacity>}
|
</TouchableOpacity>}
|
||||||
|
|
||||||
{/* VALUE */}
|
{/* VALUE */}
|
||||||
{!allBoolean.oneTimeBid && <View style={styles.inputBox}>
|
{!allBoolean.oneTimeBid && <View style={styles.inputBox}>
|
||||||
<Text style={styles.currency}>₹</Text>
|
<AppText variant="body" color={colors.textSecondary} bold style={styles.currency as any}>₹</AppText>
|
||||||
<Text style={styles.bidValueText}>{bidValue}</Text>
|
<AppText variant="h4" color={colors.text} bold style={styles.bidValueText as any}>{bidValue}</AppText>
|
||||||
</View>}
|
</View>}
|
||||||
{
|
{
|
||||||
(allBoolean.oneTimeBid || allBoolean.rebid) && <View style={styles.inputBox2}>
|
(allBoolean.oneTimeBid || allBoolean.rebid) && <View style={styles.inputBox2}>
|
||||||
<Text style={styles.currency2}>₹</Text>
|
<AppText variant="body" color={colors.textSecondary} bold style={styles.currency2 as any}>₹</AppText>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
value={value}
|
value={value}
|
||||||
onChangeText={handleChange}
|
onChangeText={handleChange}
|
||||||
keyboardType="decimal-pad"
|
keyboardType="decimal-pad"
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
style={styles.input}
|
style={[styles.input, { color: colors.text }]}
|
||||||
placeholderTextColor="#94A3B8"
|
placeholderTextColor={colors.textSecondary}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
@@ -219,7 +226,7 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid
|
|||||||
!canIncrease && styles.disabledBtn,
|
!canIncrease && styles.disabledBtn,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.stepText}>+</Text>
|
<AppText variant="h3" color="#FFF" bold style={styles.stepText as any}>+</AppText>
|
||||||
</TouchableOpacity>}
|
</TouchableOpacity>}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -228,7 +235,7 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid
|
|||||||
onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid}
|
onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
allState.isLoading ? <Loader size='small' color={colors.white} /> : <Text style={styles.submitBtnText}>Confirm Bid</Text>
|
allState.isLoading ? <Loader size='small' color="#FFF" /> : <AppText variant="button" color="#FFF" bold style={styles.submitBtnText as any}>Confirm Bid</AppText>
|
||||||
}
|
}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
@@ -265,50 +272,34 @@ const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid
|
|||||||
|
|
||||||
export default ReverseBiddingCard;
|
export default ReverseBiddingCard;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const makeStyles = (colors: any) => StyleSheet.create({
|
||||||
inputBox2: {
|
inputBox2: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: '#F1F5F9',
|
backgroundColor: colors.background,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
height: 45,
|
height: 45,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
width: '100%'
|
width: '100%'
|
||||||
},
|
},
|
||||||
currency2: {
|
currency2: {
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#64748B',
|
|
||||||
marginRight: 4,
|
marginRight: 4,
|
||||||
},
|
} as any,
|
||||||
input: {
|
input: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: '#0F172A',
|
|
||||||
width: '100%'
|
width: '100%'
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
backgroundColor: '#FFF',
|
|
||||||
borderRadius: 24,
|
borderRadius: 24,
|
||||||
padding: 20,
|
padding: 20,
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#E2E8F0',
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
...Platform.select({
|
|
||||||
ios: {
|
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOpacity: 0.05,
|
|
||||||
shadowRadius: 10,
|
|
||||||
},
|
|
||||||
android: { elevation: 2 },
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
leadingCard: {
|
leadingCard: {
|
||||||
borderColor: '#22C55E',
|
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
backgroundColor: '#F0FDF4',
|
|
||||||
},
|
},
|
||||||
cardHeader: {
|
cardHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -316,27 +307,24 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
},
|
},
|
||||||
headerRight: { flexDirection: 'column', alignItems: 'center', gap: 8 },
|
headerRight: { flexDirection: 'column', alignItems: 'center', gap: 8 },
|
||||||
productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' },
|
productName: {} as any,
|
||||||
itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 },
|
itemIdText: { marginTop: 2 } as any,
|
||||||
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
|
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
|
||||||
liveBadge: { backgroundColor: '#FEE2E2' },
|
liveBadge: { backgroundColor: `${colors.error}20` },
|
||||||
meBadge: { backgroundColor: '#BBF7D0' },
|
meBadge: { backgroundColor: `${colors.success}20` },
|
||||||
badgeText: { fontSize: 10, fontWeight: '900' },
|
badgeText: { fontSize: 10 } as any,
|
||||||
liveText: { color: '#EF4444' },
|
liveText: { color: colors.error },
|
||||||
meText: { color: '#15803D' },
|
meText: { color: colors.success },
|
||||||
arrow: { fontSize: 10, color: '#94A3B8' },
|
arrow: { fontSize: 10 },
|
||||||
priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
|
priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
|
||||||
label: {
|
label: {
|
||||||
fontSize: 11,
|
|
||||||
color: '#94A3B8',
|
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
fontWeight: '600',
|
} as any,
|
||||||
},
|
mainPrice: {} as any,
|
||||||
mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' },
|
incrementText: {} as any,
|
||||||
incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' },
|
|
||||||
collapsibleContainer: { overflow: 'hidden' },
|
collapsibleContainer: { overflow: 'hidden' },
|
||||||
divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 },
|
divider: { height: 1, backgroundColor: colors.border, marginVertical: 10 },
|
||||||
bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 },
|
bidLabel: { marginBottom: 10 } as any,
|
||||||
stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
|
stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
|
||||||
stepBtn: {
|
stepBtn: {
|
||||||
width: 45,
|
width: 45,
|
||||||
@@ -346,19 +334,19 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
disabledBtn: { backgroundColor: '#CBD5E1' },
|
disabledBtn: { backgroundColor: colors.border },
|
||||||
stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' },
|
stepText: {} as any,
|
||||||
inputBox: {
|
inputBox: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
height: 45,
|
height: 45,
|
||||||
backgroundColor: '#F1F5F9',
|
backgroundColor: colors.background,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 },
|
currency: { marginRight: 4 } as any,
|
||||||
bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' },
|
bidValueText: {} as any,
|
||||||
submitBtn: {
|
submitBtn: {
|
||||||
height: 50,
|
height: 50,
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
@@ -366,5 +354,5 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' },
|
submitBtnText: {} as any,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState, useMemo } from 'react'
|
||||||
import { Modal, View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView } from 'react-native'
|
import { Modal, View, StyleSheet, TextInput, TouchableOpacity, ScrollView, Platform } from 'react-native'
|
||||||
import { Formik } from 'formik'
|
import { Formik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { pick } from '@react-native-documents/picker'
|
import { pick } from '@react-native-documents/picker'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { apiCall } from '../../../Axios/Axios'
|
import { apiCall } from '../../../Axios/Axios'
|
||||||
import { TECHNICAL_DOCUMENT_SUBMIT, TECHNICAL_VALUE_SUBMIT } from '../../../Services/BBPS/ApiUrls'
|
import { TECHNICAL_DOCUMENT_SUBMIT, TECHNICAL_VALUE_SUBMIT } from '../../../Services/ApiCall/ApiUrls'
|
||||||
import { Props } from '../Type/BidType'
|
import { Props } from '../Type/BidType'
|
||||||
import colors from '../../../Constant/Color'
|
|
||||||
import { Loader } from '../../../Component/Index'
|
import { Loader } from '../../../Component/Index'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import NavigationString from '../../../Constant/NavigationString'
|
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<Props> = ({ visible, template, setAllBoolean }) => {
|
const TemplateFormModal: React.FC<Props> = ({ visible, template, setAllBoolean }) => {
|
||||||
if (!template) return null
|
if (!template) return null
|
||||||
|
|
||||||
const SelectedAuction = useSelector((state: any) => state.auction.auction)
|
const SelectedAuction = useSelector((state: any) => state.auction.auction)
|
||||||
const Navigation: any = useNavigation()
|
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 [loading, setLoading] = useState(false)
|
||||||
const initialValues = template?.labels?.reduce((acc: any, label: any) => {
|
const initialValues = template?.labels?.reduce((acc: any, label: any) => {
|
||||||
acc[label.labelName] = ''
|
acc[label.labelName] = ''
|
||||||
@@ -65,10 +69,10 @@ const TemplateFormModal: React.FC<Props> = ({ visible, template, setAllBoolean }
|
|||||||
if (label.labelType === '3') {
|
if (label.labelType === '3') {
|
||||||
return (
|
return (
|
||||||
<View key={label.labelName} style={styles.field}>
|
<View key={label.labelName} style={styles.field}>
|
||||||
<Text style={styles.label}>
|
<AppText variant="caption" color={colors.textSecondary} style={styles.label as any}>
|
||||||
{label.labelName}
|
{label.labelName}
|
||||||
{label.mandatory && <Text style={styles.required}> *</Text>}
|
{label.mandatory && <AppText variant="caption" color={colors.error}> *</AppText>}
|
||||||
</Text>
|
</AppText>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
@@ -79,18 +83,25 @@ const TemplateFormModal: React.FC<Props> = ({ visible, template, setAllBoolean }
|
|||||||
pickFile(label.labelName, setFieldValue)
|
pickFile(label.labelName, setFieldValue)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Text
|
{value?.name ? (
|
||||||
style={[
|
<View style={styles.fileSelectedRow}>
|
||||||
styles.fileText,
|
<FileText size={14} color={colors.success} strokeWidth={2} />
|
||||||
value?.name && styles.fileSelected,
|
<AppText variant="caption" color={colors.text} bold style={{ flex: 1 } as any} numberOfLines={1}>
|
||||||
]}
|
{value.name}
|
||||||
>
|
</AppText>
|
||||||
{value?.name || 'Upload PDF'}
|
</View>
|
||||||
</Text>
|
) : (
|
||||||
|
<View style={styles.fileSelectedRow}>
|
||||||
|
<Upload size={14} color={colors.textSecondary} strokeWidth={2} />
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>
|
||||||
|
Upload PDF
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
{showError && (
|
{showError && (
|
||||||
<Text style={styles.errorText}>{error}</Text>
|
<AppText variant="caption" color={colors.error} style={styles.errorText as any}>{error}</AppText>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@@ -98,10 +109,10 @@ const TemplateFormModal: React.FC<Props> = ({ visible, template, setAllBoolean }
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View key={label.labelName} style={styles.field}>
|
<View key={label.labelName} style={styles.field}>
|
||||||
<Text style={styles.label}>
|
<AppText variant="caption" color={colors.textSecondary} style={styles.label as any}>
|
||||||
{label.labelName}
|
{label.labelName}
|
||||||
{label.mandatory && <Text style={styles.required}> *</Text>}
|
{label.mandatory && <AppText variant="caption" color={colors.error}> *</AppText>}
|
||||||
</Text>
|
</AppText>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[
|
style={[
|
||||||
@@ -109,7 +120,7 @@ const TemplateFormModal: React.FC<Props> = ({ visible, template, setAllBoolean }
|
|||||||
showError && styles.errorBorder,
|
showError && styles.errorBorder,
|
||||||
]}
|
]}
|
||||||
placeholder={`Enter ${label.labelName}`}
|
placeholder={`Enter ${label.labelName}`}
|
||||||
placeholderTextColor="#9CA3AF"
|
placeholderTextColor={colors.textSecondary}
|
||||||
value={value}
|
value={value}
|
||||||
onChangeText={(text) =>
|
onChangeText={(text) =>
|
||||||
setFieldValue(label.labelName, text)
|
setFieldValue(label.labelName, text)
|
||||||
@@ -122,7 +133,7 @@ const TemplateFormModal: React.FC<Props> = ({ visible, template, setAllBoolean }
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{showError && (
|
{showError && (
|
||||||
<Text style={styles.errorText}>{error}</Text>
|
<AppText variant="caption" color={colors.error} style={styles.errorText as any}>{error}</AppText>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@@ -210,26 +221,26 @@ const TemplateFormModal: React.FC<Props> = ({ visible, template, setAllBoolean }
|
|||||||
|
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<View style={styles.headerLeft}>
|
<View style={styles.headerLeft}>
|
||||||
<Text style={styles.headerTitle}>Technical Documents</Text>
|
<AppText variant="h4" color={colors.text}>Technical Documents</AppText>
|
||||||
<Text style={styles.headerSubtitle}>
|
<AppText variant="caption" color={colors.textSecondary}>
|
||||||
Fill the form, Click save when you're done.
|
Fill the form, Click save when you're done.
|
||||||
</Text>
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<TouchableOpacity onPress={onClose} style={styles.closeBtn}>
|
<TouchableOpacity onPress={onClose} style={styles.closeBtn}>
|
||||||
<Text style={styles.closeText}>✕</Text>
|
<X size={20} color={colors.textSecondary} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView contentContainerStyle={styles.content}>
|
<ScrollView contentContainerStyle={styles.content}>
|
||||||
|
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
<Text style={styles.templateHeading}>Template</Text>
|
<AppText variant="h3" color={colors.text} centered style={{ marginBottom: 16 } as any}>Template</AppText>
|
||||||
|
|
||||||
<View style={styles.templateBox}>
|
<View style={styles.templateBox}>
|
||||||
<Text style={styles.templateName}>
|
<AppText variant="bodySmall" color={colors.text} bold style={{ marginBottom: 12 } as any}>
|
||||||
{template.templateName}
|
{template.templateName}
|
||||||
</Text>
|
</AppText>
|
||||||
|
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
@@ -264,12 +275,12 @@ const TemplateFormModal: React.FC<Props> = ({ visible, template, setAllBoolean }
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader
|
<Loader
|
||||||
size="small"
|
size="small"
|
||||||
color={colors.white}
|
color="#fff"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.submitText}>
|
<AppText variant="button" color="#fff" bold style={styles.submitText as any}>
|
||||||
Submit
|
Submit
|
||||||
</Text>
|
</AppText>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -287,10 +298,11 @@ const TemplateFormModal: React.FC<Props> = ({ visible, template, setAllBoolean }
|
|||||||
|
|
||||||
export default TemplateFormModal
|
export default TemplateFormModal
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const makeStyles = (colors: any, spacing: any, typography: any) =>
|
||||||
|
StyleSheet.create({
|
||||||
screen: {
|
screen: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#F9FAFB',
|
backgroundColor: colors.background,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -298,118 +310,86 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: 14,
|
paddingVertical: 14,
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: colors.surface,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderColor: '#E5E7EB',
|
borderColor: colors.border,
|
||||||
},
|
},
|
||||||
headerLeft: {
|
headerLeft: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
headerTitle: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: '700',
|
|
||||||
color: '#111827',
|
|
||||||
},
|
|
||||||
headerSubtitle: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: '#6B7280',
|
|
||||||
marginTop: 2,
|
|
||||||
},
|
|
||||||
closeBtn: {
|
closeBtn: {
|
||||||
padding: 6,
|
padding: 8,
|
||||||
},
|
backgroundColor: colors.overlay,
|
||||||
closeText: {
|
borderRadius: 10,
|
||||||
fontSize: 18,
|
|
||||||
color: '#6B7280',
|
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: colors.surface,
|
||||||
borderColor: '#E5E7EB',
|
borderRadius: 18,
|
||||||
paddingVertical: 20
|
marginTop: spacing.m,
|
||||||
|
padding: spacing.m,
|
||||||
|
...Platform.select({
|
||||||
|
ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.06, shadowRadius: 12 },
|
||||||
|
android: { elevation: 3 },
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
templateHeading: {
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: '700',
|
|
||||||
textAlign: 'center',
|
|
||||||
marginBottom: 16,
|
|
||||||
color: '#111827',
|
|
||||||
},
|
|
||||||
|
|
||||||
templateBox: {
|
templateBox: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#E5E7EB',
|
borderColor: colors.border,
|
||||||
borderRadius: 10,
|
borderRadius: 14,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
},
|
},
|
||||||
templateName: {
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: '700',
|
|
||||||
color: '#111827',
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
field: {
|
field: {
|
||||||
marginBottom: 14,
|
marginBottom: 14,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
fontSize: 13,
|
letterSpacing: 0.5,
|
||||||
fontWeight: '600',
|
textTransform: 'uppercase',
|
||||||
color: '#374151',
|
|
||||||
marginBottom: 6,
|
marginBottom: 6,
|
||||||
},
|
} as any,
|
||||||
required: {
|
|
||||||
color: '#EF4444',
|
|
||||||
},
|
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: colors.background,
|
||||||
borderWidth: 1,
|
borderWidth: 1.2,
|
||||||
borderColor: '#E5E7EB',
|
borderColor: colors.border,
|
||||||
borderRadius: 8,
|
borderRadius: 12,
|
||||||
padding: 12,
|
padding: 12,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: '#111827',
|
color: colors.text,
|
||||||
},
|
},
|
||||||
filePicker: {
|
filePicker: {
|
||||||
borderWidth: 1,
|
borderWidth: 1.2,
|
||||||
borderColor: '#E5E7EB',
|
borderColor: colors.border,
|
||||||
borderRadius: 8,
|
backgroundColor: colors.background,
|
||||||
|
borderRadius: 12,
|
||||||
padding: 12,
|
padding: 12,
|
||||||
},
|
},
|
||||||
fileText: {
|
fileSelectedRow: {
|
||||||
color: '#6B7280',
|
flexDirection: 'row',
|
||||||
fontSize: 14,
|
alignItems: 'center',
|
||||||
},
|
gap: 8,
|
||||||
fileSelected: {
|
|
||||||
color: '#111827',
|
|
||||||
fontWeight: '600',
|
|
||||||
},
|
},
|
||||||
errorText: {
|
errorText: {
|
||||||
color: '#EF4444',
|
|
||||||
fontSize: 12,
|
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
},
|
} as any,
|
||||||
errorBorder: {
|
errorBorder: {
|
||||||
borderColor: '#EF4444',
|
borderColor: colors.error,
|
||||||
},
|
},
|
||||||
submitRow: {
|
submitRow: {
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
alignItems: 'flex-end',
|
alignItems: 'flex-end',
|
||||||
height: 50
|
height: 50,
|
||||||
},
|
},
|
||||||
submitBtn: {
|
submitBtn: {
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
paddingVertical: 10,
|
paddingVertical: 12,
|
||||||
paddingHorizontal: 22,
|
paddingHorizontal: 24,
|
||||||
borderRadius: 8,
|
borderRadius: 14,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
submitText: {
|
submitText: {
|
||||||
color: '#FFFFFF',
|
letterSpacing: 0.5,
|
||||||
fontWeight: '700',
|
} as any,
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +1,74 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
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 LottieView from 'lottie-react-native';
|
||||||
|
import Animated, { FadeInDown } from 'react-native-reanimated';
|
||||||
import { InsideHeader } from '../../Component/Index';
|
import { InsideHeader } from '../../Component/Index';
|
||||||
import colors from '../../Constant/Color';
|
|
||||||
import { useWebSocketService } from '../../socket/Socket';
|
import { useWebSocketService } from '../../socket/Socket';
|
||||||
import Variables from '../../Constant/Variable';
|
import Variables from '../../Constant/Variable';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { apiCall } from '../../Axios/Axios';
|
import { apiCall } from '../../Axios/Axios';
|
||||||
import { AUCTION_ONE_TIME_BID, FORWARD_AUCTION_ITEM, GET_AUCTION_DETAILS, REVERSE_AUCTION_ITEM, SELECTED_AUCTION_ITEM } from '../../Services/BBPS/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 BiddingCard from './Component/BiddingCard';
|
||||||
|
import ConfirmBidModal from './Component/ConfirmBidModal';
|
||||||
|
import GoBackModal from './Component/GoBackModal';
|
||||||
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
|
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||||
import NavigationString from '../../Constant/NavigationString';
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
import CountdownTimer from '../../Component/Counter/CountdownTimer';
|
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 StartBidding = () => {
|
||||||
|
const { colors, spacing, shadows } = useTheme();
|
||||||
const SelectedAuction = useSelector((state: any) => state.auction.auction);
|
const SelectedAuction = useSelector((state: any) => state.auction.auction);
|
||||||
const loginUser = useSelector((state: any) => state.token.token)
|
const loginUser = useSelector((state: any) => state.token.token);
|
||||||
const Navigation: any = useNavigation()
|
const Navigation: any = useNavigation();
|
||||||
|
|
||||||
const [items, setItems] = useState<any>([]);
|
const [items, setItems] = useState<any>([]);
|
||||||
const [bidValues, setBidValues] = useState<Record<number, number>>({});
|
const [bidValues, setBidValues] = useState<Record<number, number>>({});
|
||||||
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(
|
const { subscribe, createRoom, placeBid } = useWebSocketService(
|
||||||
Variables.webSocketUrl,
|
Variables.webSocketUrl,
|
||||||
() => console.log('Connected!'),
|
() => console.log('Connected!'),
|
||||||
(error) => console.log('WebSocket Error:', error),
|
(error: any) => console.log('WebSocket Error:', error),
|
||||||
loginUser.token,
|
loginUser.token,
|
||||||
);
|
);
|
||||||
|
|
||||||
const getAuction = async () => {
|
const getAuction = async () => {
|
||||||
const selectedRes = await apiCall<any>('get', GET_AUCTION_DETAILS, {}, { id: SelectedAuction.auctionId });
|
const selectedRes = await apiCall<any>('get', GET_AUCTION_DETAILS, {}, { id: SelectedAuction.auctionId });
|
||||||
if (selectedRes.statusCode == 200 && selectedRes.data.contractorId.length) {
|
if (selectedRes.statusCode === 200 && selectedRes.data.contractorId.length) {
|
||||||
setAllBoolean((prev: any) => ({ ...prev, oneTimeBid: true }))
|
setAllBoolean(prev => ({ ...prev, oneTimeBid: true }));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getOneTimeBidValue = async () => {
|
const getOneTimeBidValue = async () => {
|
||||||
try {
|
try {
|
||||||
const selectedRes = await apiCall<any>('get', AUCTION_ONE_TIME_BID, {}, { auctionId: SelectedAuction.auctionId });
|
const selectedRes = await apiCall<any>('get', AUCTION_ONE_TIME_BID, {}, { auctionId: SelectedAuction.auctionId });
|
||||||
if (selectedRes.statusCode == 200) {
|
return selectedRes.statusCode === 200 ? selectedRes.data : [];
|
||||||
return selectedRes.data
|
} catch { return []; }
|
||||||
} else {
|
};
|
||||||
return []
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
const getAllAcutionItems = async () => {
|
const getAllAcutionItems = async () => {
|
||||||
try {
|
try {
|
||||||
const payload = { auctionNumber: SelectedAuction.auctionNumber };
|
const payload = { auctionNumber: SelectedAuction.auctionNumber };
|
||||||
@@ -58,87 +80,40 @@ const StartBidding = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedRes = await apiCall<any>('get', SELECTED_AUCTION_ITEM, {}, payload);
|
const selectedRes = await apiCall<any>('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(
|
if (SelectedAuction.auctionCategories === 'Live') {
|
||||||
new Set(selectedRes?.data || [])
|
|
||||||
);
|
|
||||||
|
|
||||||
const filteredItems = auctionItem.filter(item =>
|
|
||||||
uniqueArray.includes(item.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (SelectedAuction.auctionCategories == 'Live') {
|
|
||||||
const result = await createRoom(SelectedAuction.auctionNumber);
|
const result = await createRoom(SelectedAuction.auctionNumber);
|
||||||
|
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, (data: any) => {
|
||||||
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, data => {
|
|
||||||
console.log('🔥 BID UPDATE:', data);
|
console.log('🔥 BID UPDATE:', data);
|
||||||
});
|
});
|
||||||
const bidAmountMap2 = new Map<number, { bidAmount: number; contractorId: number }>();
|
const bidAmountMap2 = new Map<number, { bidAmount: number; contractorId: number }>();
|
||||||
|
|
||||||
(result || []).forEach((r: any) => {
|
(result || []).forEach((r: any) => {
|
||||||
bidAmountMap2.set(r.auctionItemId, {
|
bidAmountMap2.set(r.auctionItemId, { bidAmount: r.bidAmount, contractorId: r.contractorId });
|
||||||
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<number, number>();
|
|
||||||
|
|
||||||
(result || []).forEach((r: any) => {
|
|
||||||
bidAmountMap.set(r.auctionItemId, r.bidAmount);
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedFilteredItems = filteredItems.map(item => {
|
const updatedFilteredItems = filteredItems.map(item => {
|
||||||
const bidData = bidAmountMap2.get(item.id);
|
const bidData = bidAmountMap2.get(item.id);
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
bidAmount: bidData?.bidAmount ?? item.auctionStartValueFigure,
|
bidAmount: bidData?.bidAmount ?? item.auctionStartValueFigure,
|
||||||
bidAvilable: bidData?.bidAmount ?? 0,
|
bidAvilable: bidData?.bidAmount ?? 0,
|
||||||
me: bidData?.contractorId == loginUser.contractorId ? true : false
|
me: bidData?.contractorId === loginUser.contractorId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialBids: Record<number, number> = {};
|
const initialBids: Record<number, number> = {};
|
||||||
updatedFilteredItems.forEach(item => {
|
updatedFilteredItems.forEach(item => { initialBids[item.id] = item.bidAmount; });
|
||||||
initialBids[item.id] = item.bidAmount;
|
|
||||||
});
|
|
||||||
setBidValues(initialBids);
|
setBidValues(initialBids);
|
||||||
setItems(updatedFilteredItems);
|
setItems(updatedFilteredItems);
|
||||||
} else {
|
} else {
|
||||||
let result = []
|
if (SelectedAuction.rebidAllowed) setAllBoolean(prev => ({ ...prev, rebid: true }));
|
||||||
if (SelectedAuction.rebidAllowed) {
|
let result: any[] = [];
|
||||||
setAllBoolean((prev: any) => ({ ...prev, rebid: true }))
|
if (SelectedAuction.auctionCategories !== 'Live') result = await getOneTimeBidValue();
|
||||||
}
|
|
||||||
|
|
||||||
if (SelectedAuction.auctionCategories != 'Live') {
|
|
||||||
result = await getOneTimeBidValue()
|
|
||||||
}
|
|
||||||
const bidMap = new Map<number, any>();
|
const bidMap = new Map<number, any>();
|
||||||
|
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 mergedData = auctionItem.map((item: any) => {
|
||||||
const bid = bidMap.get(item.id);
|
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 {
|
return {
|
||||||
...item,
|
...item,
|
||||||
bidAmount: bid.bidAmount,
|
bidAmount: bid.bidAmount,
|
||||||
@@ -150,56 +125,36 @@ const StartBidding = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
const initialBids: Record<number, number> = {};
|
const initialBids: Record<number, number> = {};
|
||||||
mergedData.forEach(item => {
|
mergedData.forEach(item => { initialBids[item.id] = item.bidAmount; });
|
||||||
initialBids[item.id] = item.bidAmount;
|
|
||||||
});
|
|
||||||
setBidValues(initialBids);
|
|
||||||
setItems(mergedData)
|
|
||||||
setBidValues(initialBids);
|
setBidValues(initialBids);
|
||||||
|
setItems(mergedData);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching auction items:', error);
|
console.error('Error fetching auction items:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, async (message) => {
|
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, async (message: any) => {
|
||||||
// console.log('RAW MESSAGE:', message[message.length - 1]);
|
const result = message[message.length - 1];
|
||||||
const result = message[message.length - 1]
|
setItems((prev: any[]) =>
|
||||||
|
|
||||||
const updatedBidItem = (prev: any[]) =>
|
|
||||||
prev.map(item =>
|
prev.map(item =>
|
||||||
item.id === result.auctionItemId
|
item.id === result.auctionItemId
|
||||||
? { ...item, bidAmount: result.bidAmount, me: result.contractorId == loginUser.contractorId ? true : false }
|
? { ...item, bidAmount: result.bidAmount, me: result.contractorId === loginUser.contractorId }
|
||||||
: item
|
: item,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
setBidValues(prev => ({ ...prev, [result.auctionItemId]: result.bidAmount }));
|
||||||
setItems(updatedBidItem);
|
|
||||||
|
|
||||||
setBidValues(prev => ({
|
|
||||||
...prev,
|
|
||||||
[result.auctionItemId]: result.bidAmount,
|
|
||||||
}));
|
|
||||||
try {
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.log('JSON parse error:', err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (SelectedAuction.auctionCategories == 'One-time') {
|
if (SelectedAuction.auctionCategories === 'One-time') getAuction();
|
||||||
getAuction()
|
|
||||||
}
|
|
||||||
getAllAcutionItems();
|
getAllAcutionItems();
|
||||||
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, (data) => { });
|
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, () => {});
|
||||||
}, [allBoolean.reload]);
|
}, [allBoolean.reload]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initial: Record<number, number> = {};
|
const initial: Record<number, number> = {};
|
||||||
items.forEach((item: any) => {
|
items.forEach((item: any) => { initial[item.id] = item.bidAmount + item.bidVariationValue; });
|
||||||
initial[item.id] = item.bidAmount + item.bidVariationValue;
|
|
||||||
});
|
|
||||||
setBidValues(initial);
|
setBidValues(initial);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -216,51 +171,14 @@ const StartBidding = () => {
|
|||||||
fullName: loginUser.fullName,
|
fullName: loginUser.fullName,
|
||||||
roomId: SelectedAuction.auctionNumber,
|
roomId: SelectedAuction.auctionNumber,
|
||||||
};
|
};
|
||||||
setAllBoolean((prev: any) => ({ ...prev, bidPayload: payload, }));
|
setAllBoolean(prev => ({ ...prev, bidPayload: payload, showDialog: true }));
|
||||||
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 }
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const placeBidSubmit = async (data: any) => {
|
const placeBidSubmit = async (data: any) => {
|
||||||
|
setAllBoolean(prev => ({ ...prev, showDialog: false }));
|
||||||
try {
|
try {
|
||||||
const result: any = await placeBid(data);
|
const result: any = await placeBid(data);
|
||||||
|
if (result?.status && result.data) getAllAcutionItems();
|
||||||
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()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error placing bid:', error);
|
console.error('Error placing bid:', error);
|
||||||
}
|
}
|
||||||
@@ -269,56 +187,75 @@ const StartBidding = () => {
|
|||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
const onBackPress = () => {
|
const onBackPress = () => {
|
||||||
Alert.alert(
|
setAllBoolean(prev => ({ ...prev, showBackDialog: true }));
|
||||||
'Confirm',
|
return true;
|
||||||
'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
|
|
||||||
};
|
};
|
||||||
|
const sub = BackHandler.addEventListener('hardwareBackPress', onBackPress);
|
||||||
const subscription = BackHandler.addEventListener(
|
return () => sub.remove();
|
||||||
'hardwareBackPress',
|
}, [Navigation]),
|
||||||
onBackPress
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => subscription.remove();
|
const styles = makeStyles(colors, spacing, shadows);
|
||||||
}, [Navigation])
|
|
||||||
);
|
const biddingEndDate =
|
||||||
// console.log(bidValues, 'jjfdlsjfldsjfl');
|
SelectedAuction?.auctionStatus === 'Amended'
|
||||||
|
? SelectedAuction?.amendedkeyDates?.auctionBidding?.endDateTime
|
||||||
|
: SelectedAuction?.keyDates?.auctionBidding?.endDateTime;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.root}>
|
||||||
<InsideHeader title="Bidding Panel" showArrow />
|
<InsideHeader title="Bidding Panel" showArrow />
|
||||||
<View style={styles.container2}>
|
|
||||||
<Text style={styles.title}>
|
|
||||||
{SelectedAuction.deptName}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<View style={styles.timerWrapper}>
|
{/* ── Top Info Band ── */}
|
||||||
|
<Animated.View entering={FadeInDown.springify()} style={styles.infoBand}>
|
||||||
|
{/* Department row */}
|
||||||
|
<View style={styles.deptRow}>
|
||||||
|
<View style={[styles.deptIcon, { backgroundColor: `${colors.primary}15` }]}>
|
||||||
|
<Building2 size={16} color={colors.primary} strokeWidth={2} />
|
||||||
|
</View>
|
||||||
|
<AppText variant="bodySmall" color={colors.text} bold numberOfLines={1} style={styles.deptName as any}>
|
||||||
|
{SelectedAuction.deptName ?? 'Auction Department'}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Timer card */}
|
||||||
|
<View style={[styles.timerCard, { backgroundColor: `${colors.primary}10`, borderColor: `${colors.primary}25` }]}>
|
||||||
|
<View style={styles.timerLabelRow}>
|
||||||
|
<Clock4 size={13} color={colors.primary} strokeWidth={2} />
|
||||||
|
<AppText variant="caption" color={colors.primary} bold style={styles.timerLabel as any}>
|
||||||
|
BIDDING ENDS IN
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
<CountdownTimer
|
<CountdownTimer
|
||||||
endDate={SelectedAuction.keyDates.auctionBidding.endDateTime}
|
endDate={biddingEndDate}
|
||||||
myStyle={{ fontSize: 30 }}
|
myStyle={{ fontSize: 28, fontWeight: '900', color: colors.primary, letterSpacing: 1 }}
|
||||||
size={30}
|
size={28}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
|
||||||
|
|
||||||
|
{/* Auction chip */}
|
||||||
|
<View style={styles.chipRow}>
|
||||||
|
<View style={[styles.chip, { backgroundColor: `${colors.primary}12` }]}>
|
||||||
|
<Gavel size={12} color={colors.primary} strokeWidth={2} />
|
||||||
|
<AppText variant="caption" color={colors.primary} bold style={styles.chipText as any}>
|
||||||
|
{SelectedAuction.auctionNumber}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.chip, { backgroundColor: SelectedAuction.auctionCategories === 'Live' ? `${colors.success}15` : `${colors.textSecondary}12` }]}>
|
||||||
|
<View style={[styles.liveDot, { backgroundColor: SelectedAuction.auctionCategories === 'Live' ? colors.success : colors.textSecondary }]} />
|
||||||
|
<AppText
|
||||||
|
variant="caption"
|
||||||
|
color={SelectedAuction.auctionCategories === 'Live' ? colors.success : colors.textSecondary}
|
||||||
|
bold
|
||||||
|
style={styles.chipText as any}
|
||||||
|
>
|
||||||
|
{SelectedAuction.auctionCategories ?? 'Auction'}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
|
{/* ── Items List ── */}
|
||||||
<FlatList
|
<FlatList
|
||||||
data={items}
|
data={items}
|
||||||
keyExtractor={item => item.id.toString()}
|
keyExtractor={item => item.id.toString()}
|
||||||
@@ -328,15 +265,17 @@ const StartBidding = () => {
|
|||||||
bidValue={bidValues[item.id] || 0}
|
bidValue={bidValues[item.id] || 0}
|
||||||
onIncrease={() => handleUpdate(item.id, item.bidVariationValue)}
|
onIncrease={() => handleUpdate(item.id, item.bidVariationValue)}
|
||||||
onDecrease={() => handleUpdate(item.id, -item.bidVariationValue)}
|
onDecrease={() => handleUpdate(item.id, -item.bidVariationValue)}
|
||||||
onPlaceBid={() => { submitBid(item) }}
|
onPlaceBid={() => submitBid(item)}
|
||||||
SelectedAuction={SelectedAuction}
|
SelectedAuction={SelectedAuction}
|
||||||
allBoolean={allBoolean}
|
allBoolean={allBoolean}
|
||||||
setAllBoolean={setAllBoolean}
|
setAllBoolean={setAllBoolean}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
contentContainerStyle={{ padding: 16, paddingBottom: 300 }}
|
contentContainerStyle={styles.listContent}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* ── Success overlay ── */}
|
||||||
{allBoolean.showSuccess && (
|
{allBoolean.showSuccess && (
|
||||||
<View style={styles.overlay}>
|
<View style={styles.overlay}>
|
||||||
<LottieView
|
<LottieView
|
||||||
@@ -347,66 +286,117 @@ const StartBidding = () => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<ConfirmBidModal
|
||||||
|
visible={allBoolean.showDialog}
|
||||||
|
onClose={() => setAllBoolean(prev => ({ ...prev, showDialog: false }))}
|
||||||
|
onConfirm={() => placeBidSubmit(allBoolean.bidPayload)}
|
||||||
|
bidAmount={allBoolean.bidPayload?.bidAmount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GoBackModal
|
||||||
|
visible={allBoolean.showBackDialog}
|
||||||
|
onClose={() => setAllBoolean(prev => ({ ...prev, showBackDialog: false }))}
|
||||||
|
onConfirm={() => {
|
||||||
|
setAllBoolean(prev => ({ ...prev, showBackDialog: false }));
|
||||||
|
Navigation.dispatch(
|
||||||
|
CommonActions.reset({
|
||||||
|
index: 0,
|
||||||
|
routes: [{ name: NavigationString.Home }],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StartBidding;
|
export default StartBidding;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
/* ─── Styles factory ─── */
|
||||||
container: { flex: 1, backgroundColor: colors.white },
|
const makeStyles = (colors: any, spacing: any, shadows: any) =>
|
||||||
container2: {
|
StyleSheet.create({
|
||||||
paddingHorizontal: 16,
|
root: {
|
||||||
paddingTop: 12,
|
flex: 1,
|
||||||
paddingBottom: 8,
|
backgroundColor: colors.background,
|
||||||
},
|
},
|
||||||
title: {
|
|
||||||
fontSize: 18,
|
/* Info band */
|
||||||
fontFamily: FontsFamily.poppinsSemiBold,
|
infoBand: {
|
||||||
color: colors.black,
|
backgroundColor: colors.surface,
|
||||||
marginBottom: 6,
|
paddingHorizontal: spacing.m,
|
||||||
// textAlign:'center'
|
paddingVertical: spacing.m,
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderBottomColor: colors.border,
|
||||||
|
gap: spacing.s,
|
||||||
|
...shadows.light,
|
||||||
},
|
},
|
||||||
timerWrapper: {
|
deptRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
gap: spacing.s,
|
||||||
},
|
},
|
||||||
card: {
|
deptIcon: {
|
||||||
backgroundColor: '#FFF',
|
width: 30,
|
||||||
borderRadius: 24,
|
height: 30,
|
||||||
padding: 20,
|
borderRadius: 9,
|
||||||
marginBottom: 16,
|
alignItems: 'center',
|
||||||
borderWidth: 1,
|
justifyContent: 'center',
|
||||||
borderColor: '#E2E8F0',
|
},
|
||||||
overflow: 'hidden',
|
deptName: {
|
||||||
...Platform.select({ ios: { shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 10 }, android: { elevation: 2 } }),
|
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,
|
||||||
},
|
},
|
||||||
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 },
|
|
||||||
});
|
});
|
||||||
@@ -7,20 +7,25 @@ import { useWebSocketService } from '../../socket/Socket';
|
|||||||
import Variables from '../../Constant/Variable';
|
import Variables from '../../Constant/Variable';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { apiCall } from '../../Axios/Axios';
|
import { apiCall } from '../../Axios/Axios';
|
||||||
import { AUCTION_ONE_TIME_BID, GET_AUCTION_DETAILS, REVERSE_AUCTION_ITEM, SELECTED_AUCTION_ITEM } from '../../Services/BBPS/ApiUrls';
|
import { AUCTION_ONE_TIME_BID, GET_AUCTION_DETAILS, REVERSE_AUCTION_ITEM, SELECTED_AUCTION_ITEM } from '../../Services/ApiCall/ApiUrls';
|
||||||
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
|
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||||
import NavigationString from '../../Constant/NavigationString';
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
import CountdownTimer from '../../Component/Counter/CountdownTimer';
|
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 ReverseBiddingCard from './Component/ReverseBiddingCard';
|
||||||
|
import ConfirmBidModal from './Component/ConfirmBidModal';
|
||||||
|
import GoBackModal from './Component/GoBackModal';
|
||||||
|
|
||||||
const StartReverseBidding = () => {
|
const StartReverseBidding = () => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const styles = React.useMemo(() => makeStyles(colors), [colors]);
|
||||||
const SelectedAuction = useSelector((state: any) => state.auction.auction);
|
const SelectedAuction = useSelector((state: any) => state.auction.auction);
|
||||||
const loginUser = useSelector((state: any) => state.token.token)
|
const loginUser = useSelector((state: any) => state.token.token)
|
||||||
const Navigation: any = useNavigation()
|
const Navigation: any = useNavigation()
|
||||||
const [items, setItems] = useState<any>([]);
|
const [items, setItems] = useState<any>([]);
|
||||||
const [bidValues, setBidValues] = useState<Record<number, number>>({});
|
const [bidValues, setBidValues] = useState<Record<number, number>>({});
|
||||||
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(
|
const { subscribe, createRoom, placeBid } = useWebSocketService(
|
||||||
Variables.webSocketUrl,
|
Variables.webSocketUrl,
|
||||||
() => console.log('Connected!'),
|
() => console.log('Connected!'),
|
||||||
@@ -216,30 +221,13 @@ const StartReverseBidding = () => {
|
|||||||
fullName: loginUser.fullName,
|
fullName: loginUser.fullName,
|
||||||
roomId: SelectedAuction.auctionNumber,
|
roomId: SelectedAuction.auctionNumber,
|
||||||
};
|
};
|
||||||
setAllBoolean((prev: any) => ({ ...prev, bidPayload: payload, }));
|
setAllBoolean((prev: any) => ({ ...prev, bidPayload: payload, showDialog: true }));
|
||||||
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 }
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const placeBidSubmit = async (data: any) => {
|
const placeBidSubmit = async (data: any) => {
|
||||||
|
setAllBoolean((prev: any) => ({ ...prev, showDialog: false }));
|
||||||
try {
|
try {
|
||||||
const result: any = await placeBid(data);
|
const result: any = await placeBid(data);
|
||||||
console.log(result, 'kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk');
|
|
||||||
|
|
||||||
if (result?.status && result.data) {
|
if (result?.status && result.data) {
|
||||||
const updatedBidItem = (prev: any[]) =>
|
const updatedBidItem = (prev: any[]) =>
|
||||||
@@ -259,26 +247,7 @@ const StartReverseBidding = () => {
|
|||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
const onBackPress = () => {
|
const onBackPress = () => {
|
||||||
Alert.alert(
|
setAllBoolean((prev: any) => ({ ...prev, showBackDialog: true }));
|
||||||
'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
|
return true; // block default back action
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -295,9 +264,9 @@ const StartReverseBidding = () => {
|
|||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<InsideHeader title="Bidding Panel" showArrow />
|
<InsideHeader title="Bidding Panel" showArrow />
|
||||||
<View style={styles.container2}>
|
<View style={styles.container2}>
|
||||||
<Text style={styles.title}>
|
<AppText variant="h3" color={colors.text} bold style={styles.title as any}>
|
||||||
{SelectedAuction.deptName}
|
{SelectedAuction.deptName}
|
||||||
</Text>
|
</AppText>
|
||||||
|
|
||||||
<View style={styles.timerWrapper}>
|
<View style={styles.timerWrapper}>
|
||||||
<CountdownTimer
|
<CountdownTimer
|
||||||
@@ -336,66 +305,45 @@ const StartReverseBidding = () => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<ConfirmBidModal
|
||||||
|
visible={allBoolean.showDialog}
|
||||||
|
onClose={() => setAllBoolean(prev => ({ ...prev, showDialog: false }))}
|
||||||
|
onConfirm={() => placeBidSubmit(allBoolean.bidPayload)}
|
||||||
|
bidAmount={allBoolean.bidPayload?.bidAmount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GoBackModal
|
||||||
|
visible={allBoolean.showBackDialog}
|
||||||
|
onClose={() => setAllBoolean(prev => ({ ...prev, showBackDialog: false }))}
|
||||||
|
onConfirm={() => {
|
||||||
|
setAllBoolean(prev => ({ ...prev, showBackDialog: false }));
|
||||||
|
Navigation.dispatch(
|
||||||
|
CommonActions.reset({
|
||||||
|
index: 0,
|
||||||
|
routes: [{ name: NavigationString.Home }],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StartReverseBidding;
|
export default StartReverseBidding;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const makeStyles = (colors: any) => StyleSheet.create({
|
||||||
container: { flex: 1, backgroundColor: colors.white },
|
container: { flex: 1, backgroundColor: colors.background },
|
||||||
container2: {
|
container2: {
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
paddingTop: 12,
|
paddingTop: 12,
|
||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 18,
|
|
||||||
fontFamily: FontsFamily.poppinsSemiBold,
|
|
||||||
color: colors.black,
|
|
||||||
marginBottom: 6,
|
marginBottom: 6,
|
||||||
// textAlign:'center'
|
} as any,
|
||||||
},
|
|
||||||
timerWrapper: {
|
timerWrapper: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
card: {
|
overlay: { ...StyleSheet.absoluteFillObject, backgroundColor: colors.overlay, justifyContent: 'center', alignItems: 'center', zIndex: 99 },
|
||||||
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 },
|
|
||||||
});
|
});
|
||||||
@@ -1,260 +1,252 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { View, Text, StyleSheet, Dimensions, FlatList, ActivityIndicator } from 'react-native';
|
import { View, StyleSheet, Dimensions, ActivityIndicator, ScrollView, TouchableOpacity } from 'react-native';
|
||||||
import Animated, { useSharedValue, useAnimatedStyle, withDelay, withTiming } from 'react-native-reanimated';
|
import Animated, { FadeInDown, FadeInUp } from 'react-native-reanimated';
|
||||||
import { Layers, Clock, ShoppingCart, X, FileText } from 'lucide-react-native';
|
import { Layers, ShoppingCart, X, FileText, Menu, Radio, Clock, ChevronRight } from 'lucide-react-native';
|
||||||
import { Header } from '../../Component/Index';
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
import colors from '../../Constant/Color';
|
import { useDashboard, useTenderDashboard } from '../../Services/ApiCall/Hooks';
|
||||||
import { useDashboard, useESetuAdharVarification } from '../../Services/BBPS/Hooks';
|
|
||||||
import { log } from 'console';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import ConfirmDialog from '../../Constant/Dialog/ConfirmDialog';
|
import { getToken } from '../../Redux/Slices/Token';
|
||||||
|
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||||
|
import AppText from '../../components/AppText';
|
||||||
|
import SidebarModal from '../../components/SidebarModal';
|
||||||
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
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 (
|
|
||||||
<Animated.View style={[styles.card, animatedStyle]}>
|
|
||||||
<View style={styles.header}>
|
|
||||||
<Text style={styles.title} numberOfLines={2}>
|
|
||||||
{item.title}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<View style={[styles.badge, { borderColor: item.color }]}>
|
|
||||||
<Icon size={14} color={item.color} />
|
|
||||||
<Text style={[styles.badgeText, { color: item.color }]}>
|
|
||||||
{item.value}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* VALUE / LOADER */}
|
|
||||||
{isLoading ? (
|
|
||||||
<ActivityIndicator
|
|
||||||
size="small"
|
|
||||||
color={item.color}
|
|
||||||
style={{ marginTop: 12 }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text style={styles.value}>{item.value}</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Text style={styles.desc}>{item.desc}</Text>
|
|
||||||
</Animated.View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const { data, isLoading } = useDashboard();
|
const { colors } = useTheme();
|
||||||
const user = useSelector((state: any) => state.token?.token);
|
|
||||||
console.log("-=--",user)
|
const [auctionStats, setAuctionStats] = useState<any>({});
|
||||||
const { mutateAsync:eSetuAdharVarification } = useESetuAdharVarification();
|
const [tenderStats, setTenderStats] = useState<any>({});
|
||||||
const dashboard = data?.data || {};
|
const [isMenuVisible, setIsMenuVisible] = useState(false);
|
||||||
const [esetu,setEsetu] = useState<boolean>();
|
|
||||||
|
const navigation = useNavigation<any>();
|
||||||
|
const userData = useSelector(getToken);
|
||||||
|
|
||||||
|
// Hooks for data
|
||||||
|
const { data: dashboardData, refetch: refetchDashboard, isPending: isLoadingDashboard } = useDashboard();
|
||||||
|
const { data: tenderData, refetch: refetchTender, isPending: isLoadingTender } = useTenderDashboard();
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
refetchDashboard();
|
||||||
|
refetchTender();
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(user?.esetuVerified === 'Not Verified'){
|
if (dashboardData) {
|
||||||
const payLoad= {
|
setAuctionStats(dashboardData.data || {});
|
||||||
contId: user,
|
|
||||||
transactionId: "string",
|
|
||||||
udf1: "string",
|
|
||||||
udf2: "string",
|
|
||||||
udf3: "string",
|
|
||||||
udf4: "string",
|
|
||||||
responseUrl: "string"
|
|
||||||
}
|
}
|
||||||
setEsetu(true)
|
if (tenderData) {
|
||||||
eSetuAdharVarification(payLoad, {
|
setTenderStats(tenderData.data || {});
|
||||||
onSuccess: (res) => {
|
|
||||||
console.log(res)
|
|
||||||
}, onError: (err) => {
|
|
||||||
console.log(err, '-----auction item error');
|
|
||||||
}
|
}
|
||||||
});
|
}, [dashboardData, tenderData]);
|
||||||
}else{
|
|
||||||
setEsetu(false)
|
|
||||||
}
|
|
||||||
},[user])
|
|
||||||
|
|
||||||
const DASHBOARD_CARDS = [
|
const renderGlassCard = (title: string, value: string | number, icon: React.ReactNode, index: number) => (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeInUp.duration(600).delay(index * 50)}
|
||||||
|
style={[
|
||||||
|
styles.glassCard,
|
||||||
|
styles.halfWidthCard,
|
||||||
{
|
{
|
||||||
title: 'Total Auctions',
|
backgroundColor: colors.glass,
|
||||||
value: dashboard.myAuctionCount ?? 0,
|
borderColor: colors.overlay,
|
||||||
desc: 'Auctions purchased by you',
|
}
|
||||||
icon: Layers,
|
]}
|
||||||
color: '#2563EB',
|
>
|
||||||
},
|
<View style={styles.cardHeader}>
|
||||||
{
|
<View style={[styles.iconContainer, { backgroundColor: colors.overlay }]}>
|
||||||
title: 'Live Auctions',
|
{icon}
|
||||||
value: dashboard.liveAuctionCount ?? 0,
|
</View>
|
||||||
desc: 'Currently live',
|
<AppText variant="h3" color={colors.primary}>{value || '0'}</AppText>
|
||||||
icon: Clock,
|
</View>
|
||||||
color: '#0284C7',
|
<View style={styles.cardContent}>
|
||||||
},
|
<AppText variant="caption" color={colors.textSecondary} numberOfLines={1}>{title}</AppText>
|
||||||
{
|
</View>
|
||||||
title: 'Participated Auctions',
|
</Animated.View>
|
||||||
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',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: 'Total Tenders',
|
|
||||||
value: 0,
|
|
||||||
desc: 'Tenders assigned',
|
|
||||||
icon: FileText,
|
|
||||||
color: '#16A34A',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Live Tenders',
|
|
||||||
value: 0,
|
|
||||||
desc: 'Currently live',
|
|
||||||
icon: Clock,
|
|
||||||
color: '#059669',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Participated Tenders',
|
|
||||||
value: 0,
|
|
||||||
desc: 'Participated in tender',
|
|
||||||
icon: ShoppingCart,
|
|
||||||
color: '#0EA5E9',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Cancelled Tenders',
|
|
||||||
value: 0,
|
|
||||||
desc: 'Cancelled by authority',
|
|
||||||
icon: X,
|
|
||||||
color: '#EF4444',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.screen}>
|
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||||
<Header title="Dashboard" />
|
|
||||||
<ConfirmDialog
|
|
||||||
visible={esetu as boolean}
|
|
||||||
title="Aadhaar Verification Required"
|
|
||||||
message="To access features, please complete your Aadhaar verification."
|
|
||||||
confirmText="Proceed"
|
|
||||||
cancelText="Cancel"
|
|
||||||
onConfirm={() => {
|
|
||||||
setEsetu(false);
|
|
||||||
|
|
||||||
}}
|
<ScrollView contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false}>
|
||||||
onClose={() => setEsetu(false)}
|
|
||||||
/>
|
{/* Top Bar */}
|
||||||
<FlatList
|
<Animated.View entering={FadeInDown.duration(600)} style={styles.headerTop}>
|
||||||
data={DASHBOARD_CARDS}
|
<View style={styles.userInfo}>
|
||||||
keyExtractor={(_, index) => index.toString()}
|
<View style={[styles.avatarContainer, { backgroundColor: colors.primary }]}>
|
||||||
numColumns={2}
|
<AppText variant="h3" color="#FFF">{userData?.firstName?.charAt(0) || 'U'}</AppText>
|
||||||
columnWrapperStyle={styles.row}
|
</View>
|
||||||
contentContainerStyle={styles.list}
|
<View>
|
||||||
renderItem={({ item, index }) => (
|
<AppText variant="caption" color={colors.textSecondary}>Welcome,</AppText>
|
||||||
<StatCard
|
<AppText variant="body" color={colors.text} style={{ fontWeight: '600' }}>{userData?.firstName || 'User'}</AppText>
|
||||||
item={item}
|
</View>
|
||||||
index={index}
|
</View>
|
||||||
isLoading={isLoading}
|
<View style={styles.headerActions}>
|
||||||
/>
|
<TouchableOpacity onPress={() => setIsMenuVisible(true)}>
|
||||||
|
<Menu size={24} color={colors.text} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
|
<SidebarModal isVisible={isMenuVisible} onClose={() => setIsMenuVisible(false)} />
|
||||||
|
|
||||||
|
{isLoadingDashboard || isLoadingTender ? (
|
||||||
|
<ActivityIndicator size="large" color={colors.primary} style={{ marginTop: 40 }} />
|
||||||
|
) : (
|
||||||
|
<View style={styles.sectionsWrapper}>
|
||||||
|
|
||||||
|
{/* Auction Section */}
|
||||||
|
<View style={styles.sectionContainer}>
|
||||||
|
<View style={styles.sectionHeader}>
|
||||||
|
<AppText variant="body" color={colors.text} style={{ fontWeight: 'bold' }}>Auctions</AppText>
|
||||||
|
<TouchableOpacity style={styles.viewAllBtn} onPress={() => navigation.navigate(NavigationString.Auction)}>
|
||||||
|
<AppText variant="caption" color={colors.primary}>Explore</AppText>
|
||||||
|
<ChevronRight size={14} color={colors.primary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.gridContainer}>
|
||||||
|
{renderGlassCard('Live Auctions', auctionStats?.liveAuctionCount, <Radio size={18} color={colors.primary} />, 0)}
|
||||||
|
{renderGlassCard('Purchased', auctionStats?.myAuctionCount, <ShoppingCart size={18} color={colors.primary} />, 1)}
|
||||||
|
{renderGlassCard('Participated', auctionStats?.currentBiddingCount, <FileText size={18} color={colors.primary} />, 2)}
|
||||||
|
{renderGlassCard('Cancelled', auctionStats?.cancelledAuctionCount, <X size={18} color={colors.primary} />, 3)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Tender Section */}
|
||||||
|
<View style={styles.sectionContainer}>
|
||||||
|
<View style={styles.sectionHeader}>
|
||||||
|
<AppText variant="body" color={colors.text} style={{ fontWeight: 'bold' }}>Tenders</AppText>
|
||||||
|
<TouchableOpacity style={styles.viewAllBtn} onPress={() => navigation.navigate(NavigationString.Tender)}>
|
||||||
|
<AppText variant="caption" color={colors.primary}>Explore</AppText>
|
||||||
|
<ChevronRight size={14} color={colors.primary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.gridContainer}>
|
||||||
|
{renderGlassCard('Total Tenders', tenderStats[0]?.totalTender, <Layers size={18} color={colors.primary} />, 4)}
|
||||||
|
{renderGlassCard('Purchased', tenderStats[0]?.totalPurchased, <ShoppingCart size={18} color={colors.primary} />, 5)}
|
||||||
|
{renderGlassCard('Participated', tenderStats[0]?.totalParticipate, <FileText size={18} color={colors.primary} />, 6)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
/>
|
{/* Recent Activity */}
|
||||||
|
<View style={styles.sectionContainer}>
|
||||||
|
<AppText variant="body" color={colors.text} style={{ fontWeight: 'bold', marginBottom: 10 }}>Recent Activity</AppText>
|
||||||
|
<View style={[styles.activityCard, { backgroundColor: colors.glass, borderColor: colors.overlay }]}>
|
||||||
|
<View style={[styles.activityIcon, { backgroundColor: colors.overlay }]}>
|
||||||
|
<Clock size={20} color={colors.primary} />
|
||||||
|
</View>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<AppText variant="bodySmall" color={colors.text}>No recent activity</AppText>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>Your latest bids will appear here</AppText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
</View >
|
</View >
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
||||||
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
screen: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: colors.white,
|
|
||||||
},
|
},
|
||||||
|
scrollContent: {
|
||||||
list: {
|
paddingTop: 40,
|
||||||
padding: 16,
|
paddingHorizontal: 16,
|
||||||
paddingBottom: 24,
|
paddingBottom: 90,
|
||||||
},
|
},
|
||||||
|
headerTop: {
|
||||||
row: {
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
|
|
||||||
card: {
|
|
||||||
width: CARD_WIDTH,
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
borderRadius: 16,
|
|
||||||
padding: 16,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#E5E7EB',
|
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOpacity: 0.05,
|
|
||||||
shadowRadius: 8,
|
|
||||||
elevation: 3,
|
|
||||||
},
|
|
||||||
|
|
||||||
header: {
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'center',
|
||||||
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
|
userInfo: {
|
||||||
title: {
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: '600',
|
|
||||||
color: '#374151',
|
|
||||||
flex: 1,
|
|
||||||
paddingRight: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
badge: {
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 6,
|
},
|
||||||
|
avatarContainer: {
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
borderRadius: 18,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: 10,
|
||||||
|
},
|
||||||
|
headerActions: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
sectionsWrapper: {
|
||||||
|
marginTop: 0,
|
||||||
|
},
|
||||||
|
sectionContainer: {
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
viewAllBtn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
gridContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
glassCard: {
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 16,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderRadius: 20,
|
marginBottom: 12,
|
||||||
paddingHorizontal: 8,
|
flexDirection: 'column',
|
||||||
paddingVertical: 4,
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
|
halfWidthCard: {
|
||||||
badgeText: {
|
width: (width - 42) / 2,
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: '700',
|
|
||||||
},
|
},
|
||||||
|
cardHeader: {
|
||||||
value: {
|
flexDirection: 'row',
|
||||||
fontSize: 28,
|
justifyContent: 'space-between',
|
||||||
fontWeight: '800',
|
alignItems: 'center',
|
||||||
color: '#111827',
|
|
||||||
marginTop: 5,
|
|
||||||
},
|
},
|
||||||
|
iconContainer: {
|
||||||
desc: {
|
width: 32,
|
||||||
fontSize: 12,
|
height: 32,
|
||||||
color: '#6B7280',
|
borderRadius: 8,
|
||||||
marginTop: 6,
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
cardContent: {
|
||||||
|
marginTop: 8,
|
||||||
|
},
|
||||||
|
activityCard: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
activityIcon: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 12,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: 12,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { View, ActivityIndicator, StyleSheet, Text } from "react-native";
|
||||||
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
|
import { WebView } from "react-native-webview";
|
||||||
|
import { CommonActions } from "@react-navigation/native";
|
||||||
|
import colors from "../../Constant/Color";
|
||||||
|
import { BASE_URL } from "../../Services/ApiCall/ApiUrls";
|
||||||
|
|
||||||
|
const SUCCESS_DELAY_MS = 5000; // 5 seconds
|
||||||
|
|
||||||
|
export const KycWebViewScreen = ({ route, navigation }: any) => {
|
||||||
|
const webRef = useRef<WebView>(null);
|
||||||
|
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||||
|
const hasCompletedRef = useRef(false);
|
||||||
|
const successTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
|
const data = route.params ?? {};
|
||||||
|
const html = data.res.data;
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
|
const [loadError, setLoadError] = useState<boolean>(false);
|
||||||
|
const [showSuccessMessage, setShowSuccessMessage] = useState<boolean>(false);
|
||||||
|
const [countdown, setCountdown] = useState<number>(5);
|
||||||
|
const url = false;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (pollTimerRef.current) {
|
||||||
|
clearInterval(pollTimerRef.current);
|
||||||
|
}
|
||||||
|
if (successTimeoutRef.current) {
|
||||||
|
clearTimeout(successTimeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleNavigationBack = () => {
|
||||||
|
if (data.returnRoute) {
|
||||||
|
navigation.replace(data.returnRoute, data.returnParams);
|
||||||
|
} else {
|
||||||
|
navigation.dispatch(
|
||||||
|
CommonActions.reset({
|
||||||
|
index: 0,
|
||||||
|
routes: [{ name: 'Login' }],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startSuccessCountdown = () => {
|
||||||
|
setShowSuccessMessage(true);
|
||||||
|
setCountdown(5);
|
||||||
|
|
||||||
|
let remainingSeconds = 5;
|
||||||
|
const countdownInterval = setInterval(() => {
|
||||||
|
remainingSeconds -= 1;
|
||||||
|
setCountdown(remainingSeconds);
|
||||||
|
|
||||||
|
if (remainingSeconds <= 0) {
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
successTimeoutRef.current = setTimeout(() => {
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
handleNavigationBack();
|
||||||
|
}, SUCCESS_DELAY_MS);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoadStart = (event: any) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setLoadError(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoadEnd = (event: any) => {
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (event: any) => {
|
||||||
|
setIsLoading(false);
|
||||||
|
setLoadError(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNavigationStateChange = (navState: any) => {
|
||||||
|
if (
|
||||||
|
navState.title &&
|
||||||
|
navState.title.includes('E-KYC SETU - Verification Success') &&
|
||||||
|
!hasCompletedRef.current
|
||||||
|
) {
|
||||||
|
hasCompletedRef.current = true;
|
||||||
|
startSuccessCountdown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const LoadingIndicator = () => (
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<ActivityIndicator size="large" color={colors.primary || '#2563EB'} />
|
||||||
|
<Text style={styles.loadingText}>Loading KYC form...</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={{ flex: 1, backgroundColor: colors.white }}>
|
||||||
|
{url ? (
|
||||||
|
<WebView
|
||||||
|
ref={webRef}
|
||||||
|
source={{
|
||||||
|
uri: url,
|
||||||
|
baseUrl: "http://192.168.1.15:8080/",
|
||||||
|
}}
|
||||||
|
javaScriptEnabled
|
||||||
|
domStorageEnabled
|
||||||
|
startInLoadingState
|
||||||
|
originWhitelist={["*"]}
|
||||||
|
setSupportMultipleWindows={false}
|
||||||
|
onLoadStart={handleLoadStart}
|
||||||
|
onLoadEnd={handleLoadEnd}
|
||||||
|
onError={handleError}
|
||||||
|
onNavigationStateChange={handleNavigationStateChange}
|
||||||
|
renderLoading={() => <LoadingIndicator />}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<WebView
|
||||||
|
ref={webRef}
|
||||||
|
source={{
|
||||||
|
html: html ?? "<h3>No content</h3>",
|
||||||
|
// baseUrl: `${BASE_URL}ESetuKYC/initiate/`,
|
||||||
|
}}
|
||||||
|
javaScriptEnabled
|
||||||
|
domStorageEnabled
|
||||||
|
startInLoadingState
|
||||||
|
onLoadStart={handleLoadStart}
|
||||||
|
onLoadEnd={handleLoadEnd}
|
||||||
|
onError={handleError}
|
||||||
|
|
||||||
|
onNavigationStateChange={(navState) => {
|
||||||
|
console.log("Current -- URL:", navState.url);
|
||||||
|
handleNavigationStateChange(navState);
|
||||||
|
}}
|
||||||
|
|
||||||
|
onShouldStartLoadWithRequest={(request) => {
|
||||||
|
console.log("Request URL:", request.url);
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
|
||||||
|
renderLoading={() => <LoadingIndicator />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLoading && !showSuccessMessage && <LoadingIndicator />}
|
||||||
|
|
||||||
|
{showSuccessMessage && (
|
||||||
|
<View style={styles.successContainer}>
|
||||||
|
<View style={styles.successCard}>
|
||||||
|
<Text style={styles.successIcon}>✅</Text>
|
||||||
|
<Text style={styles.successTitle}>Verification Successful!</Text>
|
||||||
|
<Text style={styles.successMessage}>
|
||||||
|
Your KYC verification has been completed successfully.
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.countdownText}>
|
||||||
|
Redirecting in {countdown} seconds...
|
||||||
|
</Text>
|
||||||
|
<ActivityIndicator
|
||||||
|
size="small"
|
||||||
|
color={colors.primary || '#2563EB'}
|
||||||
|
style={{ marginTop: 16 }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loadError && (
|
||||||
|
<View style={styles.errorContainer}>
|
||||||
|
<Text style={styles.errorText}>Failed to load content</Text>
|
||||||
|
<Text style={styles.errorSubText}>Please try again</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KycWebViewScreen;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
loadingContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.white,
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
loadingText: {
|
||||||
|
marginTop: 12,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#374151',
|
||||||
|
},
|
||||||
|
successContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
zIndex: 2,
|
||||||
|
padding: 24,
|
||||||
|
},
|
||||||
|
successCard: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: 20,
|
||||||
|
padding: 32,
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 12,
|
||||||
|
elevation: 8,
|
||||||
|
maxWidth: 340,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
successIcon: {
|
||||||
|
fontSize: 64,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
successTitle: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#16A34A',
|
||||||
|
marginBottom: 12,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
successMessage: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#374151',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: 20,
|
||||||
|
lineHeight: 24,
|
||||||
|
},
|
||||||
|
countdownText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#2563EB',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
errorContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.white,
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#DC2626',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
errorSubText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#6B7280',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -15,6 +15,9 @@ import {
|
|||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import colors from '../../Constant/Color';
|
import colors from '../../Constant/Color';
|
||||||
|
import { showToast } from '../../Constant/ShowDailog';
|
||||||
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
|
import { BASE_URL } from '../../Services/ApiCall/ApiUrls';
|
||||||
|
|
||||||
interface ForgotModalProps {
|
interface ForgotModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -28,7 +31,7 @@ const ForgotModal: React.FC<ForgotModalProps> = ({
|
|||||||
visible,
|
visible,
|
||||||
onClose
|
onClose
|
||||||
}) => {
|
}) => {
|
||||||
const navigation = useNavigation() as Navigation;
|
const Navigation = useNavigation<any>();
|
||||||
const [email, setEmail] = useState<string>('');
|
const [email, setEmail] = useState<string>('');
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<string>('');
|
const [error, setError] = useState<string>('');
|
||||||
@@ -56,7 +59,7 @@ const ForgotModal: React.FC<ForgotModalProps> = ({
|
|||||||
try {
|
try {
|
||||||
// Make API call to send OTP for forgot password
|
// Make API call to send OTP for forgot password
|
||||||
const response = await fetch(
|
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',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -67,20 +70,10 @@ const ForgotModal: React.FC<ForgotModalProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
Alert.alert(
|
showToast('OTP has been sent to your email address. Please check your inbox.')
|
||||||
'Success',
|
|
||||||
'OTP has been sent to your email address. Please check your inbox.',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: 'OK',
|
|
||||||
onPress: () => {
|
|
||||||
onClose();
|
onClose();
|
||||||
navigation.navigate('ResetPassword', { email });
|
Navigation.navigate(NavigationString.ResetPassword, { email });
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
setEmail('');
|
|
||||||
} else {
|
} else {
|
||||||
const errorData = await response.text();
|
const errorData = await response.text();
|
||||||
console.error('API Error:', errorData);
|
console.error('API Error:', errorData);
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ import {
|
|||||||
TextInputKeyPressEventData,
|
TextInputKeyPressEventData,
|
||||||
ToastAndroid,
|
ToastAndroid,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { BASE_URL } from '../../Services/BBPS/ApiUrls';
|
import { Eye, EyeOff } from 'lucide-react-native';
|
||||||
|
import { BASE_URL } from '../../Services/ApiCall/ApiUrls';
|
||||||
import colors from '../../Constant/Color';
|
import colors from '../../Constant/Color';
|
||||||
|
|
||||||
interface ResetPasswordProps {
|
interface ResetPasswordProps {
|
||||||
route?: any;
|
route?: any;
|
||||||
navigation?: any;
|
navigation?: any;
|
||||||
@@ -45,12 +47,10 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
const confirmPasswordRef = useRef<TextInput>(null);
|
const confirmPasswordRef = useRef<TextInput>(null);
|
||||||
const resendTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const resendTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
// Initialize resend cooldown from storage or set to 0
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkResendCooldown();
|
checkResendCooldown();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Clean up timer on unmount
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (resendTimerRef.current) {
|
if (resendTimerRef.current) {
|
||||||
@@ -59,7 +59,6 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Update timer every second
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (resendCooldown > 0) {
|
if (resendCooldown > 0) {
|
||||||
resendTimerRef.current = setInterval(() => {
|
resendTimerRef.current = setInterval(() => {
|
||||||
@@ -98,13 +97,12 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const startResendCooldown = async () => {
|
const startResendCooldown = async () => {
|
||||||
const cooldownTime = 300; // 5 minutes in seconds
|
const cooldownTime = 300;
|
||||||
setResendCooldown(cooldownTime);
|
setResendCooldown(cooldownTime);
|
||||||
await AsyncStorage.setItem('otpResendTime', Date.now().toString());
|
await AsyncStorage.setItem('otpResendTime', Date.now().toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Auto-submit OTP when all fields are filled
|
|
||||||
const fullOtp = otp.join('');
|
const fullOtp = otp.join('');
|
||||||
if (fullOtp.length === 6 && !isOtpVerified) {
|
if (fullOtp.length === 6 && !isOtpVerified) {
|
||||||
handleSubmitOtp();
|
handleSubmitOtp();
|
||||||
@@ -112,14 +110,13 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
}, [otp]);
|
}, [otp]);
|
||||||
|
|
||||||
const handleOtpChange = (value: string, index: number) => {
|
const handleOtpChange = (value: string, index: number) => {
|
||||||
if (!/^\d*$/.test(value)) return; // Only allow numbers
|
if (!/^\d*$/.test(value)) return;
|
||||||
|
|
||||||
const newOtp = [...otp];
|
const newOtp = [...otp];
|
||||||
newOtp[index] = value;
|
newOtp[index] = value;
|
||||||
setOtp(newOtp);
|
setOtp(newOtp);
|
||||||
setOtpError('');
|
setOtpError('');
|
||||||
|
|
||||||
// Auto-focus to next input
|
|
||||||
if (value && index < 5) {
|
if (value && index < 5) {
|
||||||
otpInputs.current[index + 1]?.focus();
|
otpInputs.current[index + 1]?.focus();
|
||||||
}
|
}
|
||||||
@@ -127,7 +124,6 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
|
|
||||||
const handleOtpKeyPress = (e: NativeSyntheticEvent<TextInputKeyPressEventData>, index: number) => {
|
const handleOtpKeyPress = (e: NativeSyntheticEvent<TextInputKeyPressEventData>, index: number) => {
|
||||||
if (e.nativeEvent.key === 'Backspace' && !otp[index] && index > 0) {
|
if (e.nativeEvent.key === 'Backspace' && !otp[index] && index > 0) {
|
||||||
// Move focus to previous input on backspace
|
|
||||||
otpInputs.current[index - 1]?.focus();
|
otpInputs.current[index - 1]?.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -149,7 +145,6 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
setOtpError('');
|
setOtpError('');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Make the API request to verify OTP
|
|
||||||
const res = await axios.post(
|
const res = await axios.post(
|
||||||
`${BASE_URL}eproc/verify-otp?email=${encodeURIComponent(email)}&otp=${fullOtp}`,
|
`${BASE_URL}eproc/verify-otp?email=${encodeURIComponent(email)}&otp=${fullOtp}`,
|
||||||
null,
|
null,
|
||||||
@@ -167,7 +162,6 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
ToastAndroid.show('OTP verified successfully!', ToastAndroid.SHORT);
|
ToastAndroid.show('OTP verified successfully!', ToastAndroid.SHORT);
|
||||||
setIsOtpVerified(true);
|
setIsOtpVerified(true);
|
||||||
|
|
||||||
// Focus on password field after successful OTP verification
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
newPasswordRef.current?.focus();
|
newPasswordRef.current?.focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -193,19 +187,16 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
const validateForm = (): boolean => {
|
const validateForm = (): boolean => {
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
|
|
||||||
// Validate OTP
|
|
||||||
if (!isOtpVerified) {
|
if (!isOtpVerified) {
|
||||||
setOtpError('Please verify OTP first');
|
setOtpError('Please verify OTP first');
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate password
|
|
||||||
if (newPassword.length < 8) {
|
if (newPassword.length < 8) {
|
||||||
setPasswordError('Password must be at least 8 characters long');
|
setPasswordError('Password must be at least 8 characters long');
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate confirm password
|
|
||||||
if (newPassword !== confirmPassword) {
|
if (newPassword !== confirmPassword) {
|
||||||
setConfirmPasswordError('Passwords do not match');
|
setConfirmPasswordError('Passwords do not match');
|
||||||
isValid = false;
|
isValid = false;
|
||||||
@@ -225,7 +216,6 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Make API call to reset password according to curl
|
|
||||||
const res = await axios.post(
|
const res = await axios.post(
|
||||||
`${BASE_URL}eproc/reset-password?email=${encodeURIComponent(email)}&otp=${otp.join('')}&newPassword=${encodeURIComponent(newPassword)}&confirmPassword=${encodeURIComponent(confirmPassword)}`,
|
`${BASE_URL}eproc/reset-password?email=${encodeURIComponent(email)}&otp=${otp.join('')}&newPassword=${encodeURIComponent(newPassword)}&confirmPassword=${encodeURIComponent(confirmPassword)}`,
|
||||||
null,
|
null,
|
||||||
@@ -245,7 +235,6 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
'Your password has been reset successfully.',
|
'Your password has been reset successfully.',
|
||||||
[{
|
[{
|
||||||
text: 'OK', onPress: () => {
|
text: 'OK', onPress: () => {
|
||||||
// Navigate to login screen
|
|
||||||
navigation.navigate('Login');
|
navigation.navigate('Login');
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
@@ -273,7 +262,6 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
setResendLoading(true);
|
setResendLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Make API call to resend OTP
|
|
||||||
const res = await axios.post(
|
const res = await axios.post(
|
||||||
`${BASE_URL}eproc/send-otp-forgot-password?email=${encodeURIComponent(email)}`,
|
`${BASE_URL}eproc/send-otp-forgot-password?email=${encodeURIComponent(email)}`,
|
||||||
null,
|
null,
|
||||||
@@ -286,11 +274,9 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
|
|
||||||
if (res.data?.statusCode === 200) {
|
if (res.data?.statusCode === 200) {
|
||||||
ToastAndroid.show('OTP sent successfully', ToastAndroid.SHORT);
|
ToastAndroid.show('OTP sent successfully', ToastAndroid.SHORT);
|
||||||
// Clear OTP fields
|
|
||||||
setOtp(['', '', '', '', '', '']);
|
setOtp(['', '', '', '', '', '']);
|
||||||
setIsOtpVerified(false);
|
setIsOtpVerified(false);
|
||||||
setOtpError('');
|
setOtpError('');
|
||||||
// Start the cooldown timer
|
|
||||||
startResendCooldown();
|
startResendCooldown();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.data?.message || 'Failed to resend OTP');
|
throw new Error(res.data?.message || 'Failed to resend OTP');
|
||||||
@@ -325,7 +311,6 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
Enter the OTP sent to {email} and your new password
|
Enter the OTP sent to {email} and your new password
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* OTP Input Section */}
|
|
||||||
<View style={styles.otpSection}>
|
<View style={styles.otpSection}>
|
||||||
<Text style={styles.sectionLabel}>OTP Code</Text>
|
<Text style={styles.sectionLabel}>OTP Code</Text>
|
||||||
<View style={styles.otpContainer}>
|
<View style={styles.otpContainer}>
|
||||||
@@ -363,10 +348,8 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Password Input Section - Only enabled after OTP verification */}
|
|
||||||
<View style={styles.passwordSection}>
|
<View style={styles.passwordSection}>
|
||||||
<Text style={styles.sectionLabel}>New Password</Text>
|
<Text style={styles.sectionLabel}>New Password</Text>
|
||||||
<View style={styles.passwordInputContainer}>
|
<View style={styles.passwordInputContainer}>
|
||||||
@@ -374,7 +357,7 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
ref={newPasswordRef}
|
ref={newPasswordRef}
|
||||||
style={[styles.input, passwordError ? styles.inputError : null, !isOtpVerified && styles.disabledInput]}
|
style={[styles.input, passwordError ? styles.inputError : null, !isOtpVerified && styles.disabledInput]}
|
||||||
placeholder="Enter new password"
|
placeholder="Enter new password"
|
||||||
placeholderTextColor={!isOtpVerified ? '#4f058bff' : '#000'}
|
placeholderTextColor={!isOtpVerified ? colors.lightText : colors.black}
|
||||||
value={newPassword}
|
value={newPassword}
|
||||||
onChangeText={(text) => {
|
onChangeText={(text) => {
|
||||||
setNewPassword(text);
|
setNewPassword(text);
|
||||||
@@ -390,9 +373,11 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
style={styles.eyeIcon}
|
style={styles.eyeIcon}
|
||||||
onPress={() => setNewPasswordVisible(!newPasswordVisible)}
|
onPress={() => setNewPasswordVisible(!newPasswordVisible)}
|
||||||
>
|
>
|
||||||
<Text style={styles.eyeIconText}>
|
{newPasswordVisible ? (
|
||||||
{newPasswordVisible ? '🙈' : '👁️'}
|
<EyeOff size={20} color={colors.primary} />
|
||||||
</Text>
|
) : (
|
||||||
|
<Eye size={20} color={colors.primary} />
|
||||||
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -404,7 +389,7 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
ref={confirmPasswordRef}
|
ref={confirmPasswordRef}
|
||||||
style={[styles.input, confirmPasswordError ? styles.inputError : null, !isOtpVerified && styles.disabledInput]}
|
style={[styles.input, confirmPasswordError ? styles.inputError : null, !isOtpVerified && styles.disabledInput]}
|
||||||
placeholder="Confirm new password"
|
placeholder="Confirm new password"
|
||||||
placeholderTextColor={!isOtpVerified ? '#4f058bff' : '#000'}
|
placeholderTextColor={!isOtpVerified ? colors.lightText : colors.black}
|
||||||
value={confirmPassword}
|
value={confirmPassword}
|
||||||
onChangeText={(text) => {
|
onChangeText={(text) => {
|
||||||
setConfirmPassword(text);
|
setConfirmPassword(text);
|
||||||
@@ -420,9 +405,11 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
style={styles.eyeIcon}
|
style={styles.eyeIcon}
|
||||||
onPress={() => setConfirmPasswordVisible(!confirmPasswordVisible)}
|
onPress={() => setConfirmPasswordVisible(!confirmPasswordVisible)}
|
||||||
>
|
>
|
||||||
<Text style={styles.eyeIconText}>
|
{confirmPasswordVisible ? (
|
||||||
{confirmPasswordVisible ? '🙈' : '👁️'}
|
<EyeOff size={20} color={colors.primary} />
|
||||||
</Text>
|
) : (
|
||||||
|
<Eye size={20} color={colors.primary} />
|
||||||
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -445,7 +432,7 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity
|
{!isOtpVerified && <TouchableOpacity
|
||||||
style={[styles.resendButton, resendCooldown > 0 && styles.disabledResendButton]}
|
style={[styles.resendButton, resendCooldown > 0 && styles.disabledResendButton]}
|
||||||
onPress={handleResendOtp}
|
onPress={handleResendOtp}
|
||||||
disabled={resendLoading || resendCooldown > 0}
|
disabled={resendLoading || resendCooldown > 0}
|
||||||
@@ -459,7 +446,7 @@ const ResetPassword: React.FC<ResetPasswordProps> = ({ route, navigation }) => {
|
|||||||
) : (
|
) : (
|
||||||
<Text style={styles.resendButtonText}>Resend OTP</Text>
|
<Text style={styles.resendButtonText}>Resend OTP</Text>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
);
|
);
|
||||||
@@ -550,12 +537,9 @@ const styles = StyleSheet.create({
|
|||||||
eyeIcon: {
|
eyeIcon: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 15,
|
right: 15,
|
||||||
top: 13,
|
top: 15,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
},
|
},
|
||||||
eyeIconText: {
|
|
||||||
fontSize: 20,
|
|
||||||
},
|
|
||||||
errorText: {
|
errorText: {
|
||||||
color: '#ff3b30',
|
color: '#ff3b30',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
@@ -15,8 +14,11 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
Alert,
|
Alert,
|
||||||
|
StatusBar,
|
||||||
|
Image,
|
||||||
} from 'react-native';
|
} 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 {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
UserCircle,
|
UserCircle,
|
||||||
@@ -51,10 +53,18 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
X,
|
X,
|
||||||
Save,
|
Save,
|
||||||
|
ChevronRight,
|
||||||
|
LogOut
|
||||||
} from 'lucide-react-native';
|
} from 'lucide-react-native';
|
||||||
|
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||||
import { useContractorProfile, useUpdateUserProfile } from '../../Services/BBPS/Hooks';
|
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');
|
const { width } = Dimensions.get('window');
|
||||||
|
|
||||||
@@ -102,10 +112,12 @@ interface ContractorData {
|
|||||||
|
|
||||||
interface ProfileHeaderProps {
|
interface ProfileHeaderProps {
|
||||||
data: ContractorData;
|
data: ContractorData;
|
||||||
|
displayName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QuickStatsProps {
|
interface QuickStatsProps {
|
||||||
data: ContractorData;
|
data: ContractorData;
|
||||||
|
onAadhaarPress: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InfoSectionProps {
|
interface InfoSectionProps {
|
||||||
@@ -129,6 +141,7 @@ interface StatItem {
|
|||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
onPress?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateProfilePayload {
|
interface UpdateProfilePayload {
|
||||||
@@ -150,16 +163,68 @@ const isValidValue = (value: any): boolean => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to check if section has any valid data
|
||||||
|
const hasSectionData = (values: any[]): boolean => {
|
||||||
|
return values.some(value => isValidValue(value));
|
||||||
|
};
|
||||||
|
|
||||||
// Main Component
|
// Main Component
|
||||||
const ProfileTab: React.FC = () => {
|
const ProfileTab: React.FC = () => {
|
||||||
|
const { colors, spacing, shadows, isDarkMode } = useTheme();
|
||||||
|
const styles = useMemo(() => createStyles(colors), [colors]);
|
||||||
const { data, refetch, isLoading, error } = useContractorProfile();
|
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 [editModalVisible, setEditModalVisible] = useState(false);
|
||||||
|
const [showDialog, setShowDialog] = useState<boolean>(false);
|
||||||
|
const [isNavigating, setIsNavigating] = useState<boolean>(false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleAadhaarPress = () => {
|
||||||
|
if (user?.esetuVerified === 'Not Verified' || user?.esetuVerified === null) {
|
||||||
|
setShowDialog(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useFocusEffect(() => {
|
||||||
|
setIsNavigating(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleProceedVerification = async () => {
|
||||||
|
try {
|
||||||
|
setIsNavigating(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);
|
||||||
|
|
||||||
|
setShowDialog(false);
|
||||||
|
|
||||||
|
Navigation.navigate(NavigationString.KycWebViewScreen, { res });
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Alert.alert('Error', 'Failed to initiate verification. Please try again.');
|
||||||
|
setIsNavigating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.centerContainer}>
|
<View style={styles.centerContainer}>
|
||||||
<ActivityIndicator size="large" color={colors.primary} />
|
<ActivityIndicator size="large" color={colors.primary} />
|
||||||
<Text style={styles.loadingText}>Loading profile...</Text>
|
<AppText style={styles.loadingText}>Loading profile...</AppText>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -168,9 +233,9 @@ const ProfileTab: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.centerContainer}>
|
<View style={styles.centerContainer}>
|
||||||
<AlertCircle size={64} color={colors.error} />
|
<AlertCircle size={64} color={colors.error} />
|
||||||
<Text style={styles.errorText}>{error}</Text>
|
<AppText style={styles.screenErrorText}>{error instanceof Error ? error.message : String(error)}</AppText>
|
||||||
<TouchableOpacity style={styles.retryButton} onPress={refetch}>
|
<TouchableOpacity style={styles.retryButton} onPress={() => refetch()}>
|
||||||
<Text style={styles.retryButtonText}>Retry</Text>
|
<AppText style={styles.retryButtonText}>Retry</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -181,7 +246,7 @@ const ProfileTab: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.container}
|
style={[styles.container, { backgroundColor: colors.background }]}
|
||||||
contentContainerStyle={styles.contentContainer}
|
contentContainerStyle={styles.contentContainer}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
<RefreshControl
|
<RefreshControl
|
||||||
@@ -192,19 +257,14 @@ const ProfileTab: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
removeClippedSubviews={true}
|
|
||||||
maxToRenderPerBatch={10}
|
|
||||||
updateCellsBatchingPeriod={50}
|
|
||||||
initialNumToRender={8}
|
|
||||||
windowSize={10}
|
|
||||||
>
|
>
|
||||||
{/* Header with Gradient */}
|
{/* Header with Gradient */}
|
||||||
<ProfileHeader data={data} />
|
<ProfileHeader data={data} displayName={data?.fullName} />
|
||||||
|
|
||||||
{/* Quick Stats */}
|
{/* Quick Stats */}
|
||||||
<QuickStats data={data} />
|
<QuickStats data={data} onAadhaarPress={handleAadhaarPress} />
|
||||||
|
|
||||||
{/* Personal Information */}
|
{/* Personal Information - Always show */}
|
||||||
<InfoSection
|
<InfoSection
|
||||||
title="Personal Information"
|
title="Personal Information"
|
||||||
IconComponent={UserCircle}
|
IconComponent={UserCircle}
|
||||||
@@ -212,7 +272,11 @@ const ProfileTab: React.FC = () => {
|
|||||||
showEdit={true}
|
showEdit={true}
|
||||||
onEdit={() => setEditModalVisible(true)}
|
onEdit={() => setEditModalVisible(true)}
|
||||||
>
|
>
|
||||||
<InfoRow label="Full Name" value={data.firstName+" "+data.middleName+" "+data.lastName} IconComponent={User} />
|
<InfoRow
|
||||||
|
label="Full Name"
|
||||||
|
value={`${data.firstName} ${data.middleName} ${data.lastName}`.trim()}
|
||||||
|
IconComponent={User}
|
||||||
|
/>
|
||||||
<InfoRow label="Email" value={data.emailId} IconComponent={Mail} />
|
<InfoRow label="Email" value={data.emailId} IconComponent={Mail} />
|
||||||
<InfoRow
|
<InfoRow
|
||||||
label="Mobile"
|
label="Mobile"
|
||||||
@@ -222,7 +286,14 @@ const ProfileTab: React.FC = () => {
|
|||||||
<InfoRow label="Designation" value={data.designation} IconComponent={Briefcase} />
|
<InfoRow label="Designation" value={data.designation} IconComponent={Briefcase} />
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
|
|
||||||
{/* Company Details */}
|
{/* Company Details - Only show if has data */}
|
||||||
|
{hasSectionData([
|
||||||
|
data.company,
|
||||||
|
data.contractorType,
|
||||||
|
data.contOrganization,
|
||||||
|
data.agentWebsite,
|
||||||
|
data.agentRegno
|
||||||
|
]) && (
|
||||||
<InfoSection title="Company Details" IconComponent={Building2} delay={0}>
|
<InfoSection title="Company Details" IconComponent={Building2} delay={0}>
|
||||||
<InfoRow label="Company" value={data.company} IconComponent={Building} />
|
<InfoRow label="Company" value={data.company} IconComponent={Building} />
|
||||||
<InfoRow label="Type" value={data.contractorType} IconComponent={Tag} />
|
<InfoRow label="Type" value={data.contractorType} IconComponent={Tag} />
|
||||||
@@ -230,16 +301,31 @@ const ProfileTab: React.FC = () => {
|
|||||||
<InfoRow label="Website" value={data.agentWebsite} IconComponent={Globe} />
|
<InfoRow label="Website" value={data.agentWebsite} IconComponent={Globe} />
|
||||||
<InfoRow label="Registration No" value={data.agentRegno} IconComponent={Award} />
|
<InfoRow label="Registration No" value={data.agentRegno} IconComponent={Award} />
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Address Information */}
|
{/* Address Information - Only show if has data */}
|
||||||
|
{hasSectionData([
|
||||||
|
data.agentAddress,
|
||||||
|
data.agentCity,
|
||||||
|
data.agentState,
|
||||||
|
data.agentPincode
|
||||||
|
]) && (
|
||||||
<InfoSection title="Address Information" IconComponent={MapPin} delay={0}>
|
<InfoSection title="Address Information" IconComponent={MapPin} delay={0}>
|
||||||
<InfoRow label="Address" value={data.agentAddress} IconComponent={Home} />
|
<InfoRow label="Address" value={data.agentAddress} IconComponent={Home} />
|
||||||
<InfoRow label="City" value={data.agentCity} IconComponent={Building2} />
|
<InfoRow label="City" value={data.agentCity} IconComponent={Building2} />
|
||||||
<InfoRow label="State" value={data.agentState} IconComponent={Map} />
|
<InfoRow label="State" value={data.agentState} IconComponent={Map} />
|
||||||
<InfoRow label="Pincode" value={data.agentPincode} IconComponent={Mailbox} />
|
<InfoRow label="Pincode" value={data.agentPincode} IconComponent={Mailbox} />
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Tax & Legal */}
|
{/* Tax & Legal - Only show if has data */}
|
||||||
|
{hasSectionData([
|
||||||
|
data.orgPAN,
|
||||||
|
data.orgPANname,
|
||||||
|
data.orgPANdob,
|
||||||
|
data.gistnNo,
|
||||||
|
data.comAadharNo
|
||||||
|
]) && (
|
||||||
<InfoSection title="Tax & Legal Information" IconComponent={FileText} delay={0}>
|
<InfoSection title="Tax & Legal Information" IconComponent={FileText} delay={0}>
|
||||||
<InfoRow label="PAN Number" value={data.orgPAN} IconComponent={CreditCard} />
|
<InfoRow label="PAN Number" value={data.orgPAN} IconComponent={CreditCard} />
|
||||||
<InfoRow label="PAN Name" value={data.orgPANname} IconComponent={Type} />
|
<InfoRow label="PAN Name" value={data.orgPANname} IconComponent={Type} />
|
||||||
@@ -247,8 +333,15 @@ const ProfileTab: React.FC = () => {
|
|||||||
<InfoRow label="GSTIN" value={data.gistnNo} IconComponent={Receipt} />
|
<InfoRow label="GSTIN" value={data.gistnNo} IconComponent={Receipt} />
|
||||||
<InfoRow label="Aadhar" value={data.comAadharNo} IconComponent={FileText} />
|
<InfoRow label="Aadhar" value={data.comAadharNo} IconComponent={FileText} />
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Registration Status */}
|
{/* Registration Status - Only show if has data */}
|
||||||
|
{hasSectionData([
|
||||||
|
data.contractorClass,
|
||||||
|
data.socialCategory,
|
||||||
|
data.msmeStatus,
|
||||||
|
data.expiryDate
|
||||||
|
]) && (
|
||||||
<InfoSection title="Registration Details" IconComponent={ClipboardCheck} delay={0}>
|
<InfoSection title="Registration Details" IconComponent={ClipboardCheck} delay={0}>
|
||||||
<InfoRow label="Class" value={data.contractorClass} IconComponent={Star} />
|
<InfoRow label="Class" value={data.contractorClass} IconComponent={Star} />
|
||||||
<InfoRow label="Category" value={data.socialCategory} IconComponent={List} />
|
<InfoRow label="Category" value={data.socialCategory} IconComponent={List} />
|
||||||
@@ -257,9 +350,10 @@ const ProfileTab: React.FC = () => {
|
|||||||
label="Expiry Date"
|
label="Expiry Date"
|
||||||
value={data.expiryDate}
|
value={data.expiryDate}
|
||||||
IconComponent={CalendarClock}
|
IconComponent={CalendarClock}
|
||||||
valueColor={colors.warning}
|
valueColor={colors.text}
|
||||||
/>
|
/>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
<View style={{ height: 40 }} />
|
<View style={{ height: 40 }} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -271,6 +365,20 @@ const ProfileTab: React.FC = () => {
|
|||||||
data={data}
|
data={data}
|
||||||
onSuccess={refetch}
|
onSuccess={refetch}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Aadhaar Verification Dialog */}
|
||||||
|
<ConfirmDialog
|
||||||
|
visible={showDialog}
|
||||||
|
title="Aadhaar Verification Required"
|
||||||
|
message="To access all features, please complete your Aadhaar verification."
|
||||||
|
confirmText={isPending || isNavigating ? "Processing..." : "Proceed"}
|
||||||
|
cancelText="Cancel"
|
||||||
|
onConfirm={handleProceedVerification}
|
||||||
|
onClose={() => !isPending && !isNavigating && setShowDialog(false)}
|
||||||
|
loading={isPending || isNavigating}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -289,6 +397,8 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
|||||||
data,
|
data,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const styles = useMemo(() => createStyles(colors), [colors]);
|
||||||
const [formData, setFormData] = useState<UpdateProfilePayload>({
|
const [formData, setFormData] = useState<UpdateProfilePayload>({
|
||||||
firstName: data.firstName || '',
|
firstName: data.firstName || '',
|
||||||
middleName: data.middleName || '',
|
middleName: data.middleName || '',
|
||||||
@@ -302,7 +412,6 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
|||||||
|
|
||||||
const updateProfileMutation = useUpdateUserProfile();
|
const updateProfileMutation = useUpdateUserProfile();
|
||||||
|
|
||||||
// Reset form when modal opens
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
setFormData({
|
setFormData({
|
||||||
@@ -340,7 +449,6 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
|||||||
newErrors.mobileNo = 'Invalid mobile number';
|
newErrors.mobileNo = 'Invalid mobile number';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-generate fullname
|
|
||||||
const generatedFullName = `${formData.firstName} ${formData.middleName} ${formData.lastName}`.trim();
|
const generatedFullName = `${formData.firstName} ${formData.middleName} ${formData.lastName}`.trim();
|
||||||
setFormData(prev => ({ ...prev, fullname: generatedFullName }));
|
setFormData(prev => ({ ...prev, fullname: generatedFullName }));
|
||||||
|
|
||||||
@@ -354,7 +462,7 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateProfileMutation.mutateAsync(formData);
|
const result = await updateProfileMutation.mutateAsync(formData);
|
||||||
Alert.alert('Success', 'Profile updated successfully');
|
Alert.alert('Success', 'Profile updated successfully');
|
||||||
onSuccess();
|
onSuccess();
|
||||||
onClose();
|
onClose();
|
||||||
@@ -374,102 +482,94 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
|||||||
style={styles.modalOverlay}
|
style={styles.modalOverlay}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
>
|
>
|
||||||
<View style={styles.modalContent}>
|
<View style={[styles.modalContent, { backgroundColor: colors.background }]}>
|
||||||
{/* Header */}
|
|
||||||
<View style={styles.modalHeader}>
|
<View style={styles.modalHeader}>
|
||||||
<Text style={styles.modalTitle}>Edit Personal Information</Text>
|
<AppText variant="h3" style={styles.modalTitle}>Edit Personal Information</AppText>
|
||||||
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
||||||
<X size={24} color={colors.darkText} />
|
<X size={24} color={colors.text} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Form */}
|
|
||||||
<ScrollView style={styles.formContainer} showsVerticalScrollIndicator={false}>
|
<ScrollView style={styles.formContainer} showsVerticalScrollIndicator={false}>
|
||||||
{/* First Name */}
|
|
||||||
<View style={styles.inputGroup}>
|
<View style={styles.inputGroup}>
|
||||||
<Text style={styles.inputLabel}>First Name *</Text>
|
<AppText variant="bodySmall" style={styles.inputLabel}>First Name *</AppText>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, errors.firstName && styles.inputError]}
|
style={[styles.input, errors.firstName && styles.inputError]}
|
||||||
value={formData.firstName}
|
value={formData.firstName}
|
||||||
onChangeText={(text) => setFormData({ ...formData, firstName: text })}
|
onChangeText={(text) => setFormData({ ...formData, firstName: text })}
|
||||||
placeholder="Enter first name"
|
placeholder="Enter first name"
|
||||||
placeholderTextColor={colors.grayText}
|
placeholderTextColor={colors.textSecondary}
|
||||||
/>
|
/>
|
||||||
{errors.firstName && (
|
{errors.firstName && (
|
||||||
<Text style={styles.errorText}>{errors.firstName}</Text>
|
<AppText variant="caption" style={styles.errorText}>{errors.firstName}</AppText>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Middle Name */}
|
|
||||||
<View style={styles.inputGroup}>
|
<View style={styles.inputGroup}>
|
||||||
<Text style={styles.inputLabel}>Middle Name</Text>
|
<AppText variant="bodySmall" style={styles.inputLabel}>Middle Name</AppText>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
value={formData.middleName}
|
value={formData.middleName}
|
||||||
onChangeText={(text) => setFormData({ ...formData, middleName: text })}
|
onChangeText={(text) => setFormData({ ...formData, middleName: text })}
|
||||||
placeholder="Enter middle name"
|
placeholder="Enter middle name"
|
||||||
placeholderTextColor={colors.grayText}
|
placeholderTextColor={colors.textSecondary}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Last Name */}
|
|
||||||
<View style={styles.inputGroup}>
|
<View style={styles.inputGroup}>
|
||||||
<Text style={styles.inputLabel}>Last Name *</Text>
|
<AppText variant="bodySmall" style={styles.inputLabel}>Last Name *</AppText>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, errors.lastName && styles.inputError]}
|
style={[styles.input, errors.lastName && styles.inputError]}
|
||||||
value={formData.lastName}
|
value={formData.lastName}
|
||||||
onChangeText={(text) => setFormData({ ...formData, lastName: text })}
|
onChangeText={(text) => setFormData({ ...formData, lastName: text })}
|
||||||
placeholder="Enter last name"
|
placeholder="Enter last name"
|
||||||
placeholderTextColor={colors.grayText}
|
placeholderTextColor={colors.textSecondary}
|
||||||
/>
|
/>
|
||||||
{errors.lastName && (
|
{errors.lastName && (
|
||||||
<Text style={styles.errorText}>{errors.lastName}</Text>
|
<AppText variant="caption" style={styles.errorText}>{errors.lastName}</AppText>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Email */}
|
|
||||||
<View style={styles.inputGroup}>
|
<View style={styles.inputGroup}>
|
||||||
<Text style={styles.inputLabel}>Email *</Text>
|
<AppText variant="bodySmall" style={styles.inputLabel}>Email *</AppText>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, errors.emailId && styles.inputError]}
|
style={[styles.input, errors.emailId && styles.inputError]}
|
||||||
value={formData.emailId}
|
value={formData.emailId}
|
||||||
onChangeText={(text) => setFormData({ ...formData, emailId: text })}
|
onChangeText={(text) => setFormData({ ...formData, emailId: text })}
|
||||||
placeholder="Enter email"
|
placeholder="Enter email"
|
||||||
placeholderTextColor={colors.grayText}
|
placeholderTextColor={colors.textSecondary}
|
||||||
keyboardType="email-address"
|
keyboardType="email-address"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
/>
|
/>
|
||||||
{errors.emailId && (
|
{errors.emailId && (
|
||||||
<Text style={styles.errorText}>{errors.emailId}</Text>
|
<AppText variant="caption" style={styles.errorText}>{errors.emailId}</AppText>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Mobile Number */}
|
|
||||||
<View style={styles.inputGroup}>
|
<View style={styles.inputGroup}>
|
||||||
<Text style={styles.inputLabel}>Mobile Number *</Text>
|
<AppText variant="bodySmall" style={styles.inputLabel}>Mobile Number *</AppText>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, errors.mobileNo && styles.inputError]}
|
style={[styles.input, errors.mobileNo && styles.inputError]}
|
||||||
value={formData.mobileNo}
|
value={formData.mobileNo}
|
||||||
onChangeText={(text) => setFormData({ ...formData, mobileNo: text })}
|
onChangeText={(text) => setFormData({ ...formData, mobileNo: text })}
|
||||||
placeholder="Enter mobile number"
|
placeholder="Enter mobile number"
|
||||||
placeholderTextColor={colors.grayText}
|
placeholderTextColor={colors.textSecondary}
|
||||||
keyboardType="phone-pad"
|
keyboardType="phone-pad"
|
||||||
maxLength={10}
|
maxLength={10}
|
||||||
/>
|
/>
|
||||||
{errors.mobileNo && (
|
{errors.mobileNo && (
|
||||||
<Text style={styles.errorText}>{errors.mobileNo}</Text>
|
<AppText variant="caption" style={styles.errorText}>{errors.mobileNo}</AppText>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
{/* Footer Buttons */}
|
|
||||||
<View style={styles.modalFooter}>
|
<View style={styles.modalFooter}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.button, styles.cancelButton]}
|
style={[styles.button, styles.cancelButton]}
|
||||||
onPress={onClose}
|
onPress={onClose}
|
||||||
disabled={updateProfileMutation.isPending}
|
disabled={updateProfileMutation.isPending}
|
||||||
>
|
>
|
||||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
<AppText variant="button" style={styles.cancelButtonText}>Cancel</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -478,11 +578,11 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
|||||||
disabled={updateProfileMutation.isPending}
|
disabled={updateProfileMutation.isPending}
|
||||||
>
|
>
|
||||||
{updateProfileMutation.isPending ? (
|
{updateProfileMutation.isPending ? (
|
||||||
<ActivityIndicator size="small" color={colors.white} />
|
<ActivityIndicator size="small" color={colors.primary} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Save size={18} color={colors.white} />
|
<Save size={18} color={colors.buttonText} />
|
||||||
<Text style={styles.saveButtonText}>Save</Text>
|
<AppText variant="button" style={styles.saveButtonText}>Save</AppText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -494,7 +594,10 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Profile Header Component - OPTIMIZED
|
// Profile Header Component - OPTIMIZED
|
||||||
const ProfileHeader: React.FC<ProfileHeaderProps> = React.memo(({ data }) => {
|
const ProfileHeader: React.FC<ProfileHeaderProps> = React.memo(({ data, displayName }) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const styles = useMemo(() => createStyles(colors), [colors]);
|
||||||
|
|
||||||
const getInitials = (name: string): string => {
|
const getInitials = (name: string): string => {
|
||||||
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
||||||
const fallbackName = `${data.firstName || ''} ${data.lastName || ''}`.trim();
|
const fallbackName = `${data.firstName || ''} ${data.lastName || ''}`.trim();
|
||||||
@@ -528,198 +631,148 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = React.memo(({ data }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<LinearGradient
|
<View
|
||||||
colors={[colors.primary, colors.primaryLight]}
|
|
||||||
start={{ x: 0, y: 0 }}
|
|
||||||
end={{ x: 1, y: 1 }}
|
|
||||||
style={styles.headerGradient}
|
style={styles.headerGradient}
|
||||||
>
|
>
|
||||||
<View style={styles.avatarContainer}>
|
<View style={styles.avatarContainer}>
|
||||||
<View style={styles.avatar}>
|
<View style={styles.avatar}>
|
||||||
<Text style={styles.avatarText}>{getInitials(data.fullName)}</Text>
|
<AppText variant="h1" color={colors.text} style={styles.avatarText}>{getInitials(displayName)}</AppText>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.statusBadge, { backgroundColor: getStatusColor() }]}>
|
<View style={[styles.statusBadge, { backgroundColor: getStatusColor() }]}>
|
||||||
<View style={styles.statusDot} />
|
<View style={styles.statusDot} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.headerName}>
|
<AppText variant="h2" style={styles.headerName}>
|
||||||
{data.fullName ||
|
{displayName || 'Unknown User'}
|
||||||
`${data.firstName} ${data.lastName}`.trim() ||
|
</AppText>
|
||||||
'Unknown User'}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<View style={styles.roleBadge}>
|
<View style={styles.roleBadge}>
|
||||||
<ShieldCheck size={16} color={colors.white} />
|
<ShieldCheck size={16} color={colors.text} />
|
||||||
<Text style={styles.roleText}>{data.role}</Text>
|
<AppText variant="caption" style={styles.roleText}>{data.role}</AppText>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</LinearGradient>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ProfileHeader.displayName = 'ProfileHeader';
|
ProfileHeader.displayName = 'ProfileHeader';
|
||||||
|
|
||||||
// Quick Stats Component - OPTIMIZED
|
// Quick Stats Component - Glass Cards
|
||||||
const QuickStats: React.FC<QuickStatsProps> = React.memo(({ data }) => {
|
const QuickStats: React.FC<QuickStatsProps> = React.memo(({ data, onAadhaarPress }) => {
|
||||||
|
const user = useSelector((state: any) => state.token?.token);
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
const stats: StatItem[] = useMemo(
|
const stats: StatItem[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Icon: CheckCircle,
|
Icon: CheckCircle,
|
||||||
label: 'Status',
|
label: 'Aadhaar',
|
||||||
value: data.isActive,
|
value: user?.esetuVerified || 'Pending',
|
||||||
color: colors.success,
|
color: user?.esetuVerified === 'Not Verified' ? colors.error : colors.success,
|
||||||
|
onPress: onAadhaarPress,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: ShieldCheck,
|
Icon: ShieldCheck,
|
||||||
label: '2FA',
|
label: '2FA',
|
||||||
value: data.twoFactorAuthEnabled ? 'Enabled' : 'Disabled',
|
value: data.twoFactorAuthEnabled ? 'Enabled' : 'Disabled',
|
||||||
color: data.twoFactorAuthEnabled ? colors.success : colors.grayText,
|
color: data.twoFactorAuthEnabled ? colors.success : colors.textSecondary,
|
||||||
},
|
|
||||||
{
|
|
||||||
Icon: FileText,
|
|
||||||
label: 'Registration',
|
|
||||||
value: data.registrationStatus,
|
|
||||||
color: colors.info,
|
|
||||||
},
|
},
|
||||||
|
// ...
|
||||||
],
|
],
|
||||||
[data.isActive, data.twoFactorAuthEnabled, data.registrationStatus]
|
[data.twoFactorAuthEnabled, user?.esetuVerified, onAadhaarPress, colors]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.statsContainer}>
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 24 }}>
|
||||||
{stats.map((stat) => (
|
{stats.map((stat, index) => {
|
||||||
<View key={stat.label} style={styles.statCard}>
|
const content = (
|
||||||
<stat.Icon size={28} color={stat.color} />
|
<Animated.View
|
||||||
<Text style={styles.statValue} numberOfLines={2}>
|
entering={FadeInUp.duration(600).delay(index * 100)}
|
||||||
{stat.value}
|
style={{
|
||||||
</Text>
|
flex: 1, backgroundColor: colors.glass, borderColor: colors.overlay, borderWidth: 1,
|
||||||
<Text style={styles.statLabel}>{stat.label}</Text>
|
borderRadius: 20, padding: 12, alignItems: 'center', marginHorizontal: 4,
|
||||||
|
minHeight: 100, justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View style={{ width: 40, height: 40, borderRadius: 20, backgroundColor: stat.color + '10', alignItems: 'center', justifyContent: 'center', marginBottom: 8 }}>
|
||||||
|
<stat.Icon size={20} color={stat.color} />
|
||||||
</View>
|
</View>
|
||||||
))}
|
<AppText variant="bodySmall" color={colors.text} numberOfLines={1} style={{ textAlign: 'center' }}>{stat.value}</AppText>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>{stat.label}</AppText>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return stat.onPress ? (
|
||||||
|
<TouchableOpacity key={stat.label} style={{ flex: 1 }} onPress={stat.onPress}>{content}</TouchableOpacity>
|
||||||
|
) : (
|
||||||
|
<View key={stat.label} style={{ flex: 1 }}>{content}</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
QuickStats.displayName = 'QuickStats';
|
// Info Section Component - Glass Card Container
|
||||||
|
|
||||||
// Info Section Component - OPTIMIZED
|
|
||||||
const InfoSection: React.FC<InfoSectionProps> = React.memo(
|
const InfoSection: React.FC<InfoSectionProps> = React.memo(
|
||||||
({ title, IconComponent, children, onEdit, showEdit = false }) => {
|
({ title, IconComponent, children, onEdit, showEdit = false, delay = 0 }) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.section}>
|
<Animated.View
|
||||||
<View style={styles.sectionHeader}>
|
entering={FadeInUp.duration(600).delay(delay)}
|
||||||
<View style={styles.sectionHeaderLeft}>
|
style={{
|
||||||
<View style={styles.sectionIconContainer}>
|
backgroundColor: colors.glass,
|
||||||
<IconComponent size={24} color={colors.primary} />
|
borderColor: colors.overlay,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 20,
|
||||||
|
marginBottom: 16
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||||
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
|
<View style={{ padding: 8, backgroundColor: colors.primary + '10', borderRadius: 10, marginRight: 12 }}>
|
||||||
|
<IconComponent size={20} color={colors.primary} />
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.sectionTitle}>{title}</Text>
|
<AppText variant="h4" color={colors.text}>{title}</AppText>
|
||||||
</View>
|
</View>
|
||||||
{showEdit && onEdit && (
|
{showEdit && onEdit && (
|
||||||
<TouchableOpacity style={styles.editButton} onPress={onEdit}>
|
<TouchableOpacity onPress={onEdit} style={{ padding: 4 }}>
|
||||||
<Edit size={20} color={colors.primary} />
|
<Edit size={18} color={colors.primary} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.sectionContent}>{children}</View>
|
<View>{children}</View>
|
||||||
</View>
|
</Animated.View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
InfoSection.displayName = 'InfoSection';
|
// Info Row Component
|
||||||
|
|
||||||
// Info Row Component - OPTIMIZED
|
|
||||||
const InfoRow: React.FC<InfoRowProps> = React.memo(
|
const InfoRow: React.FC<InfoRowProps> = React.memo(
|
||||||
({ label, value, IconComponent, valueColor }) => {
|
({ label, value, IconComponent, valueColor }) => {
|
||||||
// Return null if value is not valid
|
const { colors } = useTheme();
|
||||||
if (!isValidValue(value)) {
|
|
||||||
return null;
|
if (!isValidValue(value)) return null;
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.infoRow}>
|
<View style={{ flexDirection: 'row', marginBottom: 16, alignItems: 'flex-start' }}>
|
||||||
<View style={styles.infoRowTouchable}>
|
<IconComponent size={16} color={colors.textSecondary} style={{ marginTop: 4, marginRight: 12 }} />
|
||||||
<View style={styles.infoRowContent}>
|
<View style={{ flex: 1 }}>
|
||||||
<View style={styles.infoIconContainer}>
|
<AppText variant="caption" color={colors.textSecondary} style={{ marginBottom: 2 }}>{label}</AppText>
|
||||||
<IconComponent size={18} color={colors.primary} />
|
<AppText variant='bodySmall' color={valueColor || colors.text}>{value}</AppText>
|
||||||
</View>
|
|
||||||
<View style={styles.infoTextContainer}>
|
|
||||||
<Text style={styles.infoLabel}>{label}</Text>
|
|
||||||
<Text
|
|
||||||
style={[styles.infoValue, valueColor && { color: valueColor }]}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
InfoRow.displayName = 'InfoRow';
|
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
interface Styles {
|
const createStyles = (colors: any) => StyleSheet.create({
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create<Styles>({
|
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: colors.lightGray,
|
paddingHorizontal: 20
|
||||||
},
|
},
|
||||||
contentContainer: {
|
contentContainer: {
|
||||||
paddingBottom: 20,
|
paddingBottom: 20,
|
||||||
@@ -728,16 +781,16 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.background,
|
||||||
padding: 20,
|
padding: 20,
|
||||||
},
|
},
|
||||||
loadingText: {
|
loadingText: {
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: colors.grayText,
|
color: colors.textSecondary,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
errorText: {
|
screenErrorText: {
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: colors.error,
|
color: colors.error,
|
||||||
@@ -762,18 +815,10 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Header Styles
|
|
||||||
headerGradient: {
|
headerGradient: {
|
||||||
paddingTop: Platform.OS === 'ios' ? 60 : 40,
|
paddingTop: Platform.OS === 'ios' ? 60 : 40,
|
||||||
paddingBottom: 30,
|
paddingBottom: 30,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderBottomLeftRadius: 32,
|
|
||||||
borderBottomRightRadius: 32,
|
|
||||||
elevation: 8,
|
|
||||||
shadowColor: colors.shadow,
|
|
||||||
shadowOffset: { width: 0, height: 4 },
|
|
||||||
shadowOpacity: 0.3,
|
|
||||||
shadowRadius: 8,
|
|
||||||
},
|
},
|
||||||
avatarContainer: {
|
avatarContainer: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@@ -783,11 +828,11 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
borderRadius: 50,
|
borderRadius: 50,
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.card,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderWidth: 4,
|
borderWidth: 4,
|
||||||
borderColor: colors.white,
|
borderColor: colors.card,
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
shadowColor: colors.shadow,
|
shadowColor: colors.shadow,
|
||||||
shadowOffset: { width: 0, height: 2 },
|
shadowOffset: { width: 0, height: 2 },
|
||||||
@@ -807,7 +852,7 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
height: 24,
|
height: 24,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
borderWidth: 3,
|
borderWidth: 3,
|
||||||
borderColor: colors.white,
|
borderColor: colors.card, // Updated
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
@@ -820,7 +865,7 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
headerName: {
|
headerName: {
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: colors.white,
|
color: colors.text, // Updated
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
@@ -828,19 +873,18 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
roleBadge: {
|
roleBadge: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.25)',
|
backgroundColor: colors.primary + '20', // Updated opacity approach
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: 6,
|
paddingVertical: 6,
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
gap: 6,
|
gap: 6,
|
||||||
},
|
},
|
||||||
roleText: {
|
roleText: {
|
||||||
color: colors.white,
|
color: colors.primary, // Updated
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Stats Styles
|
|
||||||
statsContainer: {
|
statsContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
marginHorizontal: 16,
|
marginHorizontal: 16,
|
||||||
@@ -850,7 +894,7 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
},
|
},
|
||||||
statCard: {
|
statCard: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.card,
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -863,21 +907,20 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
statValue: {
|
statValue: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: colors.darkText,
|
color: colors.text,
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
statLabel: {
|
statLabel: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: colors.grayText,
|
color: colors.textSecondary,
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Section Styles
|
|
||||||
section: {
|
section: {
|
||||||
marginHorizontal: 16,
|
marginHorizontal: 16,
|
||||||
marginTop: 24,
|
marginTop: 24,
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.card,
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
padding: 20,
|
padding: 20,
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
@@ -904,7 +947,7 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
backgroundColor: colors.primaryHighlight,
|
backgroundColor: colors.primary + '10', // Updated
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginRight: 12,
|
marginRight: 12,
|
||||||
@@ -912,7 +955,7 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: colors.darkText,
|
color: colors.text,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
sectionContent: {
|
sectionContent: {
|
||||||
@@ -921,17 +964,16 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
editButton: {
|
editButton: {
|
||||||
padding: 8,
|
padding: 8,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
backgroundColor: colors.primaryHighlight,
|
backgroundColor: colors.primary + '10', // Updated
|
||||||
},
|
},
|
||||||
|
|
||||||
// Info Row Styles
|
|
||||||
infoRow: {
|
infoRow: {
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
infoRowTouchable: {
|
infoRowTouchable: {
|
||||||
padding: 14,
|
padding: 14,
|
||||||
backgroundColor: colors.lightestGray,
|
backgroundColor: colors.background, // Updated
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
},
|
},
|
||||||
infoRowContent: {
|
infoRowContent: {
|
||||||
@@ -942,7 +984,7 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
width: 32,
|
width: 32,
|
||||||
height: 32,
|
height: 32,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.card, // Updated
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginRight: 12,
|
marginRight: 12,
|
||||||
@@ -954,29 +996,28 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
},
|
},
|
||||||
infoLabel: {
|
infoLabel: {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: colors.grayText,
|
color: colors.textSecondary,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
lineHeight: 18,
|
lineHeight: 18,
|
||||||
},
|
},
|
||||||
infoValue: {
|
infoValue: {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
color: colors.darkText,
|
color: colors.text,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
lineHeight: 21,
|
lineHeight: 21,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Modal Styles
|
|
||||||
modalOverlay: {
|
modalOverlay: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
},
|
},
|
||||||
modalContent: {
|
modalContent: {
|
||||||
backgroundColor: colors.white,
|
|
||||||
borderTopLeftRadius: 24,
|
borderTopLeftRadius: 24,
|
||||||
borderTopRightRadius: 24,
|
borderTopRightRadius: 24,
|
||||||
maxHeight: '90%',
|
maxHeight: '90%',
|
||||||
paddingTop: 20,
|
paddingTop: 20,
|
||||||
|
borderRadius: 30
|
||||||
},
|
},
|
||||||
modalHeader: {
|
modalHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -990,7 +1031,7 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
modalTitle: {
|
modalTitle: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: colors.darkText,
|
color: colors.text,
|
||||||
},
|
},
|
||||||
closeButton: {
|
closeButton: {
|
||||||
padding: 4,
|
padding: 4,
|
||||||
@@ -1005,7 +1046,7 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
inputLabel: {
|
inputLabel: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: colors.darkText,
|
color: colors.text,
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
@@ -1014,8 +1055,8 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
padding: 14,
|
padding: 14,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
color: colors.darkText,
|
color: colors.text,
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.card, // Updated
|
||||||
},
|
},
|
||||||
inputError: {
|
inputError: {
|
||||||
borderColor: colors.error,
|
borderColor: colors.error,
|
||||||
@@ -1042,7 +1083,7 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
gap: 8,
|
gap: 8,
|
||||||
},
|
},
|
||||||
cancelButton: {
|
cancelButton: {
|
||||||
backgroundColor: colors.lightestGray,
|
backgroundColor: colors.background, // Updated
|
||||||
},
|
},
|
||||||
saveButton: {
|
saveButton: {
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
@@ -1050,13 +1091,24 @@ const styles = StyleSheet.create<Styles>({
|
|||||||
cancelButtonText: {
|
cancelButtonText: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: colors.darkText,
|
color: colors.text,
|
||||||
},
|
},
|
||||||
saveButtonText: {
|
saveButtonText: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: colors.white,
|
color: colors.white,
|
||||||
},
|
},
|
||||||
|
navigationOverlay: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 999,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ProfileTab;
|
export default ProfileTab;
|
||||||
|
|||||||
@@ -3,23 +3,22 @@ import {
|
|||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
Platform,
|
Platform,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import React, { use, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { useNavigation, useRoute, NavigationProp, RouteProp } from '@react-navigation/native';
|
import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
|
||||||
import colors from '../../Constant/Color';
|
|
||||||
import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton';
|
import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton';
|
||||||
import MyInput from '../../Component/MyInput';
|
import MyInput from '../../components/MyInput';
|
||||||
import { showToast } from '../../Constant/ShowDailog';
|
import { showToast } from '../../Constant/ShowDailog';
|
||||||
import { PARTNER_API_ENDPOINT } from '../../Services/BBPS/ApiUrls';
|
import { usePartnerShipSubmit } from '../../Services/ApiCall/Hooks';
|
||||||
import { usePartnerShipSubmit } from '../../Services/BBPS/Hooks';
|
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { Navigation } from 'lucide-react-native';
|
|
||||||
import { NavigationString } from '../../Constant/AllImports';
|
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 = {
|
type Partner = {
|
||||||
partnerName: string;
|
partnerName: string;
|
||||||
@@ -32,11 +31,12 @@ type Navigation = {
|
|||||||
replace: (route: string) => void;
|
replace: (route: string) => void;
|
||||||
};
|
};
|
||||||
type PartnershipParams = {
|
type PartnershipParams = {
|
||||||
otherId: string | number
|
otherId: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Partnership = () => {
|
const Partnership = () => {
|
||||||
const route = useRoute<RouteProp<{ params: PartnershipParams }, 'params'>>();
|
const route = useRoute<RouteProp<{ params: PartnershipParams }, 'params'>>();
|
||||||
|
const { colors, spacing, typography, shadows } = useTheme();
|
||||||
const navigation = useNavigation() as Navigation;
|
const navigation = useNavigation() as Navigation;
|
||||||
const [partners, setPartners] = useState<Partner[]>([
|
const [partners, setPartners] = useState<Partner[]>([
|
||||||
{
|
{
|
||||||
@@ -49,9 +49,11 @@ const Partnership = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const [focusedField, setFocusedField] = useState<string>('');
|
const [focusedField, setFocusedField] = useState<string>('');
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false);
|
||||||
const { mutateAsync: partnerShipSubmit } = usePartnerShipSubmit();
|
const { mutateAsync: partnerShipSubmit } = usePartnerShipSubmit();
|
||||||
const insets = useSafeAreaInsets();
|
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 handleChange = (index: number, name: keyof Partner, value: string) => {
|
||||||
const updated = [...partners];
|
const updated = [...partners];
|
||||||
updated[index][name] = value;
|
updated[index][name] = value;
|
||||||
@@ -90,9 +92,8 @@ const Partnership = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
setLoading(true)
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// Convert percentage strings to numbers and validate
|
|
||||||
const payload = partners.map((p, i) => {
|
const payload = partners.map((p, i) => {
|
||||||
const percentage = Number(p.percentage);
|
const percentage = Number(p.percentage);
|
||||||
|
|
||||||
@@ -105,174 +106,196 @@ const Partnership = () => {
|
|||||||
ptnrAadharNo: p.aadharNumber,
|
ptnrAadharNo: p.aadharNumber,
|
||||||
ptnrMobNo: p.mobileNumber,
|
ptnrMobNo: p.mobileNumber,
|
||||||
ptnrName: p.partnerName,
|
ptnrName: p.partnerName,
|
||||||
ptnrPop: percentage, // Ensure this is a number
|
ptnrPop: percentage,
|
||||||
ptnrPANNo: p.partnerPan,
|
ptnrPANNo: p.partnerPan,
|
||||||
ptnrNo: i,
|
ptnrNo: i,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Validate total percentage equals 100%
|
|
||||||
const totalPercentage = payload.reduce((sum, p) => sum + p.ptnrPop, 0);
|
const totalPercentage = payload.reduce((sum, p) => sum + p.ptnrPop, 0);
|
||||||
if (totalPercentage !== 100) {
|
if (totalPercentage !== 100) {
|
||||||
showToast(`Total percentage must equal 100% (currently ${totalPercentage}%)`);
|
showToast(`Total percentage must equal 100% (currently ${totalPercentage}%)`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Partner ship payload:", payload);
|
|
||||||
const response = await partnerShipSubmit(payload);
|
const response = await partnerShipSubmit(payload);
|
||||||
|
|
||||||
console.log("first ====== ", response)
|
|
||||||
if (response?.statusCode === 200) {
|
if (response?.statusCode === 200) {
|
||||||
console.log('✅ Partners submitted successfully:', response);
|
|
||||||
showToast(response.message || 'Submitted successfully!');
|
showToast(response.message || 'Submitted successfully!');
|
||||||
navigation.replace(NavigationString.Home);
|
navigation.replace(NavigationString.Home);
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('❌ Submission failed:', error.message);
|
console.error('❌ Submission failed:', error.message);
|
||||||
showToast(error.message || 'Submission failed. Please check partner details.');
|
showToast(error.message || 'Submission failed. Please check partner details.');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1, backgroundColor: colors.background }}>
|
||||||
<Text style={[styles.heading, { paddingTop: insets.top }]}>Partnership Details</Text>
|
<View style={[styles.header, { paddingTop: insets.top + spacing.s }]}>
|
||||||
|
<AppText variant="h3" color={colors.text} style={{ fontWeight: '700' }}>
|
||||||
|
Partnership Details
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>
|
||||||
|
Add all partners and their share percentages
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
|
||||||
<SafeAreaView style={{ flex: 1 }}>
|
<SafeAreaView style={{ flex: 1 }}>
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
keyboardVerticalOffset={40}
|
keyboardVerticalOffset={40}
|
||||||
>
|
>
|
||||||
<ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled">
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.scrollContainer}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
{partners.map((partner, index) => (
|
{partners.map((partner, index) => (
|
||||||
<View style={{ marginBottom: 20 }} key={index}>
|
<Animated.View
|
||||||
<Text style={styles.partnerTitle}>Partner {index + 1}</Text>
|
key={index}
|
||||||
|
entering={FadeInUp.delay(index * 80).springify()}
|
||||||
|
style={[styles.card, shadows.medium]}
|
||||||
|
>
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<AppText variant="bodySmall" color={colors.primary} style={{ fontWeight: '700' }}>
|
||||||
|
Partner {index + 1}
|
||||||
|
</AppText>
|
||||||
|
{partners.length > 1 && (
|
||||||
|
<TouchableOpacity onPress={() => handleRemove(index)} activeOpacity={0.7}>
|
||||||
|
<Trash2 size={18} color={colors.error} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="user"
|
label="Partner Name"
|
||||||
placeholder="Name of Partner *"
|
placeholder="Enter Partner Name"
|
||||||
value={partner.partnerName}
|
value={partner.partnerName}
|
||||||
onChangeText={text => handleChange(index, 'partnerName', text)}
|
onChangeText={text => handleChange(index, 'partnerName', text)}
|
||||||
onFocus={() => setFocusedField(`partnerName-${index}`)}
|
onFocus={() => setFocusedField(`partnerName-${index}`)}
|
||||||
onBlur={() => setFocusedField('')}
|
onBlur={() => setFocusedField('')}
|
||||||
focused={focusedField === `partnerName-${index}`}
|
focused={focusedField === `partnerName-${index}`}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="percent"
|
label="Share Percentage"
|
||||||
placeholder="Partnership Percentage *"
|
placeholder="Enter Percentage"
|
||||||
value={partner.percentage}
|
value={partner.percentage}
|
||||||
onChangeText={text => handleChange(index, 'percentage', text)}
|
onChangeText={text => handleChange(index, 'percentage', text)}
|
||||||
onFocus={() => setFocusedField(`percentage-${index}`)}
|
onFocus={() => setFocusedField(`percentage-${index}`)}
|
||||||
onBlur={() => setFocusedField('')}
|
onBlur={() => setFocusedField('')}
|
||||||
focused={focusedField === `percentage-${index}`}
|
focused={focusedField === `percentage-${index}`}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
maxlength={3}
|
maxLength={3}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="id-card"
|
label="PAN of Partner"
|
||||||
placeholder="PAN of Partner *"
|
placeholder="Enter PAN Number"
|
||||||
value={partner.partnerPan}
|
value={partner.partnerPan}
|
||||||
onChangeText={text => handleChange(index, 'partnerPan', text)}
|
onChangeText={text => handleChange(index, 'partnerPan', text)}
|
||||||
onFocus={() => setFocusedField(`partnerPan-${index}`)}
|
onFocus={() => setFocusedField(`partnerPan-${index}`)}
|
||||||
onBlur={() => setFocusedField('')}
|
onBlur={() => setFocusedField('')}
|
||||||
focused={focusedField === `partnerPan-${index}`}
|
focused={focusedField === `partnerPan-${index}`}
|
||||||
autoCapitalize="characters"
|
autoCapitalize="characters"
|
||||||
maxlength={10}
|
maxLength={10}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="credit-card"
|
label="Aadhaar Number"
|
||||||
placeholder="Aadhar Number *"
|
placeholder="Enter 12-digit Aadhaar"
|
||||||
value={partner.aadharNumber}
|
value={partner.aadharNumber}
|
||||||
onChangeText={text => handleChange(index, 'aadharNumber', text)}
|
onChangeText={text => handleChange(index, 'aadharNumber', text)}
|
||||||
onFocus={() => setFocusedField(`aadharNumber-${index}`)}
|
onFocus={() => setFocusedField(`aadharNumber-${index}`)}
|
||||||
onBlur={() => setFocusedField('')}
|
onBlur={() => setFocusedField('')}
|
||||||
focused={focusedField === `aadharNumber-${index}`}
|
focused={focusedField === `aadharNumber-${index}`}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
maxlength={12}
|
maxLength={12}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="phone"
|
label="Mobile Number"
|
||||||
placeholder="Mobile Number *"
|
placeholder="Enter Mobile Number"
|
||||||
value={partner.mobileNumber}
|
value={partner.mobileNumber}
|
||||||
onChangeText={text => handleChange(index, 'mobileNumber', text)}
|
onChangeText={text => handleChange(index, 'mobileNumber', text)}
|
||||||
onFocus={() => setFocusedField(`mobileNumber-${index}`)}
|
onFocus={() => setFocusedField(`mobileNumber-${index}`)}
|
||||||
onBlur={() => setFocusedField('')}
|
onBlur={() => setFocusedField('')}
|
||||||
focused={focusedField === `mobileNumber-${index}`}
|
focused={focusedField === `mobileNumber-${index}`}
|
||||||
keyboardType="phone-pad"
|
keyboardType="phone-pad"
|
||||||
maxlength={10}
|
maxLength={10}
|
||||||
/>
|
/>
|
||||||
|
</Animated.View>
|
||||||
{partners.length > 1 && (
|
|
||||||
<TouchableOpacity style={styles.deleteButton} onPress={() => handleRemove(index)}>
|
|
||||||
<Text style={styles.deleteText}>Delete Partner</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<TouchableOpacity style={styles.addButton} onPress={handleAddMore}>
|
<TouchableOpacity style={styles.addButton} onPress={handleAddMore} activeOpacity={0.8}>
|
||||||
<Text style={styles.addText}>+ Add Partner</Text>
|
<UserPlus size={16} color={colors.buttonText} />
|
||||||
|
<AppText variant="bodySmall" color={colors.buttonText} style={{ fontWeight: '700', marginLeft: 8 }}>
|
||||||
|
Add Partner
|
||||||
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
|
||||||
<PrimaryButton title={loading ? "Loading..." : "Submit" }onPress={handleSubmit} />
|
<View style={styles.footer}>
|
||||||
|
<PrimaryButton title={loading ? 'Loading...' : 'Submit'} onPress={handleSubmit} />
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Partnership;
|
export default Partnership;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const makeStyles = (colors: any, spacing: any, shadows: any, typography: any) => StyleSheet.create({
|
||||||
heading: {
|
header: {
|
||||||
fontSize: 18,
|
paddingHorizontal: spacing.m,
|
||||||
fontWeight: 'bold',
|
paddingBottom: spacing.m,
|
||||||
marginBottom: 10,
|
borderBottomWidth: 1,
|
||||||
marginTop: 10,
|
borderBottomColor: colors.border,
|
||||||
textAlign: 'center',
|
backgroundColor: colors.surface,
|
||||||
|
|
||||||
},
|
|
||||||
partnerTitle: {
|
|
||||||
fontWeight: '600',
|
|
||||||
fontSize: 16,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
},
|
||||||
scrollContainer: {
|
scrollContainer: {
|
||||||
// backgroundColor: color.background,
|
|
||||||
flexGrow: 1,
|
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: {
|
addButton: {
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
padding: 10,
|
flexDirection: 'row',
|
||||||
marginTop: 10,
|
|
||||||
borderRadius: 10,
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
width: 140,
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
borderRadius: 12,
|
||||||
alignSelf: 'flex-end',
|
alignSelf: 'flex-end',
|
||||||
},
|
},
|
||||||
addText: {
|
footer: {
|
||||||
color: 'white',
|
padding: spacing.m,
|
||||||
fontWeight: '600',
|
borderTopWidth: 1,
|
||||||
},
|
borderTopColor: colors.border,
|
||||||
deleteButton: {
|
backgroundColor: colors.surface,
|
||||||
backgroundColor: '#FF5C5C',
|
|
||||||
padding: 10,
|
|
||||||
marginTop: 10,
|
|
||||||
borderRadius: 10,
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 140,
|
|
||||||
alignSelf: 'flex-end',
|
|
||||||
},
|
|
||||||
deleteText: {
|
|
||||||
color: 'white',
|
|
||||||
fontWeight: '600',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,31 +1,27 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { View, Text, TouchableOpacity, StyleSheet, Platform, KeyboardAvoidingView, SafeAreaView, ScrollView, BackHandler, ToastAndroid, Button, Alert } from 'react-native';
|
import { View, TouchableOpacity, StyleSheet, Platform, KeyboardAvoidingView, ScrollView, BackHandler, ToastAndroid, Alert } from 'react-native';
|
||||||
// import AntDesign from 'react-native-vector-icons/AntDesign';
|
|
||||||
import { ArrowLeft } from 'lucide-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 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 { ApiResponse, DocumentsType, FormDataType, OrgType, UserData } from './types/types';
|
||||||
import { pick } from '@react-native-documents/picker';
|
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 axios from 'axios';
|
||||||
import StageOne from './ragistrationStages/StageOne';
|
import StageOne from './ragistrationStages/StageOne';
|
||||||
import StageTwo from './ragistrationStages/StageTwo';
|
import StageTwo from './ragistrationStages/StageTwo';
|
||||||
import StageZero from './ragistrationStages/StageZero';
|
import StageZero from './ragistrationStages/StageZero';
|
||||||
import StageThree from './ragistrationStages/StageThree';
|
import StageThree from './ragistrationStages/StageThree';
|
||||||
import StageFour from './ragistrationStages/StageFour';
|
import StageFour from './ragistrationStages/StageFour';
|
||||||
import colors from '../../Constant/Color';
|
|
||||||
import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton';
|
import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton';
|
||||||
import StepProgressBar from '../../Component/StepProgressBar';
|
import StepProgressBar from '../../Component/StepProgressBar';
|
||||||
import { formatDateInput } from '../../Helper/formatDateInput';
|
import { formatDateInput } from '../../Helper/formatDateInput';
|
||||||
import { showToast } from '../../Constant/ShowDailog';
|
import { showToast } from '../../Constant/ShowDailog';
|
||||||
import { useCityDropdown, useContractorStageTwoSubmit, usefetchMasterData, useSendAadhaarOtp, useVerifyAadhaarOtp, useVerifyGstNumber, useVerifyPan } from '../../Services/BBPS/Hooks';
|
import { useCityDropdown, useContractorStageTwoSubmit, usefetchMasterData, useSendAadhaarOtp, useVerifyAadhaarOtp, useVerifyGstNumber, useVerifyPan } from '../../Services/ApiCall/Hooks';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import Animated, { FadeInUp, FadeInDown } from 'react-native-reanimated';
|
||||||
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
|
import AppText from '../../components/AppText';
|
||||||
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
|
|
||||||
type RouteParams = {
|
type RouteParams = {
|
||||||
userData?: {
|
userData?: {
|
||||||
@@ -38,6 +34,7 @@ type RouteParams = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Registration: React.FC = () => {
|
const Registration: React.FC = () => {
|
||||||
|
const { colors, spacing, typography, shadows } = useTheme();
|
||||||
const navigation = useNavigation<any>();
|
const navigation = useNavigation<any>();
|
||||||
const { params } = useRoute<RouteProp<Record<string, RouteParams>, string>>();
|
const { params } = useRoute<RouteProp<Record<string, RouteParams>, string>>();
|
||||||
const userData = params as RouteParams;
|
const userData = params as RouteParams;
|
||||||
@@ -102,7 +99,9 @@ const Registration: React.FC = () => {
|
|||||||
const { mutateAsync: verifyGstNumber } = useVerifyGstNumber();
|
const { mutateAsync: verifyGstNumber } = useVerifyGstNumber();
|
||||||
const { mutateAsync: contractorStageTwoSubmit } = useContractorStageTwoSubmit();
|
const { mutateAsync: contractorStageTwoSubmit } = useContractorStageTwoSubmit();
|
||||||
const { data: masterData } = usefetchMasterData();
|
const { data: masterData } = usefetchMasterData();
|
||||||
console.log("master",masterData)
|
|
||||||
|
const styles = useMemo(() => makeStyles(colors, spacing, shadows, typography), [colors, spacing, shadows, typography]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!masterData?.data) return;
|
if (!masterData?.data) return;
|
||||||
const contractorClasses = masterData.data.contractorClasses || [];
|
const contractorClasses = masterData.data.contractorClasses || [];
|
||||||
@@ -139,28 +138,15 @@ useEffect(() => {
|
|||||||
|
|
||||||
|
|
||||||
const { mutate: cityDropdownMutate } = useCityDropdown();
|
const { mutate: cityDropdownMutate } = useCityDropdown();
|
||||||
const { data: MasterData}=usefetchMasterData()
|
const { data: MasterData } = usefetchMasterData();
|
||||||
const user = useSelector((state: any) => state.token.token);
|
const user = useSelector((state: any) => state.token.token);
|
||||||
const steps = ['', '', '', '', ''];
|
const steps = ['', '', '', '', ''];
|
||||||
const dropdownStyle = {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#e0e0e0',
|
|
||||||
borderRadius: 12,
|
|
||||||
paddingHorizontal: 14,
|
|
||||||
height: 50,
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
marginBottom: 10,
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUserData = async (user: any) => {
|
const fetchUserData = async (user: any) => {
|
||||||
console.log("hitting it ")
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
console.log("hitting it 1 ")
|
|
||||||
console.log("object====",user)
|
|
||||||
if (user) {
|
if (user) {
|
||||||
const parsedUser = user;
|
const parsedUser = user;
|
||||||
console.log(parsedUser);
|
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
firstName: parsedUser?.firstName || userData?.userData?.firstName,
|
firstName: parsedUser?.firstName || userData?.userData?.firstName,
|
||||||
@@ -169,7 +155,6 @@ useEffect(() => {
|
|||||||
middleName: parsedUser?.middleName || userData?.userData?.middleName,
|
middleName: parsedUser?.middleName || userData?.userData?.middleName,
|
||||||
email: parsedUser?.email || userData?.userData?.email,
|
email: parsedUser?.email || userData?.userData?.email,
|
||||||
}));
|
}));
|
||||||
console.log('User Data from Storage:', parsedUser);
|
|
||||||
} else {
|
} else {
|
||||||
console.log('No user data found');
|
console.log('No user data found');
|
||||||
}
|
}
|
||||||
@@ -186,25 +171,11 @@ useEffect(() => {
|
|||||||
gstin = gstin.toUpperCase();
|
gstin = gstin.toUpperCase();
|
||||||
|
|
||||||
const panRegex = /^[A-Z]{5}[0-9]{4}[A-Z]$/;
|
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)) {
|
if (!panRegex.test(pan)) {
|
||||||
return { valid: false, message: "Invalid PAN format. It should be like 'ABCDE1234F'." };
|
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.' };
|
return { valid: true, message: 'PAN and GSTIN are correctly linked.' };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -213,7 +184,7 @@ useEffect(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const verifyPanHandler = async () => {
|
const verifyPanHandler = async () => {
|
||||||
setPanVerifying(true); // Start loading
|
setPanVerifying(true);
|
||||||
const payload = {
|
const payload = {
|
||||||
dob: formData?.dateOfIncorp.trim(),
|
dob: formData?.dateOfIncorp.trim(),
|
||||||
name: formData?.orgPanName.trim(),
|
name: formData?.orgPanName.trim(),
|
||||||
@@ -226,11 +197,10 @@ useEffect(() => {
|
|||||||
} else {
|
} else {
|
||||||
setPanVerified(false);
|
setPanVerified(false);
|
||||||
}
|
}
|
||||||
setPanVerifying(false); // Stop loading
|
setPanVerifying(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendAdharOTPHandler = async (text: string) => {
|
const sendAdharOTPHandler = async (text: string) => {
|
||||||
console.log('Sending Aadhaar OTP...');
|
|
||||||
setAadhaarVerifying(true);
|
setAadhaarVerifying(true);
|
||||||
setadharreference('');
|
setadharreference('');
|
||||||
|
|
||||||
@@ -329,7 +299,6 @@ useEffect(() => {
|
|||||||
gistnNo: formData.gstnNo,
|
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 || '',
|
||||||
};
|
};
|
||||||
console.log('Form Data for registration',formData);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fileData = new FormData();
|
const fileData = new FormData();
|
||||||
@@ -341,38 +310,33 @@ useEffect(() => {
|
|||||||
fileData.append('pancardfile', documents.panCard as any);
|
fileData.append('pancardfile', documents.panCard as any);
|
||||||
fileData.append('regfile', documents.registrationCertificate as any);
|
fileData.append('regfile', documents.registrationCertificate as any);
|
||||||
|
|
||||||
console.log('File Data for registration',fileData);
|
|
||||||
|
|
||||||
const res = await contractorStageTwoSubmit(fileData);
|
const res = await contractorStageTwoSubmit(fileData);
|
||||||
console.log('Register contractor Response : ', res);
|
console.log('Stage Two Res : ', res)
|
||||||
|
if (res.statusCode === 200) {
|
||||||
if (res.status === 200) {
|
|
||||||
if (res) {
|
if (res) {
|
||||||
|
|
||||||
showToast('Form submitted successfully!');
|
showToast('Form submitted successfully!');
|
||||||
console.log('response?.data?.contOtherId : ', res?.data?.contOtherId);
|
|
||||||
|
|
||||||
const orgType = selectedOrg;
|
const orgType = selectedOrg;
|
||||||
const params = { otherId: res?.data?.contOtherId, orgType: orgType };
|
const params = { otherId: res?.data?.contOtherId, orgType: orgType };
|
||||||
|
console.log("params : ", params)
|
||||||
if ([4, 5, 6].includes(orgType)) {
|
if ([4, 5, 6].includes(orgType)) {
|
||||||
navigation.replace('StageThree' as never, params as never);
|
navigation.replace(NavigationString.StageThreeScreen as never, params as never);
|
||||||
} else if (orgType === 7) {
|
} else if (orgType === 7) {
|
||||||
console.log('org type else if : ', orgType);
|
navigation.replace(NavigationString.Partnership as never, { otherId: res?.data?.contOtherId } as never);
|
||||||
navigation.replace('PartnerShip' as never, { otherId: res?.data?.contOtherId } as never);
|
|
||||||
} else {
|
} else {
|
||||||
console.log('org type else : ', orgType);
|
navigation.replace(NavigationString.Home as never);
|
||||||
navigation.replace('Dashboard' as never);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showToast('Form submission failed');
|
showToast('Form submission failed');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
showToast(res.message || 'Form submission failed. Please try again.');
|
showToast(res.message || 'Form submission failed. Please try again.');
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
console.log("Error in form submission")
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
console.log('11111', error.response?.status);
|
|
||||||
console.log('22222', error.response?.data);
|
|
||||||
console.log('3333333', error.response?.headers);
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Unexpected error', error);
|
console.error('Unexpected error', error);
|
||||||
}
|
}
|
||||||
@@ -383,27 +347,10 @@ useEffect(() => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// const fetchAllMSME = async () => {
|
|
||||||
// try {
|
|
||||||
// console.log('Fetching MSME');
|
|
||||||
// const msmeOption = await getAllMsMeOrg();
|
|
||||||
// console.log('msme options fetched:', msmeOption);
|
|
||||||
// setAllMSME(msmeOption);
|
|
||||||
// } catch (error) {
|
|
||||||
// setAllMSME([]);
|
|
||||||
// console.error('Error in city fetch:', error);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const fetchCity = async (stateId: number) => {
|
const fetchCity = async (stateId: number) => {
|
||||||
cityDropdownMutate(stateId, {
|
cityDropdownMutate(stateId, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
console.log('City options fetched:', data);
|
|
||||||
// const cityList = data.data || [];
|
|
||||||
// const mappedCity = cityList.map((item:any)=> ({
|
|
||||||
// label: item.name,
|
|
||||||
// value: item.id,
|
|
||||||
// }));
|
|
||||||
setCities(data);
|
setCities(data);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@@ -411,89 +358,10 @@ useEffect(() => {
|
|||||||
console.error('Error in city fetch:', error);
|
console.error('Error in city fetch:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// try {
|
|
||||||
// console.log('Fetching cities for state ID:', stateId);
|
|
||||||
|
|
||||||
// console.log('City options fetched:', cityOptions);
|
|
||||||
// setCities(cityOptions);
|
|
||||||
// } catch (error) {
|
|
||||||
// setCities([]);
|
|
||||||
// console.error('Error in city fetch:', error);
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// const contactorClassHandler = async () => {
|
|
||||||
// try {
|
|
||||||
// setloader(true);
|
|
||||||
// const mappedItems = await getClassDropdown();
|
|
||||||
// console.log(mappedItems, 'Contractor class items');
|
|
||||||
// setContractorClassItems(mappedItems);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log('Contractor class fetch error:', error);
|
|
||||||
// } finally {
|
|
||||||
// setloader(false);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const contactorORG = async () => {
|
|
||||||
// try {
|
|
||||||
// setloader(true);
|
|
||||||
// const mappedItems = await getOrgDropdown();
|
|
||||||
// setAllOrganizations(mappedItems);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log('Contractor ORG fetch error:', error);
|
|
||||||
// } finally {
|
|
||||||
// setloader(false);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const contactorRegistration = async () => {
|
|
||||||
// try {
|
|
||||||
// setloader(true);
|
|
||||||
// const mappedItems = await getMsmeDropdown();
|
|
||||||
// setAllMsmeOrgs(mappedItems);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log('Contractor ORG fetch error:', error);
|
|
||||||
// } finally {
|
|
||||||
// setloader(false);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
console.log('formdata : ', formData);
|
console.log('formdata : ', formData);
|
||||||
|
|
||||||
// const contactorSocial = async () => {
|
|
||||||
// try {
|
|
||||||
// setloader(true);
|
|
||||||
// const mappedItems = await getSocialDropdown();
|
|
||||||
// setAllSocialCategories(mappedItems);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log('Contractor ORG fetch error:', error);
|
|
||||||
// } finally {
|
|
||||||
// setloader(false);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const allState = async () => {
|
|
||||||
// try {
|
|
||||||
// setloader(true);
|
|
||||||
// const mappedItems = await getStateDropdown();
|
|
||||||
// setstates(mappedItems);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log('all states error:', error);
|
|
||||||
// } finally {
|
|
||||||
// setloader(false);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// contactorClassHandler();
|
|
||||||
// contactorORG();
|
|
||||||
// contactorRegistration();
|
|
||||||
// contactorSocial();
|
|
||||||
// allState();
|
|
||||||
// fetchAllMSME();
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formData?.state) {
|
if (formData?.state) {
|
||||||
fetchCity(Number(formData.state));
|
fetchCity(Number(formData.state));
|
||||||
@@ -609,15 +477,31 @@ useEffect(() => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Stage Titles ──
|
||||||
|
const stageTitles = [
|
||||||
|
'Personal & Contact',
|
||||||
|
'Registration Details',
|
||||||
|
'KYC Verification',
|
||||||
|
'Address Details',
|
||||||
|
'Upload Documents',
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, backgroundColor: colors.white }}>
|
<View style={{ flex: 1, backgroundColor: colors.background }}>
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
keyboardVerticalOffset={40}
|
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -30}
|
||||||
|
>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.scrollContainer}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
bounces={false}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
>
|
>
|
||||||
<ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled" bounces={false}>
|
|
||||||
<View style={{ paddingBottom: 20, marginTop: insets.top, flex: 1 }}>
|
<View style={{ paddingBottom: 20, marginTop: insets.top, flex: 1 }}>
|
||||||
|
{/* ═══ Back Button ═══ */}
|
||||||
|
<View style={styles.headerRow}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (stage > 0) {
|
if (stage > 0) {
|
||||||
@@ -625,12 +509,39 @@ useEffect(() => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={styles.backBtn}
|
style={styles.backBtn}
|
||||||
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
{/* Replaced AntDesign with Lucide ArrowLeft */}
|
<ArrowLeft size={22} color={colors.text} />
|
||||||
<ArrowLeft size={25} color={colors.primary} />
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
<AppText variant="h3" color={colors.text} style={{ fontWeight: '700' }}>
|
||||||
|
Registration
|
||||||
|
</AppText>
|
||||||
|
<View style={{ width: 36 }} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* ═══ Progress Bar ═══ */}
|
||||||
<StepProgressBar currentStep={stage} steps={steps} />
|
<StepProgressBar currentStep={stage} steps={steps} />
|
||||||
|
|
||||||
|
{/* ═══ Stage Title ═══
|
||||||
|
<Animated.View
|
||||||
|
key={`title-${stage}`}
|
||||||
|
entering={FadeInUp.duration(300)}
|
||||||
|
style={styles.stageTitleContainer}
|
||||||
|
>
|
||||||
|
<AppText variant="h3" color={colors.text} style={styles.stageTitle}>
|
||||||
|
{stageTitles[stage]}
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>
|
||||||
|
Step {stage + 1} of {steps.length}
|
||||||
|
</AppText>
|
||||||
|
</Animated.View> */}
|
||||||
|
|
||||||
|
{/* ═══ Stage Content ═══ */}
|
||||||
|
<Animated.View
|
||||||
|
key={`stage-${stage}`}
|
||||||
|
entering={FadeInUp.duration(350).delay(100)}
|
||||||
|
style={[styles.stageCard, shadows.medium]}
|
||||||
|
>
|
||||||
{stage === 0 && (
|
{stage === 0 && (
|
||||||
<StageZero
|
<StageZero
|
||||||
formData={formData}
|
formData={formData}
|
||||||
@@ -656,7 +567,7 @@ useEffect(() => {
|
|||||||
allMsmeOrgs={allMsmeOrgs}
|
allMsmeOrgs={allMsmeOrgs}
|
||||||
allSocialCategories={allSocialCategories}
|
allSocialCategories={allSocialCategories}
|
||||||
allMSME={allMSME}
|
allMSME={allMSME}
|
||||||
dropdownStyle={dropdownStyle}
|
dropdownStyle={{}}
|
||||||
setSelectedOrg={setSelectedOrg}
|
setSelectedOrg={setSelectedOrg}
|
||||||
selectedOrg={selectedOrg}
|
selectedOrg={selectedOrg}
|
||||||
/>
|
/>
|
||||||
@@ -701,8 +612,9 @@ useEffect(() => {
|
|||||||
states={states}
|
states={states}
|
||||||
cities={cities}
|
cities={cities}
|
||||||
fetchCity={() => {
|
fetchCity={() => {
|
||||||
fetchCity(Number(formData.state))}}
|
fetchCity(Number(formData.state));
|
||||||
dropdownStyle={dropdownStyle}
|
}}
|
||||||
|
dropdownStyle={{}}
|
||||||
styles={styles}
|
styles={styles}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -716,10 +628,14 @@ useEffect(() => {
|
|||||||
selectedOrg={selectedOrg}
|
selectedOrg={selectedOrg}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* ═══ Next / Submit Button ═══ */}
|
||||||
|
<Animated.View entering={FadeInDown.duration(300).delay(200)}>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
title={stage === 4 ? (loading ? 'Loading...' : 'Submit') : 'Next'}
|
title={stage === 4 ? (loading ? 'Loading...' : 'Submit') : 'Next'}
|
||||||
|
style={{ backgroundColor: colors.primary }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (validateStage(stage)) {
|
if (validateStage(stage)) {
|
||||||
switch (stage) {
|
switch (stage) {
|
||||||
@@ -764,6 +680,7 @@ useEffect(() => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Animated.View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</View>
|
</View>
|
||||||
@@ -772,96 +689,96 @@ useEffect(() => {
|
|||||||
|
|
||||||
export default Registration;
|
export default Registration;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
// ── Styles ────────────────────────────────────────────────────────────
|
||||||
|
const makeStyles = (colors: any, spacing: any, shadows: any, typography: any) => StyleSheet.create({
|
||||||
scrollContainer: {
|
scrollContainer: {
|
||||||
flexGrow: 1,
|
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: {
|
label: {
|
||||||
fontSize: 15,
|
fontSize: typography.size.s,
|
||||||
fontWeight: '600',
|
fontWeight: '700',
|
||||||
marginVertical: 10,
|
color: colors.primary,
|
||||||
color: colors.black,
|
marginBottom: spacing.s,
|
||||||
|
letterSpacing: 0.3,
|
||||||
|
textTransform: 'uppercase',
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
backBtn: {
|
|
||||||
padding: 5,
|
|
||||||
width: 50,
|
|
||||||
marginBottom: 5,
|
|
||||||
},
|
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: 24,
|
fontSize: typography.size.l,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: colors.black,
|
color: colors.text,
|
||||||
marginBottom: 20,
|
marginBottom: spacing.m,
|
||||||
marginTop: 10,
|
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
uploadGrid: {
|
uploadGrid: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
|
gap: spacing.s,
|
||||||
},
|
},
|
||||||
uploadCard: {
|
uploadCard: {
|
||||||
width: '48%',
|
width: '48%',
|
||||||
backgroundColor: '#fdfdfd',
|
backgroundColor: colors.surface,
|
||||||
borderRadius: 20,
|
borderRadius: 16,
|
||||||
padding: 20,
|
padding: spacing.m,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: 20,
|
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOffset: { width: 0, height: 4 },
|
|
||||||
shadowOpacity: 0.08,
|
|
||||||
shadowRadius: 6,
|
|
||||||
elevation: 4,
|
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#ddd',
|
borderColor: colors.border,
|
||||||
},
|
},
|
||||||
iconCircle: {
|
iconCircle: {
|
||||||
width: 50,
|
width: 46,
|
||||||
height: 50,
|
height: 46,
|
||||||
borderRadius: 25,
|
borderRadius: 23,
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: 10,
|
marginBottom: spacing.s,
|
||||||
},
|
},
|
||||||
cardTitle: {
|
cardTitle: {
|
||||||
fontSize: 15,
|
fontSize: typography.size.s,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: colors.black,
|
color: colors.text,
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
cardHint: {
|
cardHint: {
|
||||||
fontSize: 12,
|
fontSize: typography.size.xs,
|
||||||
color: '#888',
|
color: colors.textSecondary,
|
||||||
textAlign: 'center',
|
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,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,23 +3,21 @@ import {
|
|||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
Platform,
|
Platform,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import React, { useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
|
import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import colors from '../../Constant/Color';
|
|
||||||
import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton';
|
import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton';
|
||||||
import MyInput from '../../Component/MyInput';
|
import MyInput from '../../components/MyInput';
|
||||||
import { showToast } from '../../Constant/ShowDailog';
|
import { showToast } from '../../Constant/ShowDailog';
|
||||||
import { STAGETHREE } from '../../Services/BBPS/ApiUrls';
|
import { useContractorStageThreeSubmit } from '../../Services/ApiCall/Hooks';
|
||||||
import { useContractorStageThreeSubmit } from '../../Services/BBPS/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 = {
|
type Company = {
|
||||||
director: string;
|
director: string;
|
||||||
@@ -31,12 +29,15 @@ type Company = {
|
|||||||
|
|
||||||
type StageThreeRouteParams = {
|
type StageThreeRouteParams = {
|
||||||
orgType: string;
|
orgType: string;
|
||||||
otherId: string | number
|
otherId: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StageThreeScreen = () => {
|
const StageThreeScreen = () => {
|
||||||
const route = useRoute<RouteProp<{ params: StageThreeRouteParams }, 'params'>>();
|
const route = useRoute<RouteProp<{ params: StageThreeRouteParams }, 'params'>>();
|
||||||
const navigation = useNavigation<any>();
|
const navigation = useNavigation<any>();
|
||||||
|
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<Company[]>([
|
const [company, setCompany] = useState<Company[]>([
|
||||||
{
|
{
|
||||||
@@ -71,7 +72,6 @@ const StageThreeScreen = () => {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("first company route : ", route);
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -118,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 (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1, backgroundColor: colors.background }}>
|
||||||
<Text style={styles.heading}>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</Text>
|
<View style={[styles.header, { paddingTop: insets.top + spacing.s }]}>
|
||||||
|
<AppText variant="h3" color={colors.text} style={{ fontWeight: '700' }}>
|
||||||
|
{orgLabel} Organization
|
||||||
|
</AppText>
|
||||||
|
<AppText variant="caption" color={colors.textSecondary}>
|
||||||
|
Add directors / shareholders for your organization
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
|
||||||
<SafeAreaView style={{ flex: 1 }}>
|
<SafeAreaView style={{ flex: 1 }}>
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
keyboardVerticalOffset={40}
|
keyboardVerticalOffset={40}
|
||||||
>
|
>
|
||||||
<ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled">
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.scrollContainer}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
{company.map((partner, index) => (
|
{company.map((partner, index) => (
|
||||||
<View style={{ marginBottom: 20 }} key={index}>
|
<Animated.View
|
||||||
<Text style={styles.partnerTitle}>Company {index + 1}</Text>
|
key={index}
|
||||||
|
entering={FadeInUp.delay(index * 80).springify()}
|
||||||
|
style={[styles.card, shadows.medium]}
|
||||||
|
>
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<AppText variant="bodySmall" color={colors.primary} style={{ fontWeight: '700' }}>
|
||||||
|
Director {index + 1}
|
||||||
|
</AppText>
|
||||||
|
{company.length > 1 && (
|
||||||
|
<TouchableOpacity onPress={() => handleRemove(index)} activeOpacity={0.7}>
|
||||||
|
<Trash2 size={18} color={colors.error} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="user"
|
label="Director Name"
|
||||||
placeholder="Name of Director *"
|
placeholder="Enter Name of Director"
|
||||||
value={partner.director}
|
value={partner.director}
|
||||||
onChangeText={(text: any) => handleChange(index, 'director', text)}
|
onChangeText={(text: any) => handleChange(index, 'director', text)}
|
||||||
onFocus={() => setFocusedField(`director-${index}`)}
|
onFocus={() => setFocusedField(`director-${index}`)}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === `director-${index}`}
|
focused={focusedField === `director-${index}`}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="id-card"
|
label="DIN Number"
|
||||||
placeholder="DIN Number in Firm *"
|
placeholder="Enter DIN Number"
|
||||||
value={partner.DINNumber}
|
value={partner.DINNumber}
|
||||||
onChangeText={(text: any) => handleChange(index, 'DINNumber', text)}
|
onChangeText={(text: any) => handleChange(index, 'DINNumber', text)}
|
||||||
onFocus={() => setFocusedField(`DINNumber-${index}`)}
|
onFocus={() => setFocusedField(`DINNumber-${index}`)}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === `DINNumber-${index}`}
|
focused={focusedField === `DINNumber-${index}`}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
maxlength={8}
|
maxLength={8}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="id-card"
|
label="PAN of Director"
|
||||||
placeholder="PAN of Director *"
|
placeholder="Enter PAN Number"
|
||||||
value={partner.panNumber}
|
value={partner.panNumber}
|
||||||
onChangeText={(text: any) => handleChange(index, 'panNumber', text)}
|
onChangeText={(text: any) => handleChange(index, 'panNumber', text)}
|
||||||
onFocus={() => setFocusedField(`panNumber-${index}`)}
|
onFocus={() => setFocusedField(`panNumber-${index}`)}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === `panNumber-${index}`}
|
focused={focusedField === `panNumber-${index}`}
|
||||||
maxlength={12}
|
maxLength={12}
|
||||||
autoCapitalize="characters"
|
autoCapitalize="characters"
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="credit-card"
|
label="Aadhaar Number"
|
||||||
placeholder="Aadhar Number *"
|
placeholder="Enter 12-digit Aadhaar"
|
||||||
value={partner.aadharNumber}
|
value={partner.aadharNumber}
|
||||||
onChangeText={(text: any) => handleChange(index, 'aadharNumber', text)}
|
onChangeText={(text: any) => handleChange(index, 'aadharNumber', text)}
|
||||||
onFocus={() => setFocusedField(`aadharNumber-${index}`)}
|
onFocus={() => setFocusedField(`aadharNumber-${index}`)}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === `aadharNumber-${index}`}
|
focused={focusedField === `aadharNumber-${index}`}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
maxlength={12}
|
maxLength={12}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="phone"
|
label="Mobile Number"
|
||||||
placeholder="Mobile Number *"
|
placeholder="Enter Mobile Number"
|
||||||
value={partner.mobileNumber}
|
value={partner.mobileNumber}
|
||||||
onChangeText={(text: any) => handleChange(index, 'mobileNumber', text)}
|
onChangeText={(text: any) => handleChange(index, 'mobileNumber', text)}
|
||||||
onFocus={() => setFocusedField(`mobileNumber-${index}`)}
|
onFocus={() => setFocusedField(`mobileNumber-${index}`)}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === `mobileNumber-${index}`}
|
focused={focusedField === `mobileNumber-${index}`}
|
||||||
keyboardType="phone-pad"
|
keyboardType="phone-pad"
|
||||||
maxlength={10}
|
maxLength={10}
|
||||||
/>
|
/>
|
||||||
|
</Animated.View>
|
||||||
{company.length > 1 && (
|
|
||||||
<TouchableOpacity style={styles.deleteButton} onPress={() => handleRemove(index)}>
|
|
||||||
<Text style={styles.deleteText}>Delete Company</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<TouchableOpacity style={styles.addButton} onPress={handleAddMore}>
|
<TouchableOpacity style={styles.addButton} onPress={handleAddMore} activeOpacity={0.8}>
|
||||||
<Text style={styles.addText}>+ Add Company</Text>
|
<Building2 size={16} color={colors.buttonText} />
|
||||||
|
<AppText variant="bodySmall" color={colors.buttonText} style={{ fontWeight: '700', marginLeft: 8 }}>
|
||||||
|
Add Director
|
||||||
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
|
||||||
|
<View style={styles.footer}>
|
||||||
<PrimaryButton title={loading ? 'Loading...' : 'Submit'} onPress={handleSubmit} />
|
<PrimaryButton title={loading ? 'Loading...' : 'Submit'} onPress={handleSubmit} />
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StageThreeScreen;
|
export default StageThreeScreen;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const makeStyles = (colors: any, spacing: any, shadows: any, typography: any) => StyleSheet.create({
|
||||||
heading: {
|
header: {
|
||||||
fontSize: 18,
|
paddingHorizontal: spacing.m,
|
||||||
fontWeight: 'bold',
|
paddingBottom: spacing.m,
|
||||||
marginBottom: 10,
|
borderBottomWidth: 1,
|
||||||
marginTop: 10,
|
borderBottomColor: colors.border,
|
||||||
textAlign: 'center',
|
backgroundColor: colors.surface,
|
||||||
},
|
|
||||||
partnerTitle: {
|
|
||||||
fontWeight: '600',
|
|
||||||
fontSize: 16,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
},
|
||||||
scrollContainer: {
|
scrollContainer: {
|
||||||
// backgroundColor: color.background,
|
|
||||||
flexGrow: 1,
|
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: {
|
addButton: {
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
padding: 10,
|
flexDirection: 'row',
|
||||||
marginTop: 10,
|
|
||||||
borderRadius: 10,
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
width: 140,
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
borderRadius: 12,
|
||||||
alignSelf: 'flex-end',
|
alignSelf: 'flex-end',
|
||||||
},
|
},
|
||||||
addText: {
|
footer: {
|
||||||
color: 'white',
|
padding: spacing.m,
|
||||||
fontWeight: '600',
|
borderTopWidth: 1,
|
||||||
},
|
borderTopColor: colors.border,
|
||||||
deleteButton: {
|
backgroundColor: colors.surface,
|
||||||
backgroundColor: '#FF5C5C',
|
|
||||||
padding: 10,
|
|
||||||
marginTop: 10,
|
|
||||||
borderRadius: 10,
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 140,
|
|
||||||
alignSelf: 'flex-end',
|
|
||||||
},
|
|
||||||
deleteText: {
|
|
||||||
color: 'white',
|
|
||||||
fontWeight: '600',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableOpacity } from 'react-native';
|
import { View, TouchableOpacity } from 'react-native';
|
||||||
import {
|
import {
|
||||||
IdCard,
|
IdCard,
|
||||||
FileText,
|
FileText,
|
||||||
@@ -7,7 +7,12 @@ import {
|
|||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
Building2,
|
Building2,
|
||||||
ScrollText,
|
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 {
|
interface DocumentItem {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -30,13 +35,15 @@ const StageFour: React.FC<UploadDocumentsSectionProps> = ({
|
|||||||
formData,
|
formData,
|
||||||
selectedOrg,
|
selectedOrg,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colors, spacing, shadows } = useTheme();
|
||||||
|
|
||||||
const documentItems: DocumentItem[] = [
|
const documentItems: DocumentItem[] = [
|
||||||
{ key: 'panCard', label: 'PAN Card', icon: 'pan' },
|
{ key: 'panCard', label: 'PAN Card', icon: 'pan' },
|
||||||
{ key: 'aadharCard', label: 'Aadhar Card', icon: 'aadhar' },
|
{ key: 'aadharCard', label: 'Aadhar Card', icon: 'aadhar' },
|
||||||
{ key: 'gstnCertificate', label: 'GSTN Certificate', icon: 'gst' },
|
{ 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: '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 => {
|
const shouldRenderItem = (itemKey: string): boolean => {
|
||||||
@@ -52,54 +59,84 @@ const StageFour: React.FC<UploadDocumentsSectionProps> = ({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true; // pan + aadhar + rest (unless filtered above)
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderIcon = (iconType: DocumentItem['icon']) => {
|
const renderIcon = (iconType: DocumentItem['icon']) => {
|
||||||
const commonProps = { size: 26, color: '#fff', strokeWidth: 2 };
|
const commonProps = { size: 24, color: colors.buttonText, strokeWidth: 2 };
|
||||||
|
|
||||||
switch (iconType) {
|
switch (iconType) {
|
||||||
case 'pan':
|
case 'pan':
|
||||||
return <IdCard {...commonProps} />; // PAN → ID card feel [web:19]
|
return <IdCard {...commonProps} />;
|
||||||
case 'aadhar':
|
case 'aadhar':
|
||||||
return <IdCard {...commonProps} />; // Same icon, different label
|
return <IdCard {...commonProps} />;
|
||||||
case 'gst':
|
case 'gst':
|
||||||
return <FileBadge {...commonProps} />; // File with badge [web:20]
|
return <FileBadge {...commonProps} />;
|
||||||
case 'contractor':
|
case 'contractor':
|
||||||
return <ShieldCheck {...commonProps} />; // Safety / verification feel
|
return <ShieldCheck {...commonProps} />;
|
||||||
case 'msme':
|
case 'msme':
|
||||||
return <Building2 {...commonProps} />; // Organization / company
|
return <Building2 {...commonProps} />;
|
||||||
case 'registration':
|
case 'registration':
|
||||||
return <ScrollText {...commonProps} />; // Certificate / document scroll
|
return <ScrollText {...commonProps} />;
|
||||||
default:
|
default:
|
||||||
return <FileText {...commonProps} />;
|
return <FileText {...commonProps} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let animIndex = 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Text style={styles.sectionTitle}>Upload Required Documents</Text>
|
<AppText variant="h3" color={colors.text} style={{ fontWeight: '700', textAlign: 'center', marginBottom: spacing.m }}>
|
||||||
|
Upload Documents
|
||||||
|
</AppText>
|
||||||
|
|
||||||
<View style={styles.uploadGrid}>
|
<View style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}>
|
||||||
{documentItems.map(item => {
|
{documentItems.map(item => {
|
||||||
if (!shouldRenderItem(item.key)) return null;
|
if (!shouldRenderItem(item.key)) return null;
|
||||||
|
const isUploaded = !!documents[item.key];
|
||||||
|
const currentIndex = animIndex++;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<Animated.View
|
||||||
key={item.key}
|
key={item.key}
|
||||||
style={styles.uploadCard}
|
entering={FadeInUp.delay(80 * currentIndex).springify()}
|
||||||
onPress={() => handleDocumentPick(item.key)}
|
style={{ width: '50%', padding: spacing.xs }}
|
||||||
>
|
>
|
||||||
<View style={styles.iconCircle}>{renderIcon(item.icon)}</View>
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.uploadCard,
|
||||||
|
{ width: '100%' },
|
||||||
|
{
|
||||||
|
borderColor: isUploaded ? colors.primary : colors.border,
|
||||||
|
backgroundColor: isUploaded ? `${colors.primary}08` : colors.surface,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onPress={() => handleDocumentPick(item.key)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<View style={[styles.iconCircle, isUploaded && { backgroundColor: colors.primary }]}>
|
||||||
|
{isUploaded ? (
|
||||||
|
<CheckCircle size={24} color={colors.buttonText} strokeWidth={2.5} />
|
||||||
|
) : (
|
||||||
|
renderIcon(item.icon)
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
<Text style={styles.cardTitle}>{item.label}</Text>
|
<AppText variant="bodySmall" color={colors.text} style={{ fontWeight: '600', textAlign: 'center', marginBottom: 4 }}>
|
||||||
|
{item.label}
|
||||||
|
</AppText>
|
||||||
|
|
||||||
<Text style={styles.cardHint}>
|
<AppText variant="caption" color={isUploaded ? colors.primary : colors.textSecondary} style={{ textAlign: 'center', fontSize: 10 }}>
|
||||||
{documents[item.key]
|
{isUploaded
|
||||||
? `${documents[item.key]?.name} ✅`
|
? `${documents[item.key]?.name?.slice(0, 18)}...`
|
||||||
: 'Tap to upload PDF'}
|
: 'Tap to upload PDF'}
|
||||||
</Text>
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
</Animated.View>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Text, View, StyleSheet } from 'react-native';
|
import { View, TouchableOpacity } from 'react-native';
|
||||||
|
import DatePicker from 'react-native-date-picker';
|
||||||
import DatePicker from 'react-native-date-picker'
|
import MyInput from '../../../components/MyInput';
|
||||||
import MyInput from '../../../Component/MyInput';
|
|
||||||
import MyDropdown from '../../../Component/Dropdown/MyDropdown';
|
import MyDropdown from '../../../Component/Dropdown/MyDropdown';
|
||||||
import colors from '../../../Constant/Color';
|
|
||||||
import { DropdownOption } from '../types/types';
|
import { DropdownOption } from '../types/types';
|
||||||
|
import { useTheme } from '../../../Hooks/useTheme';
|
||||||
|
import AppText from '../../../components/AppText';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
formData: {
|
formData: {
|
||||||
@@ -14,7 +14,7 @@ interface Props {
|
|||||||
orgType: string;
|
orgType: string;
|
||||||
registrationDetail: string | number;
|
registrationDetail: string | number;
|
||||||
socialCategory: string;
|
socialCategory: string;
|
||||||
expiryDate: string
|
expiryDate: string;
|
||||||
};
|
};
|
||||||
handleChange: (key: string, value: string) => void;
|
handleChange: (key: string, value: string) => void;
|
||||||
focusedField: string | null;
|
focusedField: string | null;
|
||||||
@@ -26,7 +26,7 @@ interface Props {
|
|||||||
allMSME: DropdownOption[];
|
allMSME: DropdownOption[];
|
||||||
dropdownStyle: object;
|
dropdownStyle: object;
|
||||||
setSelectedOrg: (val: number) => void;
|
setSelectedOrg: (val: number) => void;
|
||||||
selectedOrg: string | number
|
selectedOrg: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StageOne: React.FC<Props> = ({
|
const StageOne: React.FC<Props> = ({
|
||||||
@@ -40,22 +40,27 @@ const StageOne: React.FC<Props> = ({
|
|||||||
allSocialCategories,
|
allSocialCategories,
|
||||||
dropdownStyle,
|
dropdownStyle,
|
||||||
setSelectedOrg,
|
setSelectedOrg,
|
||||||
selectedOrg
|
selectedOrg,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colors, spacing } = useTheme();
|
||||||
const [selectedAllMSME, setSelectedAllMSME] = useState()
|
const [selectedAllMSME, setSelectedAllMSME] = useState();
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false);
|
||||||
const [date, setDate] = useState(new Date())
|
const [date, setDate] = useState(new Date());
|
||||||
|
|
||||||
const handleDateConfirm = (selectedDate: Date) => {
|
const handleDateConfirm = (selectedDate: Date) => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
setDate(selectedDate);
|
setDate(selectedDate);
|
||||||
handleChange('expiryDate', selectedDate.toISOString().split('T')[0]); // Format as YYYY-MM-DD
|
const day = String(selectedDate.getDate()).padStart(2, '0');
|
||||||
|
const month = String(selectedDate.getMonth() + 1).padStart(2, '0');
|
||||||
|
const year = selectedDate.getFullYear();
|
||||||
|
handleChange('expiryDate', `${day}/${month}/${year}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<Text style={styles.label}>Registration & Type Details</Text>
|
<AppText variant="caption" color={colors.primary} style={{ fontWeight: '700', letterSpacing: 0.3, textTransform: 'uppercase', marginBottom: spacing.s }}>
|
||||||
|
Registration & Type Details
|
||||||
|
</AppText>
|
||||||
|
|
||||||
<MyDropdown
|
<MyDropdown
|
||||||
label="Type of Organization"
|
label="Type of Organization"
|
||||||
@@ -68,24 +73,26 @@ const StageOne: React.FC<Props> = ({
|
|||||||
setSelectedOrg(item.value);
|
setSelectedOrg(item.value);
|
||||||
handleChange('orgType', item.value);
|
handleChange('orgType', item.value);
|
||||||
}}
|
}}
|
||||||
style={dropdownStyle}
|
|
||||||
onFocus={() => setFocusedField('orgType')}
|
onFocus={() => setFocusedField('orgType')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focusedField={focusedField === 'orgType'}
|
focusedField={focusedField === 'orgType'}
|
||||||
/>
|
/>
|
||||||
{
|
|
||||||
selectedOrg !== 8 &&
|
{selectedOrg !== 8 && (
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="file"
|
label="Registration Number"
|
||||||
placeholder="Registration Number *"
|
placeholder="Enter Registration Number"
|
||||||
value={formData.registrationNumber}
|
value={formData.registrationNumber}
|
||||||
onChangeText={text => handleChange('registrationNumber', text)}
|
onChangeText={text => handleChange('registrationNumber', text)}
|
||||||
onFocus={() => setFocusedField('registrationNumber')}
|
onFocus={() => setFocusedField('registrationNumber')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'registrationNumber'}
|
focused={focusedField === 'registrationNumber'}
|
||||||
autoCapitalize='characters' />
|
autoCapitalize="characters"
|
||||||
}
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
{selectedOrg &&
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedOrg && (
|
||||||
<MyDropdown
|
<MyDropdown
|
||||||
label="Contractor Class"
|
label="Contractor Class"
|
||||||
data={contractorClassItems}
|
data={contractorClassItems}
|
||||||
@@ -93,7 +100,6 @@ const StageOne: React.FC<Props> = ({
|
|||||||
valueField="value"
|
valueField="value"
|
||||||
placeholder="Select Class"
|
placeholder="Select Class"
|
||||||
value={selectedOrg == 8 ? contractorClassItems[0]?.value : formData.contractorClass}
|
value={selectedOrg == 8 ? contractorClassItems[0]?.value : formData.contractorClass}
|
||||||
style={dropdownStyle}
|
|
||||||
disable={selectedOrg == 8 ? true : false}
|
disable={selectedOrg == 8 ? true : false}
|
||||||
onChange={item => {
|
onChange={item => {
|
||||||
handleChange('contractorClass', item.value);
|
handleChange('contractorClass', item.value);
|
||||||
@@ -101,9 +107,10 @@ const StageOne: React.FC<Props> = ({
|
|||||||
onFocus={() => setFocusedField('contractorClass')}
|
onFocus={() => setFocusedField('contractorClass')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focusedField={focusedField === 'contractorClass'}
|
focusedField={focusedField === 'contractorClass'}
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{selectedOrg !== 8 &&
|
{selectedOrg !== 8 && (
|
||||||
<MyDropdown
|
<MyDropdown
|
||||||
label="Registration Detail (MSME/NSIC etc.)"
|
label="Registration Detail (MSME/NSIC etc.)"
|
||||||
data={allMsmeOrgs}
|
data={allMsmeOrgs}
|
||||||
@@ -112,32 +119,15 @@ const StageOne: React.FC<Props> = ({
|
|||||||
placeholder="Select Registration Type"
|
placeholder="Select Registration Type"
|
||||||
value={formData.registrationDetail}
|
value={formData.registrationDetail}
|
||||||
onChange={item => {
|
onChange={item => {
|
||||||
setSelectedAllMSME(item.value)
|
setSelectedAllMSME(item.value);
|
||||||
console.log('first', item)
|
handleChange('registrationDetail', item.value);
|
||||||
handleChange('registrationDetail', item.value)
|
|
||||||
}}
|
}}
|
||||||
style={dropdownStyle}
|
|
||||||
onFocus={() => setFocusedField('registrationDetail')}
|
onFocus={() => setFocusedField('registrationDetail')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focusedField={focusedField === 'registrationDetail'}
|
focusedField={focusedField === 'registrationDetail'}
|
||||||
/>}
|
/>
|
||||||
{/* <MyDropdown
|
)}
|
||||||
label="MSME Details"
|
|
||||||
data={allMSME}
|
|
||||||
labelField="label"
|
|
||||||
valueField="value"
|
|
||||||
placeholder="Select MSME Type"
|
|
||||||
value={''}
|
|
||||||
onChange={item => {
|
|
||||||
setSelectedAllMSME(item.value)
|
|
||||||
handleChange('allMsme', item.value)
|
|
||||||
}}
|
|
||||||
style={dropdownStyle}
|
|
||||||
|
|
||||||
onFocus={() => setFocusedField('allMsme')}
|
|
||||||
onBlur={() => setFocusedField(null)}
|
|
||||||
focusedField={focusedField === 'allMsme'}
|
|
||||||
/> */}
|
|
||||||
<MyDropdown
|
<MyDropdown
|
||||||
label="Social Category"
|
label="Social Category"
|
||||||
data={allSocialCategories}
|
data={allSocialCategories}
|
||||||
@@ -146,50 +136,39 @@ const StageOne: React.FC<Props> = ({
|
|||||||
placeholder="Select Social Category"
|
placeholder="Select Social Category"
|
||||||
value={formData.socialCategory}
|
value={formData.socialCategory}
|
||||||
onChange={item => handleChange('socialCategory', item.value)}
|
onChange={item => handleChange('socialCategory', item.value)}
|
||||||
style={dropdownStyle}
|
|
||||||
|
|
||||||
onFocus={() => setFocusedField('socialCategory')}
|
onFocus={() => setFocusedField('socialCategory')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focusedField={focusedField === 'socialCategory'}
|
focusedField={focusedField === 'socialCategory'}
|
||||||
/>
|
/>
|
||||||
{
|
|
||||||
selectedAllMSME !== 3 && selectedOrg !== 8 &&
|
|
||||||
<>
|
|
||||||
|
|
||||||
|
{selectedAllMSME !== 3 && selectedOrg !== 8 && (
|
||||||
|
<>
|
||||||
|
<TouchableOpacity onPress={() => setOpen(true)} activeOpacity={0.8}>
|
||||||
|
<View pointerEvents="none">
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="calendar-alt"
|
label="Expiry Date"
|
||||||
placeholder="Expiry Date *"
|
placeholder="Select Expiry Date"
|
||||||
value={formData.expiryDate}
|
value={formData.expiryDate}
|
||||||
onChangeText={text => handleChange('expiryDate', text)}
|
onChangeText={text => handleChange('expiryDate', text)}
|
||||||
onFocus={() => setFocusedField('expiryDate')}
|
onFocus={() => setFocusedField('expiryDate')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'expiryDate'}
|
focused={open || focusedField === 'expiryDate'}
|
||||||
rightIcon='wallet'
|
editable={false}
|
||||||
editable={true}
|
|
||||||
rightIconPress={() => setOpen(true)}
|
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
modal
|
modal
|
||||||
open={open}
|
open={open}
|
||||||
date={date}
|
date={date}
|
||||||
onConfirm={handleDateConfirm}
|
onConfirm={handleDateConfirm}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
mode="date" // Ensure you're using date mode
|
mode="date"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
label: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: '600',
|
|
||||||
color: colors.black,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default StageOne;
|
export default StageOne;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { DropdownOption } from '../types/types';
|
import { DropdownOption } from '../types/types';
|
||||||
import MyInput from '../../../Component/MyInput';
|
import MyInput from '../../../components/MyInput';
|
||||||
import MyDropdown from '../../../Component/Dropdown/MyDropdown';
|
import MyDropdown from '../../../Component/Dropdown/MyDropdown';
|
||||||
|
import { useTheme } from '../../../Hooks/useTheme';
|
||||||
|
import AppText from '../../../components/AppText';
|
||||||
|
|
||||||
interface AddressDetailsSectionProps {
|
interface AddressDetailsSectionProps {
|
||||||
formData: {
|
formData: {
|
||||||
@@ -21,7 +21,7 @@ interface AddressDetailsSectionProps {
|
|||||||
cities: DropdownOption[];
|
cities: DropdownOption[];
|
||||||
fetchCity: (stateValue: number) => void;
|
fetchCity: (stateValue: number) => void;
|
||||||
dropdownStyle?: object;
|
dropdownStyle?: object;
|
||||||
styles?: any; // Optional styles prop for custom styling
|
styles?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StageThree: React.FC<AddressDetailsSectionProps> = ({
|
const StageThree: React.FC<AddressDetailsSectionProps> = ({
|
||||||
@@ -32,54 +32,51 @@ const StageThree: React.FC<AddressDetailsSectionProps> = ({
|
|||||||
states,
|
states,
|
||||||
cities,
|
cities,
|
||||||
fetchCity,
|
fetchCity,
|
||||||
styles
|
styles,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colors, spacing } = useTheme();
|
||||||
|
|
||||||
const dropdownStyle = {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#e0e0e0',
|
|
||||||
borderRadius: 12,
|
|
||||||
paddingHorizontal: 14,
|
|
||||||
height: 50,
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
marginBottom: 10,
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Text style={styles.label}>Address Details</Text>
|
<AppText variant="caption" color={colors.primary} style={styles.label}>
|
||||||
|
Address Details
|
||||||
|
</AppText>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="home"
|
label="Address"
|
||||||
placeholder="Enter Address *"
|
placeholder="Enter Full Address"
|
||||||
value={formData.address}
|
value={formData.address}
|
||||||
onChangeText={text => handleChange('address', text)}
|
onChangeText={text => handleChange('address', text)}
|
||||||
onFocus={() => setFocusedField('address')}
|
onFocus={() => setFocusedField('address')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'address'}
|
focused={focusedField === 'address'}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
maxlength={10}
|
label="Contact Number"
|
||||||
icon="phone"
|
maxLength={10}
|
||||||
placeholder="Contact Number *"
|
placeholder="Enter Contact Number"
|
||||||
value={formData.agentContact2}
|
value={formData.agentContact2}
|
||||||
onChangeText={text => handleChange('agentContact2', text)}
|
onChangeText={text => handleChange('agentContact2', text)}
|
||||||
onFocus={() => setFocusedField('agentContact2')}
|
onFocus={() => setFocusedField('agentContact2')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'agentContact2'}
|
focused={focusedField === 'agentContact2'}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="lock"
|
label="Pin Code"
|
||||||
placeholder="Pin Code *"
|
placeholder="Enter 6-digit Pin Code"
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
maxlength={6}
|
maxLength={6}
|
||||||
value={formData.pinCode}
|
value={formData.pinCode}
|
||||||
onChangeText={text => handleChange('pinCode', text)}
|
onChangeText={text => handleChange('pinCode', text)}
|
||||||
onFocus={() => setFocusedField('pinCode')}
|
onFocus={() => setFocusedField('pinCode')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'pinCode'}
|
focused={focusedField === 'pinCode'}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyDropdown
|
<MyDropdown
|
||||||
@@ -94,7 +91,6 @@ const StageThree: React.FC<AddressDetailsSectionProps> = ({
|
|||||||
handleChange('city', '');
|
handleChange('city', '');
|
||||||
fetchCity(item.value);
|
fetchCity(item.value);
|
||||||
}}
|
}}
|
||||||
style={dropdownStyle}
|
|
||||||
onFocus={() => setFocusedField('state')}
|
onFocus={() => setFocusedField('state')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focusedField={focusedField === 'state'}
|
focusedField={focusedField === 'state'}
|
||||||
@@ -108,7 +104,6 @@ const StageThree: React.FC<AddressDetailsSectionProps> = ({
|
|||||||
placeholder="Select City"
|
placeholder="Select City"
|
||||||
value={formData.city}
|
value={formData.city}
|
||||||
onChange={item => handleChange('city', item.value)}
|
onChange={item => handleChange('city', item.value)}
|
||||||
style={dropdownStyle}
|
|
||||||
onFocus={() => setFocusedField('city')}
|
onFocus={() => setFocusedField('city')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focusedField={focusedField === 'city'}
|
focusedField={focusedField === 'city'}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import DatePicker from 'react-native-date-picker';
|
||||||
import {
|
import {
|
||||||
Text,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
View,
|
View,
|
||||||
|
Alert,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import MyInput from '../../../Component/MyInput';
|
import MyInput from '../../../components/MyInput';
|
||||||
|
import { useTheme } from '../../../Hooks/useTheme';
|
||||||
// Adjust the path to your custom input
|
import AppText from '../../../components/AppText';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
formData: {
|
formData: {
|
||||||
@@ -21,12 +22,10 @@ interface Props {
|
|||||||
handleChange: (key: string, value: string) => void;
|
handleChange: (key: string, value: string) => void;
|
||||||
focusedField: string | null;
|
focusedField: string | null;
|
||||||
setFocusedField: (field: string | null | any) => void;
|
setFocusedField: (field: string | null | any) => void;
|
||||||
// PAN
|
|
||||||
panVerified: boolean;
|
panVerified: boolean;
|
||||||
setPanVerified: (value: boolean) => void;
|
setPanVerified: (value: boolean) => void;
|
||||||
verifyPanHandler: () => void;
|
verifyPanHandler: () => void;
|
||||||
panVerifying: boolean;
|
panVerifying: boolean;
|
||||||
// Aadhaar
|
|
||||||
aadhaarVerified: boolean;
|
aadhaarVerified: boolean;
|
||||||
setAadhaarVerified: (value: boolean) => void;
|
setAadhaarVerified: (value: boolean) => void;
|
||||||
sendAdharOTPHandler: (aadhaarNumber: string) => void;
|
sendAdharOTPHandler: (aadhaarNumber: string) => void;
|
||||||
@@ -34,13 +33,12 @@ interface Props {
|
|||||||
setadharotpsend: (value: boolean) => void;
|
setadharotpsend: (value: boolean) => void;
|
||||||
verifyAdharOtpHandler: () => void;
|
verifyAdharOtpHandler: () => void;
|
||||||
aadhaarVerifying: boolean;
|
aadhaarVerifying: boolean;
|
||||||
// GST
|
|
||||||
verifyGSTHandler: () => void;
|
verifyGSTHandler: () => void;
|
||||||
gstverifying: boolean;
|
gstverifying: boolean;
|
||||||
color: any;
|
color: any;
|
||||||
styles: any;
|
styles: any;
|
||||||
formatDateInput: (text: string) => string;
|
formatDateInput: (text: string) => string;
|
||||||
selectedOrg:string|number
|
selectedOrg: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StageTwo: React.FC<Props> = ({
|
const StageTwo: React.FC<Props> = ({
|
||||||
@@ -64,61 +62,107 @@ const StageTwo: React.FC<Props> = ({
|
|||||||
color,
|
color,
|
||||||
styles,
|
styles,
|
||||||
formatDateInput,
|
formatDateInput,
|
||||||
selectedOrg
|
selectedOrg,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colors, spacing, shadows } = useTheme();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [date, setDate] = useState(new Date());
|
||||||
|
|
||||||
|
const handleDateConfirm = (selectedDate: Date) => {
|
||||||
|
setOpen(false);
|
||||||
|
setDate(selectedDate);
|
||||||
|
const day = String(selectedDate.getDate()).padStart(2, '0');
|
||||||
|
const month = String(selectedDate.getMonth() + 1).padStart(2, '0');
|
||||||
|
const year = selectedDate.getFullYear();
|
||||||
|
setPanVerified(false);
|
||||||
|
handleChange('dateOfIncorp', `${day}/${month}/${year}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* ---------- PAN Details ---------- */}
|
{/* ── PAN Details ── */}
|
||||||
<Text style={styles.label}>PAN Details</Text>
|
<AppText variant="caption" color={colors.primary} style={styles.label}>
|
||||||
|
PAN Details
|
||||||
|
</AppText>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="file"
|
label={selectedOrg == 8 ? "PAN Number" : "Organization PAN Number"}
|
||||||
placeholder={selectedOrg == 8 ? "Pan Number *" : "Organization PAN Number *"}
|
placeholder={selectedOrg == 8 ? "Enter PAN Number" : "Enter Organization PAN"}
|
||||||
value={formData.orgPanNumber}
|
value={formData.orgPanNumber}
|
||||||
onChangeText={(text) => {
|
onChangeText={(text) => {
|
||||||
setPanVerified(false);
|
setPanVerified(false);
|
||||||
handleChange('orgPanNumber', text);
|
handleChange('orgPanNumber', text);
|
||||||
}}
|
}}
|
||||||
isVarify={panVerified}
|
isVerify={panVerified}
|
||||||
onFocus={() => setFocusedField('pan')}
|
onFocus={() => setFocusedField('pan')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'pan'}
|
focused={focusedField === 'pan'}
|
||||||
editable={!panVerified}
|
editable={!panVerified}
|
||||||
autoCapitalize="characters"
|
autoCapitalize="characters"
|
||||||
maxlength={10}
|
maxLength={10}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => !panVerified && setOpen(true)}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
disabled={panVerified}
|
||||||
|
>
|
||||||
|
<View pointerEvents="none">
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="calendar"
|
label={selectedOrg == 8 ? "Date of Birth" : "Date of Incorporation"}
|
||||||
placeholder={selectedOrg == 8 ? "Date of Birth *" :"Date of Incorporation *"}
|
placeholder="DD/MM/YYYY"
|
||||||
value={formData.dateOfIncorp}
|
value={formData.dateOfIncorp}
|
||||||
onChangeText={(text) => {
|
onChangeText={(text) => {
|
||||||
const formatted = formatDateInput(text);
|
const formatted = formatDateInput(text);
|
||||||
setPanVerified(false);
|
setPanVerified(false);
|
||||||
handleChange('dateOfIncorp', formatted);
|
handleChange('dateOfIncorp', formatted);
|
||||||
}}
|
}}
|
||||||
isVarify={panVerified}
|
isVerify={panVerified}
|
||||||
onFocus={() => setFocusedField('dob')}
|
onFocus={() => setFocusedField('dob')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'dob'}
|
focused={open || focusedField === 'dob'}
|
||||||
editable={!panVerified}
|
editable={false}
|
||||||
keyboardType='numeric'
|
keyboardType="numeric"
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<DatePicker
|
||||||
|
modal
|
||||||
|
open={open}
|
||||||
|
date={date}
|
||||||
|
onConfirm={handleDateConfirm}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
mode="date"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="file-signature"
|
label="PAN Name"
|
||||||
placeholder="PAN Name *"
|
placeholder="Enter Name as on PAN"
|
||||||
value={formData.orgPanName}
|
value={formData.orgPanName}
|
||||||
onChangeText={(text) => {
|
onChangeText={(text) => {
|
||||||
setPanVerified(false);
|
setPanVerified(false);
|
||||||
handleChange('orgPanName', text);
|
handleChange('orgPanName', text);
|
||||||
}}
|
}}
|
||||||
isVarify={panVerified}
|
isVerify={panVerified}
|
||||||
onFocus={() => setFocusedField('panName')}
|
onFocus={() => setFocusedField('panName')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'panName'}
|
focused={focusedField === 'panName'}
|
||||||
editable={!panVerified}
|
editable={!panVerified}
|
||||||
autoCapitalize="characters"
|
autoCapitalize="characters"
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{formData.orgPanNumber &&
|
{formData.orgPanNumber &&
|
||||||
@@ -126,32 +170,69 @@ const StageTwo: React.FC<Props> = ({
|
|||||||
formData.orgPanName &&
|
formData.orgPanName &&
|
||||||
!panVerified && (
|
!panVerified && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{
|
style={verifyButtonStyle}
|
||||||
backgroundColor: color.primary,
|
|
||||||
padding: 10,
|
|
||||||
marginTop: 10,
|
|
||||||
borderRadius: 10,
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 120,
|
|
||||||
alignSelf: 'flex-end',
|
|
||||||
}}
|
|
||||||
disabled={panVerifying}
|
disabled={panVerifying}
|
||||||
onPress={verifyPanHandler}
|
onPress={verifyPanHandler}
|
||||||
|
activeOpacity={0.8}
|
||||||
>
|
>
|
||||||
<Text style={{ color: 'white', fontWeight: '600' }}>
|
<AppText variant="bodySmall" color={colors.buttonText} style={{ fontWeight: '700' }}>
|
||||||
{panVerifying ? 'Verifying' : 'Verify PAN'}
|
{panVerifying ? 'Verifying...' : 'Verify PAN'}
|
||||||
</Text>
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ---------- Aadhaar Details ---------- */}
|
{panVerified && (
|
||||||
<Text style={styles.label}>Aadhaar Details</Text>
|
<TouchableOpacity
|
||||||
|
style={{
|
||||||
|
borderWidth: 1.5,
|
||||||
|
borderColor: '#E67E22',
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
marginTop: spacing.s,
|
||||||
|
borderRadius: 10,
|
||||||
|
alignItems: 'center' as const,
|
||||||
|
alignSelf: 'flex-end' as const,
|
||||||
|
minWidth: 120,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
}}
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={'#E67E22'} style={{ fontWeight: '700' }}>
|
||||||
|
Change PAN
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ── Aadhaar Details ── */}
|
||||||
|
<View style={{ marginTop: spacing.m, borderTopWidth: 1, borderTopColor: colors.border, paddingTop: spacing.m }}>
|
||||||
|
<AppText variant="caption" color={colors.primary} style={styles.label}>
|
||||||
|
Aadhaar Details
|
||||||
|
</AppText>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="address-card"
|
label="Aadhaar Number"
|
||||||
placeholder="Aadhaar Number *"
|
placeholder="Enter 12-digit Aadhaar Number"
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
maxlength={12}
|
maxLength={12}
|
||||||
value={formData.aadhaarNumber}
|
value={formData.aadhaarNumber}
|
||||||
onChangeText={(text) => {
|
onChangeText={(text) => {
|
||||||
handleChange('aadhaarNumber', text);
|
handleChange('aadhaarNumber', text);
|
||||||
@@ -159,31 +240,27 @@ const StageTwo: React.FC<Props> = ({
|
|||||||
setadharotpsend(false);
|
setadharotpsend(false);
|
||||||
handleChange('aadhaarOtp', '');
|
handleChange('aadhaarOtp', '');
|
||||||
}}
|
}}
|
||||||
isVarify={aadhaarVerified}
|
isVerify={aadhaarVerified}
|
||||||
onFocus={() => setFocusedField('adhar')}
|
onFocus={() => setFocusedField('adhar')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'adhar'}
|
focused={focusedField === 'adhar'}
|
||||||
editable={!aadhaarVerified}
|
editable={!aadhaarVerified}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{formData.aadhaarNumber.length === 12 && !adharotpsend && (
|
{formData.aadhaarNumber.length === 12 && !adharotpsend && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{
|
style={verifyButtonStyle}
|
||||||
backgroundColor: color.primary,
|
|
||||||
padding: 10,
|
|
||||||
marginTop: 10,
|
|
||||||
borderRadius: 10,
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 120,
|
|
||||||
alignSelf: 'flex-end',
|
|
||||||
}}
|
|
||||||
onPress={() => sendAdharOTPHandler(formData.aadhaarNumber)}
|
onPress={() => sendAdharOTPHandler(formData.aadhaarNumber)}
|
||||||
disabled={aadhaarVerifying}
|
disabled={aadhaarVerifying}
|
||||||
|
activeOpacity={0.8}
|
||||||
>
|
>
|
||||||
{aadhaarVerifying ? (
|
{aadhaarVerifying ? (
|
||||||
<ActivityIndicator color="#fff" />
|
<ActivityIndicator color={colors.buttonText} size="small" />
|
||||||
) : (
|
) : (
|
||||||
<Text style={{ color: 'white', fontWeight: '600' }}>Send OTP</Text>
|
<AppText variant="bodySmall" color={colors.buttonText} style={{ fontWeight: '700' }}>
|
||||||
|
Send OTP
|
||||||
|
</AppText>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
@@ -191,43 +268,78 @@ const StageTwo: React.FC<Props> = ({
|
|||||||
{adharotpsend && !aadhaarVerified && (
|
{adharotpsend && !aadhaarVerified && (
|
||||||
<>
|
<>
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="lock"
|
label="OTP"
|
||||||
placeholder="Enter OTP *"
|
placeholder="Enter 6-digit OTP"
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
maxlength={6}
|
maxLength={6}
|
||||||
value={formData.aadhaarOtp}
|
value={formData.aadhaarOtp}
|
||||||
onChangeText={(text) => handleChange('aadhaarOtp', text)}
|
onChangeText={(text) => handleChange('aadhaarOtp', text)}
|
||||||
onFocus={() => setFocusedField('adharOtp')}
|
onFocus={() => setFocusedField('adharOtp')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'adharOtp'}
|
focused={focusedField === 'adharOtp'}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{
|
style={verifyButtonStyle}
|
||||||
backgroundColor: color.primary,
|
|
||||||
padding: 10,
|
|
||||||
marginTop: 10,
|
|
||||||
borderRadius: 10,
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 120,
|
|
||||||
alignSelf: 'flex-end',
|
|
||||||
}}
|
|
||||||
onPress={verifyAdharOtpHandler}
|
onPress={verifyAdharOtpHandler}
|
||||||
disabled={aadhaarVerifying}
|
disabled={aadhaarVerifying}
|
||||||
|
activeOpacity={0.8}
|
||||||
>
|
>
|
||||||
<Text style={{ color: 'white', fontWeight: '600' }}>
|
<AppText variant="bodySmall" color={colors.buttonText} style={{ fontWeight: '700' }}>
|
||||||
{aadhaarVerifying ? 'Verifying...' : 'Verify'}
|
{aadhaarVerifying ? 'Verifying...' : 'Verify OTP'}
|
||||||
</Text>
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ---------- GST Section ---------- */}
|
{aadhaarVerified && (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={{
|
||||||
|
borderWidth: 1.5,
|
||||||
|
borderColor: '#E67E22',
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
marginTop: spacing.s,
|
||||||
|
borderRadius: 10,
|
||||||
|
alignItems: 'center' as const,
|
||||||
|
alignSelf: 'flex-end' as const,
|
||||||
|
minWidth: 120,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
}}
|
||||||
|
onPress={() => {
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={'#E67E22'} style={{ fontWeight: '700' }}>
|
||||||
|
Change Aadhaar
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
{selectedOrg !== 11 &&
|
{/* ── GST Section ── */}
|
||||||
<>
|
{selectedOrg !== 11 && (
|
||||||
|
<View style={{ marginTop: spacing.m, borderTopWidth: 1, borderTopColor: colors.border, paddingTop: spacing.m }}>
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="file-invoice"
|
label="GSTN Number"
|
||||||
placeholder="GSTN No"
|
placeholder="Enter GSTN Number"
|
||||||
value={formData.gstnNo}
|
value={formData.gstnNo}
|
||||||
onChangeText={(text) => handleChange('gstnNo', text)}
|
onChangeText={(text) => handleChange('gstnNo', text)}
|
||||||
onFocus={() => setFocusedField('gstNumber')}
|
onFocus={() => setFocusedField('gstNumber')}
|
||||||
@@ -235,30 +347,10 @@ const StageTwo: React.FC<Props> = ({
|
|||||||
focused={focusedField === 'gstNumber'}
|
focused={focusedField === 'gstNumber'}
|
||||||
autoCapitalize="characters"
|
autoCapitalize="characters"
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
{/* {formData?.gstnNo?.length === 15 && (
|
)}
|
||||||
<TouchableOpacity
|
|
||||||
style={{
|
|
||||||
backgroundColor: color.primary,
|
|
||||||
padding: 10,
|
|
||||||
marginTop: 10,
|
|
||||||
borderRadius: 10,
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 120,
|
|
||||||
alignSelf: 'flex-end',
|
|
||||||
}}
|
|
||||||
onPress={verifyGSTHandler}
|
|
||||||
>
|
|
||||||
<Text style={{ color: 'white', fontWeight: '600' }}>
|
|
||||||
{gstverifying ? 'Verifying...' : 'Verify GST'}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)} */}
|
|
||||||
</>}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StageTwo;
|
export default StageTwo;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { Text, View, StyleSheet } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import MyInput from '../../../Component/MyInput';
|
import MyInput from '../../../components/MyInput';
|
||||||
|
import { useTheme } from '../../../Hooks/useTheme';
|
||||||
|
import AppText from '../../../components/AppText';
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
formData: {
|
formData: {
|
||||||
|
|
||||||
agentContact2: string;
|
agentContact2: string;
|
||||||
organizationName: string;
|
organizationName: string;
|
||||||
officialWebsite: string;
|
officialWebsite: string;
|
||||||
@@ -14,7 +14,7 @@ interface Props {
|
|||||||
email: string;
|
email: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
middleName: string;
|
middleName: string;
|
||||||
lastName: string
|
lastName: string;
|
||||||
};
|
};
|
||||||
handleChange: (key: string, value: string) => void;
|
handleChange: (key: string, value: string) => void;
|
||||||
focusedField: string | null;
|
focusedField: string | null;
|
||||||
@@ -29,27 +29,29 @@ const StageZero: React.FC<Props> = ({
|
|||||||
setFocusedField,
|
setFocusedField,
|
||||||
styles,
|
styles,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colors, spacing } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Text style={styles.label}>Personal & Contact Details</Text>
|
<AppText variant="caption" color={colors.primary} style={styles.label}>
|
||||||
{/* <View style={styles.row}> */}
|
Personal & Contact Details
|
||||||
|
</AppText>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="user"
|
label="First Name"
|
||||||
placeholder="First Name *"
|
placeholder="First Name"
|
||||||
value={formData.firstName}
|
value={formData.firstName}
|
||||||
onChangeText={(text: string) => handleChange('firstName', text)}
|
onChangeText={(text: string) => handleChange('firstName', text)}
|
||||||
onFocus={() => setFocusedField('firstname')}
|
onFocus={() => setFocusedField('firstname')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'firstname'}
|
focused={focusedField === 'firstname'}
|
||||||
editable={false}
|
editable={false}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
<View style={{ width: 10 }} />
|
|
||||||
{
|
|
||||||
formData.middleName ?
|
|
||||||
|
|
||||||
|
{formData.middleName ? (
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="user"
|
label="Middle Name"
|
||||||
placeholder="Middle Name"
|
placeholder="Middle Name"
|
||||||
value={formData.middleName}
|
value={formData.middleName}
|
||||||
onChangeText={(text: string) => handleChange('middleName', text)}
|
onChangeText={(text: string) => handleChange('middleName', text)}
|
||||||
@@ -57,24 +59,25 @@ const StageZero: React.FC<Props> = ({
|
|||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'middleName'}
|
focused={focusedField === 'middleName'}
|
||||||
editable={false}
|
editable={false}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
: null}
|
) : null}
|
||||||
|
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="user"
|
label="Last Name"
|
||||||
placeholder="Last Name *"
|
placeholder="Last Name"
|
||||||
value={formData.lastName}
|
value={formData.lastName}
|
||||||
onChangeText={(text: string) => handleChange('lastName', text)}
|
onChangeText={(text: string) => handleChange('lastName', text)}
|
||||||
onFocus={() => setFocusedField('lastName')}
|
onFocus={() => setFocusedField('lastName')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'lastName'}
|
focused={focusedField === 'lastName'}
|
||||||
editable={false}
|
editable={false}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="envelope"
|
label="Email"
|
||||||
placeholder="Email *"
|
placeholder="Email Address"
|
||||||
keyboardType="email-address"
|
keyboardType="email-address"
|
||||||
value={formData.email}
|
value={formData.email}
|
||||||
onChangeText={(text: string) => handleChange('email', text)}
|
onChangeText={(text: string) => handleChange('email', text)}
|
||||||
@@ -82,11 +85,12 @@ const StageZero: React.FC<Props> = ({
|
|||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'email'}
|
focused={focusedField === 'email'}
|
||||||
editable={false}
|
editable={false}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="phone"
|
label="Mobile Number"
|
||||||
placeholder="Mobile Number *"
|
placeholder="Mobile Number"
|
||||||
keyboardType="phone-pad"
|
keyboardType="phone-pad"
|
||||||
value={formData.mobileNumber}
|
value={formData.mobileNumber}
|
||||||
onChangeText={(text: string) => handleChange('mobileNumber', text)}
|
onChangeText={(text: string) => handleChange('mobileNumber', text)}
|
||||||
@@ -94,22 +98,24 @@ const StageZero: React.FC<Props> = ({
|
|||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'mobileNumber'}
|
focused={focusedField === 'mobileNumber'}
|
||||||
editable={false}
|
editable={false}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="building"
|
label="Organization Name"
|
||||||
placeholder="Organization Name *"
|
placeholder="Enter Organization Name"
|
||||||
value={formData.organizationName}
|
value={formData.organizationName}
|
||||||
onChangeText={(text: string) => handleChange('organizationName', text)}
|
onChangeText={(text: string) => handleChange('organizationName', text)}
|
||||||
onFocus={() => setFocusedField('organizationName')}
|
onFocus={() => setFocusedField('organizationName')}
|
||||||
onBlur={() => setFocusedField(null)}
|
onBlur={() => setFocusedField(null)}
|
||||||
focused={focusedField === 'organizationName'}
|
focused={focusedField === 'organizationName'}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MyInput
|
<MyInput
|
||||||
icon="globe"
|
label="Official Website"
|
||||||
placeholder="Official Website (optional)"
|
placeholder="Website (optional)"
|
||||||
value={formData.officialWebsite}
|
value={formData.officialWebsite}
|
||||||
onChangeText={(text: string) => handleChange('officialWebsite', text)}
|
onChangeText={(text: string) => handleChange('officialWebsite', text)}
|
||||||
onFocus={() => setFocusedField('officialWebsite')}
|
onFocus={() => setFocusedField('officialWebsite')}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
|
||||||
TextInput,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Alert,
|
Alert,
|
||||||
@@ -13,16 +11,18 @@ import {
|
|||||||
ScrollView,
|
ScrollView,
|
||||||
ToastAndroid,
|
ToastAndroid,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import colors from '../../Constant/Color';
|
import { User, Phone, Mail, Lock, CheckSquare, Square, Shield, UserPlus, Contact, Eye, EyeOff } from 'lucide-react-native';
|
||||||
import { User, Phone, Mail, Lock, Eye, EyeOff, ArrowLeft, CheckSquare, Square } from 'lucide-react-native';
|
import { useGetDesignation, useRegisterContractor } from '../../Services/ApiCall/Hooks';
|
||||||
import { useGetDesignation, useRegisterContractor } from '../../Services/BBPS/Hooks';
|
|
||||||
import MyDropdown from '../../Component/Dropdown/MyDropdown';
|
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 TermsAndConditionsModal from '../../Component/TermsAndConditionsModal/TermsAndConditionsModal';
|
||||||
|
import AppText from '../../components/AppText';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import Animated, { FadeInUp } from 'react-native-reanimated';
|
import Animated, { FadeInUp, FadeInDown } from 'react-native-reanimated';
|
||||||
import { InsideHeader } from '../../Component/Index';
|
import { useTheme } from '../../Hooks/useTheme';
|
||||||
|
|
||||||
export type ContractorFormData = {
|
export type ContractorFormData = {
|
||||||
firstName: string;
|
firstName: string;
|
||||||
@@ -40,7 +40,7 @@ export type ContractorFormData = {
|
|||||||
|
|
||||||
type DesignationOption = {
|
type DesignationOption = {
|
||||||
label: string;
|
label: string;
|
||||||
value: string; // string id from API
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SignupSchema = Yup.object().shape({
|
const SignupSchema = Yup.object().shape({
|
||||||
@@ -48,7 +48,7 @@ const SignupSchema = Yup.object().shape({
|
|||||||
firstName: Yup.string().required('First name is required.'),
|
firstName: Yup.string().required('First name is required.'),
|
||||||
middleName: Yup.string().optional(),
|
middleName: Yup.string().optional(),
|
||||||
lastName: Yup.string().required('Last name is required.'),
|
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()
|
mobileNumber: Yup.string()
|
||||||
.matches(/^[6-9]\d{9}$/, 'Invalid mobile number.')
|
.matches(/^[6-9]\d{9}$/, 'Invalid mobile number.')
|
||||||
.required('Mobile number is required.'),
|
.required('Mobile number is required.'),
|
||||||
@@ -65,6 +65,7 @@ const SignupSchema = Yup.object().shape({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const Signup = () => {
|
const Signup = () => {
|
||||||
|
const { colors, spacing, typography, shadows } = useTheme();
|
||||||
const navigation = useNavigation<any>();
|
const navigation = useNavigation<any>();
|
||||||
const [designation, setDesignation] = useState<DesignationOption[]>([]);
|
const [designation, setDesignation] = useState<DesignationOption[]>([]);
|
||||||
const { data, isLoading: isDesignationLoading } = useGetDesignation();
|
const { data, isLoading: isDesignationLoading } = useGetDesignation();
|
||||||
@@ -74,6 +75,7 @@ const Signup = () => {
|
|||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
|
|
||||||
|
const styles = useMemo(() => makeStyles(colors, spacing, shadows, typography), [colors, spacing, shadows, typography]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const list = (data as any)?.data;
|
const list = (data as any)?.data;
|
||||||
@@ -81,89 +83,15 @@ const Signup = () => {
|
|||||||
|
|
||||||
const formattedDesignation: DesignationOption[] = list.map((item: any) => ({
|
const formattedDesignation: DesignationOption[] = list.map((item: any) => ({
|
||||||
label: item.designationName,
|
label: item.designationName,
|
||||||
value: String(item.designationid), // e.g. "3"
|
value: String(item.designationid),
|
||||||
}));
|
}));
|
||||||
setDesignation(formattedDesignation);
|
setDesignation(formattedDesignation);
|
||||||
|
|
||||||
}, [data]);
|
}, [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 (
|
|
||||||
<>
|
|
||||||
<View style={styles.inputWrapper}>
|
|
||||||
<IconComponent size={18} color={colors.grayText} style={styles.icon} />
|
|
||||||
<TextInput
|
|
||||||
placeholder={placeholder}
|
|
||||||
style={styles.input}
|
|
||||||
placeholderTextColor={colors.placeholder}
|
|
||||||
value={value}
|
|
||||||
onChangeText={onChangeText}
|
|
||||||
onBlur={onBlur}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
{touched && error && <Text style={styles.errorText}>{error}</Text>}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderPasswordInput = (
|
|
||||||
placeholder: string,
|
|
||||||
value: string,
|
|
||||||
onChangeText: (text: string) => void,
|
|
||||||
onBlur: () => void,
|
|
||||||
show: boolean,
|
|
||||||
setShow: (show: boolean) => void,
|
|
||||||
error?: string,
|
|
||||||
touched?: boolean,
|
|
||||||
) => (
|
|
||||||
<>
|
|
||||||
<View style={styles.inputWrapper}>
|
|
||||||
<Lock size={18} color={colors.grayText} style={styles.icon} />
|
|
||||||
<TextInput
|
|
||||||
placeholder={placeholder}
|
|
||||||
style={styles.input}
|
|
||||||
placeholderTextColor={colors.placeholder}
|
|
||||||
value={value}
|
|
||||||
onChangeText={onChangeText}
|
|
||||||
onBlur={onBlur}
|
|
||||||
secureTextEntry={!show}
|
|
||||||
/>
|
|
||||||
<TouchableOpacity onPress={() => setShow(!show)}>
|
|
||||||
{show ? <Eye size={18} color={colors.grayText} /> : <EyeOff size={18} color={colors.grayText} />}
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
{touched && error && <Text style={styles.errorText}>{error}</Text>}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmitForm = (values: any) => {
|
const handleSubmitForm = (values: any) => {
|
||||||
console.log('values', values);
|
|
||||||
|
|
||||||
if (!isCheck) {
|
if (!isCheck) {
|
||||||
Alert.alert('Check terms and condition');
|
Alert.alert('Terms Required', 'Please accept the terms and conditions to proceed.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +103,7 @@ const Signup = () => {
|
|||||||
mobNo: values.mobileNumber.trim(),
|
mobNo: values.mobileNumber.trim(),
|
||||||
emailAdd: values.email.trim(),
|
emailAdd: values.email.trim(),
|
||||||
contractorType: values.bidderType,
|
contractorType: values.bidderType,
|
||||||
designation: values.designation ? Number(values.designation) : 0, // convert string -> number
|
designation: values.designation ? Number(values.designation) : 0,
|
||||||
password: values.password.trim(),
|
password: values.password.trim(),
|
||||||
otherDesignation: null,
|
otherDesignation: null,
|
||||||
conPassword: values.confirmPassword.trim(),
|
conPassword: values.confirmPassword.trim(),
|
||||||
@@ -183,6 +111,7 @@ const Signup = () => {
|
|||||||
|
|
||||||
registerContractor(formData, {
|
registerContractor(formData, {
|
||||||
onSuccess: (res: any) => {
|
onSuccess: (res: any) => {
|
||||||
|
console.log('Register Res : ', res)
|
||||||
if (res?.status === '201') {
|
if (res?.status === '201') {
|
||||||
ToastAndroid.show(`${res.message}`, ToastAndroid.SHORT);
|
ToastAndroid.show(`${res.message}`, ToastAndroid.SHORT);
|
||||||
navigation.pop();
|
navigation.pop();
|
||||||
@@ -191,27 +120,28 @@ const Signup = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
|
console.log('Register Res : ', error)
|
||||||
ToastAndroid.show(error.message, ToastAndroid.SHORT);
|
ToastAndroid.show(error.message, ToastAndroid.SHORT);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={{ flex: 1, backgroundColor: colors.background }}>
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={{ flex: 1 }}>
|
||||||
<InsideHeader title="Sign Up" showArrow />
|
<InsideHeader title="Create Account" showArrow />
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={styles.container}
|
style={{ flex: 1 }}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
keyboardVerticalOffset={40}
|
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
|
||||||
>
|
>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
bidderType: '',
|
bidderType: 'Indian',
|
||||||
firstName: '',
|
firstName: '',
|
||||||
middleName: '',
|
middleName: '',
|
||||||
lastName: '',
|
lastName: '',
|
||||||
designation: '', // string, not 0
|
designation: '',
|
||||||
mobileNumber: '',
|
mobileNumber: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
@@ -232,174 +162,239 @@ const Signup = () => {
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
contentContainerStyle={styles.scrollContainer}
|
contentContainerStyle={styles.scrollContainer}
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
bounces={false}
|
showsVerticalScrollIndicator={false}
|
||||||
>
|
>
|
||||||
<Animated.View entering={FadeInUp.duration(450).delay(80)}>
|
{/* ═══ 1. Bidder Type Toggle ═══ */}
|
||||||
<Text style={styles.label}>Bidder Type</Text>
|
<Animated.View entering={FadeInUp.duration(400).delay(100)} style={{ marginBottom: spacing.m }}>
|
||||||
<View style={styles.toggleContainer}>
|
<View style={styles.sectionLabelRow}>
|
||||||
|
<UserPlus size={14} color={colors.primary} />
|
||||||
|
<AppText variant="caption" color={colors.primary} style={styles.sectionLabel}>
|
||||||
|
Bidder Type
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.toggleContainer, shadows.light]}>
|
||||||
{['Indian', 'Foreign'].map(type => (
|
{['Indian', 'Foreign'].map(type => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={type}
|
key={type}
|
||||||
style={[
|
style={[
|
||||||
styles.toggleButton,
|
styles.toggleButton,
|
||||||
values.bidderType === type && styles.selectedToggle,
|
values.bidderType === type && [styles.toggleButtonActive, { backgroundColor: colors.primary }],
|
||||||
]}
|
]}
|
||||||
onPress={() => setFieldValue('bidderType', type)}
|
onPress={() => setFieldValue('bidderType', type)}
|
||||||
|
activeOpacity={0.8}
|
||||||
>
|
>
|
||||||
<Text
|
<AppText
|
||||||
style={[
|
variant="bodySmall"
|
||||||
styles.toggleText,
|
color={values.bidderType === type ? colors.buttonText : colors.textSecondary}
|
||||||
values.bidderType === type && styles.selectedToggleText,
|
style={{ fontWeight: values.bidderType === type ? '700' : '500' }}
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
{type}
|
{type} Contractor
|
||||||
</Text>
|
</AppText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
{touched.bidderType && errors.bidderType && (
|
{touched.bidderType && errors.bidderType && (
|
||||||
<Text style={styles.errorText}>{errors.bidderType}</Text>
|
<AppText variant="caption" color={colors.error} style={styles.errorText}>
|
||||||
|
{errors.bidderType}
|
||||||
|
</AppText>
|
||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
<Animated.View entering={FadeInUp.duration(450).delay(120)}>
|
|
||||||
<View style={styles.row}>
|
{/* ═══ 2. Personal Information ═══ */}
|
||||||
<View style={styles.halfInput}>
|
<Animated.View entering={FadeInUp.duration(450).delay(200)} style={[styles.card, shadows.medium]}>
|
||||||
{renderInputWithIcon(
|
<View style={styles.cardHeader}>
|
||||||
'user',
|
<User size={16} color={colors.primary} />
|
||||||
'First Name *',
|
<AppText variant="bodySmall" color={colors.primary} style={styles.cardTitle}>
|
||||||
values.firstName,
|
Personal Information
|
||||||
handleChange('firstName'),
|
</AppText>
|
||||||
() => handleBlur('firstName'),
|
|
||||||
errors.firstName,
|
|
||||||
touched.firstName,
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<View style={styles.halfInput}>
|
|
||||||
{renderInputWithIcon(
|
|
||||||
'user',
|
|
||||||
'Middle Name',
|
|
||||||
values.middleName,
|
|
||||||
handleChange('middleName'),
|
|
||||||
() => handleBlur('middleName'),
|
|
||||||
errors.middleName,
|
|
||||||
touched.middleName,
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{renderInputWithIcon(
|
<MyInput
|
||||||
'user',
|
label="First Name"
|
||||||
'Last Name *',
|
placeholder="Enter First Name"
|
||||||
values.lastName,
|
value={values.firstName}
|
||||||
handleChange('lastName'),
|
onChangeText={handleChange('firstName')}
|
||||||
() => handleBlur('lastName'),
|
onBlur={handleBlur('firstName')}
|
||||||
errors.lastName,
|
error={touched.firstName && errors.firstName ? errors.firstName : undefined}
|
||||||
touched.lastName,
|
rightIcon={<User size={18} color={colors.textSecondary} />}
|
||||||
)}
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
</Animated.View>
|
/>
|
||||||
|
|
||||||
|
<MyInput
|
||||||
|
label="Middle Name"
|
||||||
|
placeholder="Enter Middle Name (Optional)"
|
||||||
|
value={values.middleName}
|
||||||
|
onChangeText={handleChange('middleName')}
|
||||||
|
onBlur={handleBlur('middleName')}
|
||||||
|
error={touched.middleName && errors.middleName ? errors.middleName : undefined}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MyInput
|
||||||
|
label="Last Name"
|
||||||
|
placeholder="Enter Last Name"
|
||||||
|
value={values.lastName}
|
||||||
|
onChangeText={handleChange('lastName')}
|
||||||
|
onBlur={handleBlur('lastName')}
|
||||||
|
error={touched.lastName && errors.lastName ? errors.lastName : undefined}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
|
/>
|
||||||
|
|
||||||
<Animated.View entering={FadeInUp.duration(450).delay(160)}>
|
|
||||||
<Text style={styles.label}>Designation</Text>
|
|
||||||
<MyDropdown
|
<MyDropdown
|
||||||
label="Select Designation"
|
label="Designation"
|
||||||
labelField="label"
|
labelField="label"
|
||||||
valueField="value"
|
valueField="value"
|
||||||
data={designation}
|
data={designation}
|
||||||
value={values.designation}
|
value={values.designation}
|
||||||
onChange={(item: any) => {
|
onChange={(item: any) => { setFieldValue('designation', item.value); }}
|
||||||
setFieldValue('designation', item.value);
|
placeholder={isDesignationLoading ? 'Loading...' : 'Select Designation'}
|
||||||
}}
|
|
||||||
placeholder={isDesignationLoading ? 'Loading...' : 'Choose Designation'}
|
|
||||||
style={dropdownStyle}
|
|
||||||
/>
|
/>
|
||||||
{touched.designation && errors.designation && (
|
{touched.designation && errors.designation && (
|
||||||
<Text style={styles.errorText}>{errors.designation}</Text>
|
<AppText variant="caption" color={colors.error} style={styles.errorText}>
|
||||||
|
{errors.designation}
|
||||||
|
</AppText>
|
||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
<Animated.View entering={FadeInUp.duration(450).delay(200)}>
|
|
||||||
{renderInputWithIcon(
|
|
||||||
'phone',
|
|
||||||
'Mobile Number *',
|
|
||||||
values.mobileNumber,
|
|
||||||
handleChange('mobileNumber'),
|
|
||||||
() => handleBlur('mobileNumber'),
|
|
||||||
errors.mobileNumber,
|
|
||||||
touched.mobileNumber,
|
|
||||||
{
|
|
||||||
keyboardType: 'phone-pad',
|
|
||||||
maxLength: 10,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
|
|
||||||
{renderInputWithIcon(
|
{/* ═══ 3. Contact Details ═══ */}
|
||||||
'mail',
|
<Animated.View entering={FadeInUp.duration(450).delay(300)} style={[styles.card, shadows.medium]}>
|
||||||
'Email *',
|
<View style={styles.cardHeader}>
|
||||||
values.email,
|
<Contact size={16} color={colors.primary} />
|
||||||
handleChange('email'),
|
<AppText variant="bodySmall" color={colors.primary} style={styles.cardTitle}>
|
||||||
() => handleBlur('email'),
|
Contact Details
|
||||||
errors.email,
|
</AppText>
|
||||||
touched.email,
|
|
||||||
{
|
|
||||||
keyboardType: 'email-address',
|
|
||||||
autoCapitalize: 'none',
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
<Animated.View entering={FadeInUp.duration(450).delay(240)}>
|
|
||||||
{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,
|
|
||||||
)}
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
<Animated.View entering={FadeInUp.duration(450).delay(280)}>
|
|
||||||
<View style={styles.termsRow}>
|
|
||||||
<TouchableOpacity onPress={() => setCheckModal(true)}>
|
|
||||||
{isCheck ? (
|
|
||||||
<CheckSquare size={20} color={colors.primary} />
|
|
||||||
) : (
|
|
||||||
<Square size={20} color={colors.black} />
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
<Text style={styles.termsText}>
|
|
||||||
By continuing, I agree to the Terms and Conditions
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
<Animated.View entering={FadeInUp.duration(450).delay(320)} style={styles.buttonWrapper}>
|
<MyInput
|
||||||
<PrimaryButton
|
label="Mobile Number"
|
||||||
title={isRegistering ? 'Submitting...' : 'Submit'}
|
placeholder="Enter Mobile Number"
|
||||||
disabled={isRegistering}
|
value={values.mobileNumber}
|
||||||
onPress={() => handleSubmit()}
|
onChangeText={handleChange('mobileNumber')}
|
||||||
|
onBlur={handleBlur('mobileNumber')}
|
||||||
|
error={touched.mobileNumber && errors.mobileNumber ? errors.mobileNumber : undefined}
|
||||||
|
keyboardType="phone-pad"
|
||||||
|
maxLength={10}
|
||||||
|
rightIcon={<Phone size={18} color={colors.textSecondary} />}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MyInput
|
||||||
|
label="Email Address"
|
||||||
|
placeholder="Enter Email Address"
|
||||||
|
value={values.email}
|
||||||
|
onChangeText={handleChange('email')}
|
||||||
|
onBlur={handleBlur('email')}
|
||||||
|
error={touched.email && errors.email ? errors.email : undefined}
|
||||||
|
keyboardType="email-address"
|
||||||
|
autoCapitalize="none"
|
||||||
|
rightIcon={<Mail size={18} color={colors.textSecondary} />}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
|
|
||||||
|
{/* ═══ 4. Security ═══ */}
|
||||||
|
<Animated.View entering={FadeInUp.duration(450).delay(400)} style={[styles.card, shadows.medium]}>
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<Shield size={16} color={colors.primary} />
|
||||||
|
<AppText variant="bodySmall" color={colors.primary} style={styles.cardTitle}>
|
||||||
|
Security
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<MyInput
|
||||||
|
label="Password"
|
||||||
|
placeholder="••••••••"
|
||||||
|
value={values.password}
|
||||||
|
onChangeText={handleChange('password')}
|
||||||
|
onBlur={handleBlur('password')}
|
||||||
|
secureTextEntry={!showPassword}
|
||||||
|
error={touched.password && errors.password ? errors.password : undefined}
|
||||||
|
rightIcon={
|
||||||
|
showPassword
|
||||||
|
? <Eye size={18} color={colors.textSecondary} />
|
||||||
|
: <EyeOff size={18} color={colors.textSecondary} />
|
||||||
|
}
|
||||||
|
rightIconPress={() => setShowPassword(prev => !prev)}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MyInput
|
||||||
|
label="Confirm Password"
|
||||||
|
placeholder="••••••••"
|
||||||
|
value={values.confirmPassword}
|
||||||
|
onChangeText={handleChange('confirmPassword')}
|
||||||
|
onBlur={handleBlur('confirmPassword')}
|
||||||
|
secureTextEntry={!showConfirmPassword}
|
||||||
|
error={touched.confirmPassword && errors.confirmPassword ? errors.confirmPassword : undefined}
|
||||||
|
rightIcon={
|
||||||
|
showConfirmPassword
|
||||||
|
? <Eye size={18} color={colors.textSecondary} />
|
||||||
|
: <EyeOff size={18} color={colors.textSecondary} />
|
||||||
|
}
|
||||||
|
rightIconPress={() => setShowConfirmPassword(prev => !prev)}
|
||||||
|
containerStyle={{ marginBottom: spacing.s }}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
|
|
||||||
|
{/* ═══ Terms & Conditions ═══ */}
|
||||||
|
<Animated.View entering={FadeInUp.duration(450).delay(500)}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.termsCard, shadows.light]}
|
||||||
|
onPress={() => setCheckModal(true)}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
>
|
||||||
|
{isCheck ? (
|
||||||
|
<CheckSquare size={22} color={colors.primary} strokeWidth={2.5} />
|
||||||
|
) : (
|
||||||
|
<Square size={22} color={colors.textSecondary} strokeWidth={2} />
|
||||||
|
)}
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary} style={{ flex: 1 }}>
|
||||||
|
I agree to the{' '}
|
||||||
|
<AppText variant="bodySmall" color={colors.primary} style={{ fontWeight: '700', textDecorationLine: 'underline' }}>
|
||||||
|
Terms and Conditions
|
||||||
|
</AppText>
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
|
|
||||||
|
{/* ═══ Submit Button ═══ */}
|
||||||
|
<Animated.View entering={FadeInDown.duration(400).delay(600)} style={styles.buttonWrapper}>
|
||||||
|
<PrimaryButton
|
||||||
|
title="Create Account"
|
||||||
|
loading={isRegistering}
|
||||||
|
onPress={handleSubmit}
|
||||||
|
variant="primary"
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
|
|
||||||
|
{/* ═══ Login Link ═══ */}
|
||||||
|
<Animated.View entering={FadeInUp.duration(400).delay(650)}>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => navigation.pop()}
|
||||||
|
style={styles.loginRow}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<AppText variant="bodySmall" color={colors.textSecondary}>
|
||||||
|
Already have an account?{' '}
|
||||||
|
<AppText variant="bodySmall" color={colors.primary} style={{ fontWeight: '700' }}>
|
||||||
|
Sign In
|
||||||
|
</AppText>
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
|
|
||||||
{checkModal && (
|
{checkModal && (
|
||||||
<TermsAndConditionsModal
|
<TermsAndConditionsModal
|
||||||
visible={checkModal}
|
visible={checkModal}
|
||||||
onClose={() => setCheckModal(false)}
|
onClose={() => setCheckModal(false)}
|
||||||
onChecked={() => setIsCheck(true)}
|
onChecked={() => { setIsCheck(true); setCheckModal(false); }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -413,99 +408,85 @@ const Signup = () => {
|
|||||||
|
|
||||||
export default Signup;
|
export default Signup;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
// ── Styles ────────────────────────────────────────────────────────────
|
||||||
flex: 1,
|
const makeStyles = (colors: any, spacing: any, shadows: any, typography: any) => StyleSheet.create({
|
||||||
backgroundColor: colors.white,
|
|
||||||
},
|
|
||||||
scrollContainer: {
|
scrollContainer: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
paddingHorizontal: 16,
|
padding: spacing.m,
|
||||||
paddingTop: 12,
|
paddingBottom: 30,
|
||||||
paddingBottom: 24,
|
|
||||||
justifyContent: 'flex-start',
|
|
||||||
},
|
},
|
||||||
label: {
|
sectionLabelRow: {
|
||||||
fontSize: 15,
|
flexDirection: 'row',
|
||||||
fontWeight: '600',
|
alignItems: 'center',
|
||||||
marginTop: 6,
|
gap: 6,
|
||||||
marginBottom: 4,
|
marginBottom: 6,
|
||||||
color: colors.darkText,
|
},
|
||||||
|
sectionLabel: {
|
||||||
|
fontWeight: '700',
|
||||||
|
fontSize: typography.size.xs,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
textTransform: 'uppercase',
|
||||||
},
|
},
|
||||||
toggleContainer: {
|
toggleContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
borderRadius: 12,
|
||||||
marginBottom: 4,
|
padding: 3,
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
height: 46,
|
||||||
},
|
},
|
||||||
toggleButton: {
|
toggleButton: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingVertical: 10,
|
justifyContent: 'center',
|
||||||
borderRadius: 10,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: colors.primary,
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginHorizontal: 4,
|
borderRadius: 9,
|
||||||
backgroundColor: colors.white,
|
|
||||||
},
|
},
|
||||||
selectedToggle: {
|
toggleButtonActive: {
|
||||||
backgroundColor: colors.primary,
|
elevation: 2,
|
||||||
borderColor: colors.primary,
|
|
||||||
},
|
},
|
||||||
toggleText: {
|
card: {
|
||||||
fontSize: 14,
|
backgroundColor: colors.surface,
|
||||||
color: colors.grayText,
|
borderRadius: 14,
|
||||||
|
padding: 14,
|
||||||
|
marginBottom: spacing.m,
|
||||||
},
|
},
|
||||||
selectedToggleText: {
|
cardHeader: {
|
||||||
color: colors.white,
|
|
||||||
fontWeight: '600',
|
|
||||||
},
|
|
||||||
row: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
marginTop: 6,
|
|
||||||
},
|
|
||||||
halfInput: {
|
|
||||||
width: '48%',
|
|
||||||
},
|
|
||||||
inputWrapper: {
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: colors.white,
|
gap: 8,
|
||||||
borderRadius: 12,
|
marginBottom: spacing.s,
|
||||||
borderWidth: 1,
|
paddingBottom: spacing.xs,
|
||||||
borderColor: colors.border,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
paddingHorizontal: 10,
|
borderBottomColor: colors.border,
|
||||||
marginTop: 8,
|
|
||||||
height: 52,
|
|
||||||
},
|
},
|
||||||
icon: {
|
cardTitle: {
|
||||||
marginRight: 10,
|
fontWeight: '700',
|
||||||
},
|
fontSize: typography.size.s,
|
||||||
input: {
|
letterSpacing: 0.3,
|
||||||
flex: 1,
|
|
||||||
fontSize: 15,
|
|
||||||
color: colors.darkText,
|
|
||||||
paddingVertical: 0,
|
|
||||||
},
|
},
|
||||||
errorText: {
|
errorText: {
|
||||||
color: colors.error,
|
marginTop: 4,
|
||||||
fontSize: 12,
|
fontSize: typography.size.xs,
|
||||||
marginTop: 3,
|
fontWeight: '500',
|
||||||
marginLeft: 4,
|
|
||||||
},
|
},
|
||||||
termsRow: {
|
termsCard: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginTop: 14,
|
gap: 10,
|
||||||
marginHorizontal: 2,
|
backgroundColor: colors.surface,
|
||||||
},
|
borderRadius: 12,
|
||||||
termsText: {
|
padding: 14,
|
||||||
flex: 1,
|
marginBottom: spacing.m,
|
||||||
fontSize: 13,
|
borderWidth: 1,
|
||||||
color: colors.darkText,
|
borderColor: colors.border,
|
||||||
marginLeft: 8,
|
|
||||||
},
|
},
|
||||||
buttonWrapper: {
|
buttonWrapper: {
|
||||||
marginTop: 18,
|
marginBottom: spacing.s,
|
||||||
|
},
|
||||||
|
loginRow: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: spacing.m,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
import { Animated, StyleSheet, View } from 'react-native';
|
import { Animated, StyleSheet, View } from 'react-native';
|
||||||
import { useNavigation, useIsFocused } from '@react-navigation/native';
|
import { useNavigation, useIsFocused } from '@react-navigation/native';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useStore } from 'react-redux'; // For hydration check
|
|
||||||
|
|
||||||
import { showToast } from '../../Constant/ShowDailog';
|
import { showErrorAlert, showToast } from '../../Constant/ShowDailog';
|
||||||
import { useRagistrationState } from '../../Services/BBPS/Hooks';
|
import { useRagistrationState } from '../../Services/ApiCall/Hooks';
|
||||||
import NavigationString from '../../Constant/NavigationString';
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
|
|
||||||
// Navigation type
|
// Navigation type
|
||||||
@@ -24,34 +23,14 @@ const Splash: React.FC = () => {
|
|||||||
const isFocused = useIsFocused();
|
const isFocused = useIsFocused();
|
||||||
const fadeAnim = useRef(new Animated.Value(0)).current;
|
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||||
const reduxToken = useSelector((state: any) => state.token?.token);
|
const reduxToken = useSelector((state: any) => state.token?.token);
|
||||||
console.log("user Token",reduxToken)
|
const isPersistRehydrated = useSelector((state: any) => state?._persist?.rehydrated);
|
||||||
const store = useStore();
|
|
||||||
const { mutateAsync: registrationStateMutate } = useRagistrationState();
|
const { mutateAsync: registrationStateMutate } = useRagistrationState();
|
||||||
const [isHydrated, setIsHydrated] = useState(false);
|
const hasSessionToken = Boolean(reduxToken && typeof reduxToken === 'object' && reduxToken.token);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const routeByOrgType = useCallback((params: { otherId: any; orgType: any }) => {
|
||||||
let unsubscribe: any;
|
const orgType = String(params.orgType ?? '');
|
||||||
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 = String(params.orgType);
|
|
||||||
if (orgType === '4' || orgType === '5' || orgType === '6') {
|
if (orgType === '4' || orgType === '5' || orgType === '6') {
|
||||||
navigation.replace('StageThree', params);
|
navigation.replace(NavigationString.StageThreeScreen, params);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +40,27 @@ const Splash: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
navigation.replace('Dashboard');
|
navigation.replace('Dashboard');
|
||||||
};
|
}, [navigation]);
|
||||||
|
|
||||||
const handleGetData = async () => {
|
const handleGetData = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await registrationStateMutate();
|
const res = await registrationStateMutate();
|
||||||
console.log("ragister",res)
|
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?.statusCode === 404 || res?.statusCode === 401) {
|
if (res?.statusCode === 404 || res?.statusCode === 401) {
|
||||||
navigation.replace('Registration');
|
navigation.replace('Registration');
|
||||||
return;
|
return;
|
||||||
@@ -75,8 +69,6 @@ const Splash: React.FC = () => {
|
|||||||
if (res?.statusCode === 200 && res?.data) {
|
if (res?.statusCode === 200 && res?.data) {
|
||||||
const { registrationStatus, contOtherId, contOrganization } = res.data;
|
const { registrationStatus, contOtherId, contOrganization } = res.data;
|
||||||
const params = { otherId: contOtherId, orgType: contOrganization };
|
const params = { otherId: contOtherId, orgType: contOrganization };
|
||||||
console.log('Registration status:', registrationStatus);
|
|
||||||
|
|
||||||
switch (registrationStatus) {
|
switch (registrationStatus) {
|
||||||
case null:
|
case null:
|
||||||
case undefined:
|
case undefined:
|
||||||
@@ -90,14 +82,17 @@ const Splash: React.FC = () => {
|
|||||||
navigation.replace(NavigationString.Home);
|
navigation.replace(NavigationString.Home);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
navigation.replace(NavigationString.Registration);
|
navigation.replace(NavigationString.Login);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error('API error:', res);
|
console.error('API error:', res);
|
||||||
showToast(res?.message || 'Something went wrong');
|
showErrorAlert(res?.error || 'Something went wrong. Please try re-logging in.');
|
||||||
navigation.replace('Registration');
|
navigation.reset({
|
||||||
|
index: 0,
|
||||||
|
routes: [{ name: NavigationString.Login }],
|
||||||
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Registration status error:', error);
|
console.error('Registration status error:', error);
|
||||||
showToast(error?.message || 'Something went wrong');
|
showToast(error?.message || 'Something went wrong');
|
||||||
@@ -105,18 +100,19 @@ const Splash: React.FC = () => {
|
|||||||
if (error?.response?.status === 404) {
|
if (error?.response?.status === 404) {
|
||||||
navigation.replace('Registration');
|
navigation.replace('Registration');
|
||||||
} else {
|
} else {
|
||||||
await AsyncStorage.removeItem('token');
|
await AsyncStorage.removeItem('persist:root');
|
||||||
navigation.replace('Signin');
|
navigation.replace(NavigationString.Login);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}, [navigation, registrationStateMutate, routeByOrgType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFocused || !isHydrated) return;
|
if (!isFocused || !isPersistRehydrated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const runSplash = async () => {
|
const runSplash = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
|
||||||
Animated.timing(fadeAnim, {
|
Animated.timing(fadeAnim, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: 500,
|
duration: 500,
|
||||||
@@ -124,7 +120,7 @@ const Splash: React.FC = () => {
|
|||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
|
|
||||||
if (!reduxToken.token) {
|
if (!hasSessionToken) {
|
||||||
navigation.reset({
|
navigation.reset({
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ name: NavigationString.Login }],
|
routes: [{ name: NavigationString.Login }],
|
||||||
@@ -132,7 +128,7 @@ const Splash: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reduxToken.userType == 'tender') {
|
if (reduxToken.userType === 'tender') {
|
||||||
await handleGetData();
|
await handleGetData();
|
||||||
} else {
|
} else {
|
||||||
navigation.reset({
|
navigation.reset({
|
||||||
@@ -144,18 +140,16 @@ const Splash: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
navigation.reset({
|
navigation.reset({
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ name: NavigationString.Login || 'Signin' }],
|
routes: [{ name: NavigationString.Login }],
|
||||||
});
|
});
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const timeoutId = setTimeout(runSplash, 200);
|
const timeoutId = setTimeout(runSplash, 200);
|
||||||
return () => clearTimeout(timeoutId);
|
return () => clearTimeout(timeoutId);
|
||||||
}, [isFocused, isHydrated]);
|
}, [fadeAnim, handleGetData, hasSessionToken, isFocused, isPersistRehydrated, navigation, reduxToken?.userType]);
|
||||||
|
|
||||||
if (!isHydrated) {
|
if (!isPersistRehydrated) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
<Animated.Image
|
<Animated.Image
|
||||||
@@ -175,11 +169,6 @@ const Splash: React.FC = () => {
|
|||||||
style={[styles.logo, { opacity: fadeAnim }]}
|
style={[styles.logo, { opacity: fadeAnim }]}
|
||||||
resizeMode="contain"
|
resizeMode="contain"
|
||||||
/>
|
/>
|
||||||
{isLoading && (
|
|
||||||
<View style={{ marginTop: 20 }}>
|
|
||||||
{/* Optional spinner */}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,33 +1,39 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { View, Text, TextInput, ScrollView, TouchableOpacity, StyleSheet, ActivityIndicator, Alert, } from 'react-native';
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
StyleSheet,
|
||||||
|
ActivityIndicator,
|
||||||
|
Alert,
|
||||||
|
} from 'react-native';
|
||||||
import RNFS from 'react-native-fs';
|
import RNFS from 'react-native-fs';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import axios from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { InsideHeader } from '../../../Component/Index';
|
|
||||||
import colors from '../../../Constant/Color';
|
import colors from '../../../Constant/Color';
|
||||||
import { GET_ENVELOPE_C, SUBMIT_ENVELOPE_C_UPLOAD } from '../../../Services/BBPS/ApiUrls';
|
import { GET_ENVELOPE_C, SUBMIT_ENVELOPE_C_UPLOAD } from '../../../Services/ApiCall/ApiUrls';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { encrypt, encryptWithRSA } from '../../../Services/Encryption/encrypt';
|
||||||
|
|
||||||
|
|
||||||
// Excel column keys
|
/* -------------------- UTILS -------------------- */
|
||||||
const QTY_KEY = '__EMPTY_1';
|
interface OfficerKey {
|
||||||
const RATE_KEY = '__EMPTY_2';
|
officersId: number;
|
||||||
const VALUE_KEY = '__EMPTY_3';
|
officerPublicKey: string;
|
||||||
const TOTAL_LABEL = 'TOTAL';
|
officerType: string;
|
||||||
|
}
|
||||||
|
|
||||||
const EnvelopeCExcelPreview = (tenderData: any) => {
|
|
||||||
const token = useSelector((state: any) => state.token.token);
|
|
||||||
const navigation = useNavigation() as any;
|
|
||||||
const [data, setData] = useState<any[]>([]);
|
|
||||||
const [columns, setColumns] = useState<string[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
interface SaveEnvelopeOfficerDetail {
|
||||||
loadExcelFromApi();
|
officerId: number;
|
||||||
}, []);
|
officerEncryptedKey: string;
|
||||||
|
officerType: string;
|
||||||
|
}
|
||||||
|
|
||||||
// ArrayBuffer → Base64
|
// Convert ArrayBuffer → Base64
|
||||||
const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
|
const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
|
||||||
let binary = '';
|
let binary = '';
|
||||||
const bytes = new Uint8Array(buffer);
|
const bytes = new Uint8Array(buffer);
|
||||||
@@ -37,147 +43,381 @@ const EnvelopeCExcelPreview = (tenderData: any) => {
|
|||||||
return global.btoa(binary);
|
return global.btoa(binary);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate TOTAL VALUE
|
|
||||||
const calculateTotal = (rows: any[]) =>
|
|
||||||
rows.reduce((sum, row) => {
|
|
||||||
if (typeof row['DEMO DEPARTEMENT'] !== 'number') return sum;
|
|
||||||
const val = Number(row[VALUE_KEY]);
|
|
||||||
return sum + (isNaN(val) ? 0 : val);
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
// LOAD EXCEL
|
// Find column dynamically by keywords
|
||||||
|
const findColumnKey = (columns: string[], keywords: string[]) =>
|
||||||
|
columns.find(col =>
|
||||||
|
keywords.some(k => col.toLowerCase().includes(k.toLowerCase()))
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// Detect TOTAL row (any column contains TOTAL)
|
||||||
|
const isTotalRow = (row: any) =>
|
||||||
|
Object.values(row).some(v => String(v).toUpperCase() === 'TOTAL');
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------- COMPONENT -------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
const EnvelopeCExcelPreview = (props: any) => {
|
||||||
|
const tenderData = props;
|
||||||
|
console.log("Tender Data", tenderData)
|
||||||
|
const token = useSelector((state: any) => state.token.token);
|
||||||
|
const navigation = useNavigation() as any;
|
||||||
|
|
||||||
|
|
||||||
|
const [data, setData] = useState<any[]>([]);
|
||||||
|
const [columns, setColumns] = useState<string[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadExcelFromApi();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------- LOAD EXCEL -------------------- */
|
||||||
|
|
||||||
|
|
||||||
const loadExcelFromApi = async () => {
|
const loadExcelFromApi = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const response = await axios.get(`${GET_ENVELOPE_C}${tenderData?.tenderId?.tenderId}`, {
|
|
||||||
|
console.log('\n📊 LOADING EXCEL FROM API');
|
||||||
|
console.log('═══════════════════════════════════════════════════════');
|
||||||
|
console.log('🔗 API URL:', `${GET_ENVELOPE_C}${tenderData?.tenderId?.tenderId}`);
|
||||||
|
console.log('🆔 Tender ID:', tenderData?.tenderId?.tenderId);
|
||||||
|
console.log('🔑 Token:', token.token ? 'Present' : 'Missing');
|
||||||
|
|
||||||
|
|
||||||
|
const response = await axios.get(
|
||||||
|
`${GET_ENVELOPE_C}${tenderData?.tenderId?.tenderId}`,
|
||||||
|
{
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
headers: {
|
headers: {
|
||||||
Authentication: `Bearer ${token.token}`,
|
Authentication: `Bearer ${token.token}`,
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
console.log("excel response",response)
|
);
|
||||||
|
|
||||||
|
|
||||||
|
console.log('\n✅ API RESPONSE RECEIVED');
|
||||||
|
console.log('───────────────────────────────────────────────────────');
|
||||||
|
console.log('📡 Status:', response.status);
|
||||||
|
console.log('📡 Status Text:', response.statusText);
|
||||||
|
console.log('📦 Response Data Size (KB):', (response.data.byteLength / 1024).toFixed(2));
|
||||||
|
|
||||||
|
|
||||||
const base64 = arrayBufferToBase64(response.data);
|
const base64 = arrayBufferToBase64(response.data);
|
||||||
const workbook = XLSX.read(base64, { type: 'base64' });
|
const workbook = XLSX.read(base64, { type: 'base64' });
|
||||||
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||||
const rows: any[] = XLSX.utils.sheet_to_json(sheet, { defval: '' });
|
const rows: any[] = XLSX.utils.sheet_to_json(sheet, { defval: '' });
|
||||||
|
|
||||||
|
|
||||||
|
console.log('📊 PARSED DATA - Total Rows:', rows.length);
|
||||||
|
|
||||||
|
|
||||||
setColumns(Object.keys(rows[0] || {}));
|
setColumns(Object.keys(rows[0] || {}));
|
||||||
setData(rows);
|
setData(rows);
|
||||||
|
|
||||||
await RNFS.writeFile(
|
|
||||||
`${RNFS.DocumentDirectoryPath}/envelopeC.xlsx`,
|
console.log('✅ EXCEL LOADED SUCCESSFULLY\n');
|
||||||
base64,
|
|
||||||
'base64'
|
|
||||||
);
|
} catch (e: any) {
|
||||||
} catch (e) {
|
console.log('\n❌ ERROR LOADING EXCEL');
|
||||||
Alert.alert('Error', 'Failed to load Excel');
|
console.error('Error:', e.message);
|
||||||
|
Alert.alert('Error', 'Failed to load Excel: ' + e.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// UPDATE CELL (QTY / RATE) + AUTO VALUE + AUTO TOTAL
|
|
||||||
|
/* -------------------- UPDATE CELL -------------------- */
|
||||||
|
|
||||||
|
|
||||||
const updateCell = (rowIndex: number, key: string, value: string) => {
|
const updateCell = (rowIndex: number, key: string, value: string) => {
|
||||||
const updated = [...data];
|
const updated = [...data];
|
||||||
const row = updated[rowIndex];
|
const row = updated[rowIndex];
|
||||||
|
|
||||||
// TOTAL row locked
|
|
||||||
if (row['DEMO DEPARTEMENT'] === TOTAL_LABEL) return;
|
|
||||||
|
|
||||||
// Allow only QTY & RATE edit
|
if (isTotalRow(row)) return;
|
||||||
if (key === QTY_KEY || key === RATE_KEY) {
|
|
||||||
|
|
||||||
|
const qtyKey: any = findColumnKey(columns, ['qty', 'quantity']);
|
||||||
|
const rateKey: any = findColumnKey(columns, ['rate', 'price']);
|
||||||
|
const valueKey = findColumnKey(columns, ['value', 'amount', 'total']);
|
||||||
|
|
||||||
|
|
||||||
|
if (key === qtyKey || key === rateKey) {
|
||||||
row[key] = value;
|
row[key] = value;
|
||||||
|
|
||||||
const qty = Number(row[QTY_KEY]);
|
|
||||||
const rate = Number(row[RATE_KEY]);
|
|
||||||
|
|
||||||
// 🔥 Auto VALUE = QTY × RATE
|
const qty = Number(row[qtyKey]);
|
||||||
row[VALUE_KEY] =
|
const rate = Number(row[rateKey]);
|
||||||
|
|
||||||
|
|
||||||
|
if (valueKey) {
|
||||||
|
row[valueKey] =
|
||||||
!isNaN(qty) && !isNaN(rate) ? qty * rate : 0;
|
!isNaN(qty) && !isNaN(rate) ? qty * rate : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 Update TOTAL
|
|
||||||
const totalRowIndex = updated.findIndex(
|
|
||||||
r => r['DEMO DEPARTEMENT'] === TOTAL_LABEL
|
|
||||||
);
|
|
||||||
|
|
||||||
if (totalRowIndex !== -1) {
|
|
||||||
updated[totalRowIndex][VALUE_KEY] = calculateTotal(updated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Recalculate TOTAL
|
||||||
|
const totalRowIndex = updated.findIndex(isTotalRow);
|
||||||
|
if (totalRowIndex !== -1 && valueKey) {
|
||||||
|
updated[totalRowIndex][valueKey] = updated.reduce((sum, r) => {
|
||||||
|
if (isTotalRow(r)) return sum;
|
||||||
|
const val = Number(r[valueKey]);
|
||||||
|
return sum + (isNaN(val) ? 0 : val);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
setData(updated);
|
setData(updated);
|
||||||
};
|
};
|
||||||
|
|
||||||
// SAVE UPDATED EXCEL
|
|
||||||
|
/* -------------------- SAVE & UPLOAD EXCEL -------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
const buildOfficerEncryptedKeys = (envelopeKey: string): SaveEnvelopeOfficerDetail[] => {
|
||||||
|
const officerKeys: OfficerKey[] = tenderData?.tenderId?.envelopeKeyDetailsResDTO?.departmentOfficerPublicKeys || [];
|
||||||
|
|
||||||
|
|
||||||
|
console.log('\n🔐 ENCRYPTING ENVELOPE KEY FOR OFFICERS');
|
||||||
|
console.log('═══════════════════════════════════════════════════════');
|
||||||
|
console.log('📌 Envelope Key to encrypt:', envelopeKey);
|
||||||
|
console.log('👥 Number of officers:', officerKeys.length);
|
||||||
|
|
||||||
|
|
||||||
|
const encryptedKeys: SaveEnvelopeOfficerDetail[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
for (let i = 0; i < officerKeys.length; i++) {
|
||||||
|
const officer = officerKeys[i];
|
||||||
|
console.log(`\n🔄 Officer ${i + 1}/${officerKeys.length}`);
|
||||||
|
console.log(' ID:', officer.officersId);
|
||||||
|
console.log(' Type:', officer.officerType);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const encryptedKey = encryptWithRSA(envelopeKey, officer.officerPublicKey);
|
||||||
|
|
||||||
|
|
||||||
|
if (!encryptedKey) {
|
||||||
|
console.error(` ❌ FAILED to encrypt key for officer ${officer.officersId}`);
|
||||||
|
} else {
|
||||||
|
console.log(` ✅ SUCCESS - Encrypted key length: ${encryptedKey.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
encryptedKeys.push({
|
||||||
|
officerId: officer.officersId,
|
||||||
|
officerEncryptedKey: encryptedKey || '',
|
||||||
|
officerType: officer.officerType,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(` ❌ ERROR encrypting for officer ${officer.officersId}:`, error);
|
||||||
|
encryptedKeys.push({
|
||||||
|
officerId: officer.officersId,
|
||||||
|
officerEncryptedKey: '',
|
||||||
|
officerType: officer.officerType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log('\n✅ ALL OFFICER KEYS ENCRYPTED');
|
||||||
|
console.log('═══════════════════════════════════════════════════════\n');
|
||||||
|
return encryptedKeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ✅ NEW: Encrypt Excel file content (binary) after creation
|
||||||
|
const encryptExcelFile = async (filePath: string, encryptionKey: string): Promise<string> => {
|
||||||
|
console.log('\n🔐 ENCRYPTING EXCEL FILE CONTENT');
|
||||||
|
console.log('═══════════════════════════════════════════════════════');
|
||||||
|
console.log('📁 File Path:', filePath);
|
||||||
|
console.log('🔑 Encryption Key:', encryptionKey);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Read the Excel file as Base64
|
||||||
|
const fileContent = await RNFS.readFile(filePath, 'base64');
|
||||||
|
console.log('📖 File read successfully, Base64 length:', fileContent.length);
|
||||||
|
|
||||||
|
|
||||||
|
// Encrypt the entire file content
|
||||||
|
console.log('🔒 Encrypting file content...');
|
||||||
|
const encryptedContent: any = encrypt(fileContent, encryptionKey);
|
||||||
|
console.log('✅ File content encrypted, Encrypted length:', encryptedContent?.length);
|
||||||
|
|
||||||
|
|
||||||
|
// Save encrypted content to a new file
|
||||||
|
const encryptedFilePath = `${RNFS.DocumentDirectoryPath}/envelopeC_encrypted.xlsx`;
|
||||||
|
await RNFS.writeFile(encryptedFilePath, encryptedContent, 'utf8');
|
||||||
|
console.log('💾 Encrypted file saved at:', encryptedFilePath);
|
||||||
|
|
||||||
|
|
||||||
|
console.log('✅ EXCEL FILE ENCRYPTION COMPLETED');
|
||||||
|
console.log('═══════════════════════════════════════════════════════\n');
|
||||||
|
|
||||||
|
|
||||||
|
return encryptedFilePath;
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ ERROR ENCRYPTING EXCEL FILE:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const saveUpdatedExcel = async () => {
|
const saveUpdatedExcel = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
|
||||||
|
console.log('\n💾 SAVING UPDATED EXCEL');
|
||||||
|
console.log('═══════════════════════════════════════════════════════');
|
||||||
|
|
||||||
|
|
||||||
|
const ENCRYPTION_KEY = tenderData?.tenderId?.envelopeKeyDetailsResDTO?.envelopeCGenerateKey || '';
|
||||||
|
|
||||||
|
if (!ENCRYPTION_KEY) {
|
||||||
|
console.error('❌ ENCRYPTION KEY NOT FOUND');
|
||||||
|
Alert.alert('Error', 'Encryption key not available');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log('🔑 Encryption Key:', ENCRYPTION_KEY);
|
||||||
|
|
||||||
|
|
||||||
|
// Step 1: Create normal Excel workbook with user data (NOT ENCRYPTED YET)
|
||||||
|
console.log('\n📝 STEP 1: Creating Excel workbook with user data');
|
||||||
|
console.log('───────────────────────────────────────────────────────');
|
||||||
const worksheet = XLSX.utils.json_to_sheet(data);
|
const worksheet = XLSX.utils.json_to_sheet(data);
|
||||||
const workbook = XLSX.utils.book_new();
|
const workbook = XLSX.utils.book_new();
|
||||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
|
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
|
||||||
|
|
||||||
// Generate binary string
|
|
||||||
const binary = XLSX.write(workbook, {
|
const binary = XLSX.write(workbook, {
|
||||||
bookType: 'xlsx',
|
bookType: 'xlsx',
|
||||||
type: 'binary',
|
type: 'binary',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Convert binary string to buffer
|
|
||||||
const buffer = new Uint8Array(binary.length);
|
const buffer = new Uint8Array(binary.length);
|
||||||
for (let i = 0; i < binary.length; i++) {
|
for (let i = 0; i < binary.length; i++) {
|
||||||
buffer[i] = binary.charCodeAt(i) & 0xff;
|
buffer[i] = binary.charCodeAt(i) & 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const filePath = `${RNFS.DocumentDirectoryPath}/envelopeC_updated.xlsx`;
|
// Step 2: Save the plain Excel file
|
||||||
|
console.log('\n📝 STEP 2: Saving plain Excel file');
|
||||||
|
console.log('───────────────────────────────────────────────────────');
|
||||||
|
const plainFilePath = `${RNFS.DocumentDirectoryPath}/envelopeC_plain.xlsx`;
|
||||||
|
await RNFS.writeFile(plainFilePath, buffer.toString(), 'ascii');
|
||||||
|
console.log('✅ Plain Excel saved at:', plainFilePath);
|
||||||
|
|
||||||
// Write as binary
|
|
||||||
await RNFS.writeFile(filePath, buffer.toString(), 'ascii');
|
|
||||||
// const fileBinary = await RNFS.readFile(filePath, 'ascii');
|
|
||||||
|
|
||||||
// console.log('Excel binary content:', fileBinary);
|
// Step 3: Encrypt the entire Excel file
|
||||||
// Alert.alert('Success', 'Excel saved successfully');
|
console.log('\n📝 STEP 3: Encrypting the Excel file');
|
||||||
|
console.log('───────────────────────────────────────────────────────');
|
||||||
|
const encryptedFilePath = await encryptExcelFile(plainFilePath, ENCRYPTION_KEY);
|
||||||
|
console.log('✅ Encrypted Excel file ready');
|
||||||
|
|
||||||
|
|
||||||
|
// Step 4: Build officer encrypted keys
|
||||||
|
console.log('\n📝 STEP 4: Building officer encrypted keys');
|
||||||
|
console.log('───────────────────────────────────────────────────────');
|
||||||
|
const officerEncryptedKeys = buildOfficerEncryptedKeys(ENCRYPTION_KEY);
|
||||||
|
|
||||||
|
|
||||||
|
// Step 5: Prepare FormData for upload
|
||||||
|
console.log('\n📝 STEP 5: Building FormData');
|
||||||
|
console.log('───────────────────────────────────────────────────────');
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
const requestDto = {
|
||||||
// requestDto payload
|
|
||||||
formData.append(
|
|
||||||
'requestDto',
|
|
||||||
JSON.stringify({
|
|
||||||
tenderId: tenderData?.tenderId?.tenderId,
|
tenderId: tenderData?.tenderId?.tenderId,
|
||||||
deptId: tenderData?.tenderId?.deptId,
|
deptId: tenderData?.tenderId?.deptId,
|
||||||
typesOfTender: tenderData?.tenderId?.typesOfTender,
|
typesOfTender: tenderData?.tenderId?.typesOfTender,
|
||||||
fieldValue: '',
|
fieldValue: '',
|
||||||
fieldValueInWord: 'zero',
|
fieldValueInWord: 'zero',
|
||||||
})
|
deviceType: 'MOBILE',
|
||||||
);
|
fileName: 'envelopeC_encrypted.xlsx',
|
||||||
|
saveEnvelopesOfficersDetailsResDTO: officerEncryptedKeys,
|
||||||
|
randomKeyGenerated: ENCRYPTION_KEY,
|
||||||
|
};
|
||||||
|
|
||||||
// binary file
|
|
||||||
|
formData.append('requestDto', JSON.stringify(requestDto));
|
||||||
|
|
||||||
|
|
||||||
|
// Use the ENCRYPTED file for upload
|
||||||
formData.append('file', {
|
formData.append('file', {
|
||||||
uri: `file://${filePath}`,
|
uri: `file://${encryptedFilePath}`,
|
||||||
name: 'envelopeC_updated.xlsx',
|
name: 'envelopeC_encrypted.xlsx',
|
||||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
});
|
} as any);
|
||||||
|
|
||||||
const res = await axios.post(SUBMIT_ENVELOPE_C_UPLOAD, formData, {
|
|
||||||
|
console.log('📋 Request DTO:', JSON.stringify(requestDto, null, 2));
|
||||||
|
console.log('📎 Encrypted File Path:', encryptedFilePath);
|
||||||
|
|
||||||
|
|
||||||
|
// Step 6: Upload to server
|
||||||
|
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: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
Authentication: `Bearer ${token.token}`,
|
Authentication: `Bearer ${token.token}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
console.log('✅ UPLOAD SUCCESSFUL');
|
||||||
|
console.log('Response:', response.data);
|
||||||
|
console.log('═══════════════════════════════════════════════════════\n');
|
||||||
|
|
||||||
|
|
||||||
|
// Clean up temporary files
|
||||||
|
await RNFS.unlink(plainFilePath).catch(e => console.log('Plain file cleanup:', e.message));
|
||||||
|
await RNFS.unlink(encryptedFilePath).catch(e => console.log('Encrypted file cleanup:', e.message));
|
||||||
|
|
||||||
|
|
||||||
|
Alert.alert('Success', 'Excel uploaded successfully');
|
||||||
navigation.goBack();
|
navigation.goBack();
|
||||||
console.log("res of updated excel file : ", res)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
} catch (e: any) {
|
||||||
Alert.alert('Error', 'Save failed');
|
console.error('❌ Error saving/uploading Excel:', {
|
||||||
|
message: e?.message,
|
||||||
|
response: e?.response?.data,
|
||||||
|
status: e?.response?.status,
|
||||||
|
statusText: e?.response?.statusText,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Alert.alert('Error', e?.response?.data?.message || 'Save failed');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------- UI -------------------- */
|
||||||
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -187,43 +427,43 @@ const EnvelopeCExcelPreview = (tenderData: any) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const qtyKey = findColumnKey(columns, ['qty', 'quantity']);
|
||||||
|
const rateKey = findColumnKey(columns, ['rate', 'price']);
|
||||||
|
const valueKey = findColumnKey(columns, ['value', 'amount', 'total']);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
<ScrollView horizontal contentContainerStyle={{ padding: 10 }}>
|
||||||
<ScrollView horizontal contentContainerStyle={{ paddingHorizontal: 10, paddingTop: 20, backgroundColor: colors.white }}>
|
|
||||||
<View>
|
<View>
|
||||||
{/* HEADER */}
|
{/* HEADER */}
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
{columns.map(col => (
|
{columns.map(col => (
|
||||||
<Text key={col} style={styles.headerCell}>
|
<Text key={col} style={styles.headerCell}>{col}</Text>
|
||||||
{col}
|
|
||||||
</Text>
|
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
||||||
{/* DATA */}
|
{/* DATA */}
|
||||||
<ScrollView style={{ maxHeight: 420 }}>
|
<ScrollView style={{ maxHeight: 420 }}>
|
||||||
{data.map((row, rowIndex) => (
|
{data.map((row, rowIndex) => (
|
||||||
<View key={rowIndex} style={styles.row}>
|
<View key={rowIndex} style={styles.row}>
|
||||||
{columns.map(key => {
|
{columns.map(key => {
|
||||||
const isTotal = row['DEMO DEPARTEMENT'] === TOTAL_LABEL;
|
const editable =
|
||||||
const isEditable =
|
!isTotalRow(row) && (key === qtyKey || key === rateKey);
|
||||||
!isTotal && (key === QTY_KEY || key === RATE_KEY);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
key={key}
|
key={key}
|
||||||
value={String(row[key])}
|
value={String(row[key])}
|
||||||
editable={isEditable}
|
editable={editable}
|
||||||
keyboardType={
|
keyboardType={editable ? 'numeric' : 'default'}
|
||||||
key === QTY_KEY || key === RATE_KEY
|
|
||||||
? 'numeric'
|
|
||||||
: 'default'
|
|
||||||
}
|
|
||||||
style={[
|
style={[
|
||||||
styles.cell,
|
styles.cell,
|
||||||
isTotal && styles.totalCell,
|
isTotalRow(row) && styles.totalCell,
|
||||||
key === VALUE_KEY && styles.valueCell,
|
key === valueKey && styles.valueCell,
|
||||||
]}
|
]}
|
||||||
onChangeText={text =>
|
onChangeText={text =>
|
||||||
updateCell(rowIndex, key, text)
|
updateCell(rowIndex, key, text)
|
||||||
@@ -237,19 +477,26 @@ const EnvelopeCExcelPreview = (tenderData: any) => {
|
|||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
{tenderData?.buttonEnable && (
|
||||||
<TouchableOpacity style={styles.button} onPress={saveUpdatedExcel}>
|
<TouchableOpacity style={styles.button} onPress={saveUpdatedExcel}>
|
||||||
<Text style={styles.buttonText}>Save Updated Excel</Text>
|
<Text style={styles.buttonText}>Save Updated Excel</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default EnvelopeCExcelPreview;
|
export default EnvelopeCExcelPreview;
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------- STYLES -------------------- */
|
||||||
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { flex: 1, backgroundColor: '#fff' },
|
container: { flex: 1, backgroundColor: '#fff' },
|
||||||
loader: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
loader: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||||
title: { fontSize: 18, fontWeight: '700', marginBottom: 10 },
|
|
||||||
row: { flexDirection: 'row' },
|
row: { flexDirection: 'row' },
|
||||||
headerCell: {
|
headerCell: {
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
@@ -272,7 +519,7 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
marginTop: 15,
|
margin: 15,
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
padding: 14,
|
padding: 14,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
|
|||||||
@@ -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<any>();
|
||||||
|
const route = useRoute<any>();
|
||||||
|
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 (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<InsideHeader title="Device Binding" showArrow />
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.iconContainer}>
|
||||||
|
{success ? (
|
||||||
|
<CheckCircle size={64} color={colors.success} />
|
||||||
|
) : (
|
||||||
|
<AlertCircle size={64} color={colors.error} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<AppText variant="h3" color={colors.text} style={styles.title}>
|
||||||
|
{success ? "Binding Successful" : "Device Not Bound"}
|
||||||
|
</AppText>
|
||||||
|
|
||||||
|
<AppText variant="body" color={colors.textSecondary} style={styles.message}>
|
||||||
|
{success ? "Your device binding request has been submitted successfully." : message}
|
||||||
|
</AppText>
|
||||||
|
|
||||||
|
{showBindButton && !success && (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.button}
|
||||||
|
onPress={handleBindDevice}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<ActivityIndicator size="small" color={colors.buttonText} />
|
||||||
|
) : (
|
||||||
|
<AppText variant="button" color={colors.buttonText}>
|
||||||
|
Bind This Device
|
||||||
|
</AppText>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={success ? styles.button : styles.secondaryButton}
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
>
|
||||||
|
<AppText variant="button" color={success ? colors.buttonText : colors.text}>
|
||||||
|
{success ? "Finish" : "Go Back"}
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeviceBinding;
|
||||||
@@ -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<any>();
|
||||||
|
const route = useRoute<any>();
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<InsideHeader title="DSC Binding" showArrow />
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.iconContainer}>
|
||||||
|
<AlertCircle size={64} color={colors.error} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<AppText variant="h3" color={colors.text} style={styles.title}>
|
||||||
|
DSC Not Bound
|
||||||
|
</AppText>
|
||||||
|
|
||||||
|
<AppText variant="body" color={colors.textSecondary} style={styles.message}>
|
||||||
|
{message}
|
||||||
|
</AppText>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.button}
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
>
|
||||||
|
<AppText variant="button" color={colors.buttonText}>
|
||||||
|
Go Back
|
||||||
|
</AppText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DscBinding;
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
import React, { useCallback, useState, useEffect } from 'react';
|
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 Animated, { FadeInDown, FadeOutUp } from 'react-native-reanimated';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { useFetchLiveAuction, useFetchLiveTender } from '../../Services/BBPS/Hooks';
|
import { useFetchLiveAuction, useFetchLiveTender } from '../../Services/ApiCall/Hooks';
|
||||||
import { SearchInput } from '../../Component/Serach/SeacrhInput';
|
import { SearchInput } from '../../Component/Serach/SeacrhInput';
|
||||||
import { Skelton } from '../../Component/Index';
|
import { Skelton } from '../../Component/Index';
|
||||||
import colors from '../../Constant/Color';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { togglePurchaseTender } from '../../Redux/Slices/SelectedPurchaseTender';
|
import { togglePurchaseTender } from '../../Redux/Slices/SelectedPurchaseTender';
|
||||||
import LiveTenderTile from './LiveTenderTile';
|
import LiveTenderTile from './LiveTenderTile';
|
||||||
|
import { useTheme } from '../../Hooks/useTheme'; // Using Theme
|
||||||
|
|
||||||
const LiveTender = () => {
|
const LiveTender = () => {
|
||||||
const navigation: any = useNavigation();
|
const navigation: any = useNavigation();
|
||||||
const Dispatch = useDispatch()
|
const Dispatch = useDispatch()
|
||||||
|
const { colors } = useTheme(); // Hook
|
||||||
const user = useSelector((state: any) => state?.token.token);
|
const user = useSelector((state: any) => state?.token.token);
|
||||||
console.log("userdata",user)
|
|
||||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender);
|
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender);
|
||||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch }: any = useFetchLiveTender();
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch }: any = useFetchLiveTender();
|
||||||
|
|
||||||
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
|
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -49,6 +48,7 @@ const LiveTender = () => {
|
|||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({ item, index }: any) => {
|
({ item, index }: any) => {
|
||||||
|
if (item.tenderStatus === "Cancelled") return null;
|
||||||
const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.tenderId === item.tenderId)
|
const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.tenderId === item.tenderId)
|
||||||
return <LiveTenderTile
|
return <LiveTenderTile
|
||||||
contractorId={user?.id}
|
contractorId={user?.id}
|
||||||
@@ -69,13 +69,20 @@ const LiveTender = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Skelton />;
|
return <View style={{ flex: 1, backgroundColor: colors.background }}>
|
||||||
|
<Skelton />
|
||||||
|
</View>;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("data live : ", data )
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1, backgroundColor: colors.background }}>
|
||||||
<SearchInput onChange={() => { }} placeholder="Search...." />
|
{/* Liquid Background Blobs */}
|
||||||
|
<View style={styles.blobContainer}>
|
||||||
|
<View style={[styles.blob, { top: -50, right: -50, backgroundColor: colors.primary, opacity: 0.05 }]} />
|
||||||
|
<View style={[styles.blob, { top: 200, left: -100, backgroundColor: colors.secondary, opacity: 0.05 }]} />
|
||||||
|
<View style={[styles.blob, { bottom: -50, right: -20, backgroundColor: colors.primary, opacity: 0.05 }]} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<SearchInput onChange={() => { }} placeholder="Search Tenders..." filter={false} />
|
||||||
|
|
||||||
{showRefreshBanner && (
|
{showRefreshBanner && (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
@@ -90,7 +97,7 @@ const LiveTender = () => {
|
|||||||
paddingHorizontal: 14,
|
paddingHorizontal: 14,
|
||||||
marginTop: 6,
|
marginTop: 6,
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: colors.surface,
|
||||||
shadowColor: '#000',
|
shadowColor: '#000',
|
||||||
shadowOpacity: 0.06,
|
shadowOpacity: 0.06,
|
||||||
shadowRadius: 8,
|
shadowRadius: 8,
|
||||||
@@ -105,7 +112,7 @@ const LiveTender = () => {
|
|||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: '#374151',
|
color: colors.text,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -127,7 +134,7 @@ const LiveTender = () => {
|
|||||||
<RefreshControl
|
<RefreshControl
|
||||||
refreshing={isRefetching}
|
refreshing={isRefetching}
|
||||||
onRefresh={refetch}
|
onRefresh={refetch}
|
||||||
tintColor="transparent"
|
tintColor={colors.primary}
|
||||||
colors={[colors.primary]}
|
colors={[colors.primary]}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -136,11 +143,23 @@ const LiveTender = () => {
|
|||||||
windowSize={7}
|
windowSize={7}
|
||||||
removeClippedSubviews
|
removeClippedSubviews
|
||||||
updateCellsBatchingPeriod={50}
|
updateCellsBatchingPeriod={50}
|
||||||
onRefresh={refetch}
|
|
||||||
refreshing={isRefetching}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
blobContainer: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
zIndex: -1,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
blob: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: 300,
|
||||||
|
height: 300,
|
||||||
|
borderRadius: 150,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default LiveTender;
|
export default LiveTender;
|
||||||