merge signup process

This commit is contained in:
rohit-kumar-fullstack
2026-01-13 12:38:43 +05:30
33 changed files with 4412 additions and 2325 deletions
+221 -2123
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -36,6 +36,7 @@
"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-file-viewer": "^2.1.5", "react-native-file-viewer": "^2.1.5",
"react-native-fs": "^2.20.0", "react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.30.0", "react-native-gesture-handler": "^2.30.0",
@@ -63,7 +64,7 @@
"@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.0", "@react-native-community/cli": "^18.0.1",
"@react-native-community/cli-platform-android": "18.0.0", "@react-native-community/cli-platform-android": "18.0.0",
"@react-native-community/cli-platform-ios": "18.0.0", "@react-native-community/cli-platform-ios": "18.0.0",
"@react-native/babel-preset": "0.79.0", "@react-native/babel-preset": "0.79.0",
+25
View File
@@ -11,16 +11,41 @@ export const apiCall = async <R, D = {}>(
): Promise<R> => { ): Promise<R> => {
try { try {
const token: any = store.getState().token.token; const token: any = store.getState().token.token;
console.log("url : ", url)
console.log("token : ", token)
// Generate curl command
let curlCmd = `curl -X ${method.toUpperCase()} "${url}"`;
if (token?.token) curlCmd += ` -H "Authentication: Bearer ${token.token}"`;
curlCmd += ` -H "Content-Type: ${contentType || 'application/json'}"`;
if (data) {
if (contentType === 'multipart/form-data') {
curlCmd += ` --form "${JSON.stringify(data).slice(1,-1).replace(/"/g, '\\"')}"`;
} else {
curlCmd += ` -d '${JSON.stringify(data)}'`;
}
}
if (params) {
curlCmd += ` ${Object.entries(params).map(([k,v]) => `-${k}=${v}`).join(' ')}`;
}
console.log("=== CURL ===");
console.log(curlCmd);
console.log("============");
const response = await axios({ const response = await axios({
method, method,
url, url,
data, data,
params, params,
// withCredentials: true,
// baseURL: Variable.Main_Base,
headers: { headers: {
Authentication: `Bearer ${token?.token}`, Authentication: `Bearer ${token?.token}`,
'Content-Type': contentType || 'application/json', 'Content-Type': contentType || 'application/json',
}, },
}); });
console.log("Api response",response)
return response.data; return response.data;
} catch (error: any) { } catch (error: any) {
const errorMessage = error?.response?.data?.error; const errorMessage = error?.response?.data?.error;
+137
View File
@@ -0,0 +1,137 @@
import React from 'react';
import { StyleSheet, Text, View, StyleProp, ViewStyle, TextStyle } from 'react-native';
import { Dropdown } from 'react-native-element-dropdown';
import { Shield } from 'lucide-react-native';
const colors = {
primary: '#F97316',
primaryLight: '#FDBA74',
primaryHighlight: '#f4f1efff',
secondary: '#1B4B66',
secondaryLight: '#639599',
success: '#22C55E',
error: '#EF4444',
warning: '#F59E0B',
info: '#3B82F6',
golden: '#FFD700',
white: '#FFFFFF',
black: '#000000',
darkText: '#2B2B2B',
grayText: '#6B7280',
lightText: '#9CA3AF',
border: '#E5E7EB',
lightGray: '#F9FAFB',
disabled: '#D1D5DB',
placeholder: '#9CA3AF',
backdrop: 'rgba(0,0,0,0.5)',
shadow: 'rgba(0,0,0,0.12)',
Orange: '#F97316',
OrangeLight: '#FDBA74',
OrangeSoft: '#FFF3E8',
darkGray: '#5f6368',
lightestGray: '#f1f3f4',
successLight: '#e6f4ea',
};
type DropdownItem = {
[key: string]: any;
};
type MyDropdownProps = {
label?: string;
data: DropdownItem[];
placeholder?: string;
labelField: string;
valueField: string;
value: any;
onChange: (item: DropdownItem) => void;
style?: StyleProp<ViewStyle>;
containerStyle?: StyleProp<ViewStyle>;
placeholderStyle?: StyleProp<TextStyle>;
selectedTextStyle?: StyleProp<TextStyle>;
focusedField?: boolean;
onFocus?: () => void;
onBlur?: () => void;
dropdownStyle?: StyleProp<ViewStyle>;
[key: string]: any;
};
const MyDropdown: React.FC<MyDropdownProps> = ({
label,
data,
placeholder = 'Select an option',
labelField,
valueField,
value,
onChange,
style,
containerStyle,
placeholderStyle,
selectedTextStyle,
focusedField,
onFocus,
onBlur,
dropdownStyle,
...rest
}) => {
return (
<View style={[{ zIndex: 4000 }, containerStyle]}>
{label && <Text style={styles.label}>{label}</Text>}
<Dropdown
data={data}
labelField={labelField}
valueField={valueField}
placeholder={placeholder}
value={value}
onChange={onChange}
style={[
styles.dropdown,
style,
{ borderColor: focusedField ? colors.primary : '#e0e0e0' },
]}
containerStyle={[styles.dropdownContainer, dropdownStyle]}
selectedTextStyle={[styles.selectedText, selectedTextStyle]}
placeholderStyle={[styles.placeholderText, placeholderStyle]}
onFocus={onFocus}
onBlur={onBlur}
search
searchPlaceholder="Search Class"
renderLeftIcon={() => (
<Shield
style={{ marginRight: 10 }}
color={focusedField ? colors.primary : '#aaa'}
size={20}
/>
)}
{...rest}
/>
</View>
);
};
export default MyDropdown;
const styles = StyleSheet.create({
label: {
marginVertical: 10,
color: colors.darkText,
},
dropdown: {
borderWidth: 1,
borderRadius: 8,
paddingHorizontal: 10,
paddingVertical: 12,
borderColor: colors.border,
backgroundColor: colors.white,
},
dropdownContainer: {
borderRadius: 12,
borderColor: colors.border,
},
selectedText: {
color: colors.darkText,
},
placeholderText: {
color: colors.placeholder,
},
});
@@ -0,0 +1,75 @@
import { StyleSheet, Text, TouchableOpacity, ViewStyle } from 'react-native';
import React from 'react';
import LinearGradient from 'react-native-linear-gradient';
import colors from '../../Constant/Color';
type PrimaryButtonProps = {
title: string;
onPress: () => void | any;
style?: ViewStyle;
disabled?: boolean;
};
const PrimaryButton: React.FC<PrimaryButtonProps> = ({ title, onPress, style, disabled }) => {
return (
<LinearGradient
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]}
>
<TouchableOpacity
style={styles.touchable}
onPress={onPress}
activeOpacity={0.82}
disabled={disabled}
>
<Text style={styles.buttonText}>{title}</Text>
</TouchableOpacity>
</LinearGradient>
);
};
export default PrimaryButton;
const styles = StyleSheet.create({
primaryButton: {
borderRadius: 14,
paddingVertical: 16,
paddingHorizontal: 28,
marginBottom: 20,
minHeight: 54,
// Orange-themed shadows
shadowColor: colors.primary,
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.3,
shadowRadius: 12,
elevation: 10,
overflow: 'hidden',
},
touchable: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
buttonText: {
textAlign: 'center',
color: colors.white,
fontWeight: '800', // Extra bold for orange contrast
fontSize: 16,
letterSpacing: 0.3,
textShadowColor: 'rgba(0,0,0,0.2)',
textShadowOffset: { width: 0, height: 1 },
textShadowRadius: 2,
includeFontPadding: false,
},
});
+109
View File
@@ -0,0 +1,109 @@
import React, { useRef, useEffect } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
Animated,
} from 'react-native';
import colors from '../Constant/Color';
type StepProgressBarProps = {
currentStep: number;
steps: string[];
onStepPress?: (stepIndex: number) => void;
};
const StepProgressBar: React.FC<StepProgressBarProps> = ({
currentStep,
steps,
onStepPress = () => {},
}) => {
const animations = useRef<Animated.Value[]>(
steps.map(() => new Animated.Value(1))
).current;
useEffect(() => {
animations.forEach((anim, index) => {
Animated.timing(anim, {
toValue: index === currentStep ? 1.2 : 1,
duration: 300,
useNativeDriver: true,
}).start();
});
}, [currentStep]);
return (
<View style={styles.container}>
{steps.map((label, index) => {
const isActive = index === currentStep;
const isCompleted = index < currentStep;
return (
<TouchableOpacity
key={index}
onPress={() => onStepPress(index)}
style={styles.stepContainer}
activeOpacity={0.8}
>
<Animated.View
style={[
styles.circle,
isCompleted && styles.completedCircle,
isActive && styles.activeCircle,
{
transform: [{ scale: animations[index] }],
},
]}
/>
<Text
style={[
styles.label,
isCompleted && { color: '#4caf50' },
isActive && { color: '#2196f3' },
]}
>
{label}
</Text>
</TouchableOpacity>
);
})}
</View>
);
};
export default StepProgressBar;
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 20,
flexWrap: 'wrap',
justifyContent: 'center',
},
stepContainer: {
alignItems: 'center',
width: 70,
},
circle: {
width: 50,
height: 5,
borderRadius: 25,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.primary,
},
completedCircle: {
backgroundColor: '#4caf50',
},
activeCircle: {
backgroundColor: '#2196f3',
},
label: {
// marginTop: 6,
// fontSize: 12,
textAlign: 'center',
color: '#555',
},
});
@@ -0,0 +1,201 @@
import React, { ReactNode } from 'react';
import { View, Text, Modal, ScrollView, TouchableOpacity, StyleSheet, Dimensions } from 'react-native';
import { CheckCircle, X } from 'lucide-react-native'; // Updated icons
import colors from '../../Constant/Color';
type ModalProps = {
visible: boolean;
onClose: () => void;
onChecked: () => void
}
const TermsAndConditionsModal: React.FC<ModalProps> = ({ visible, onClose, onChecked }) => {
return (
<Modal
animationType="slide"
transparent={false}
visible={visible}
onRequestClose={onClose}
>
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.scrollContainer}>
<View style={styles.header}>
<Text style={styles.title}>Terms & Conditions</Text>
<Text style={styles.subtitle}>Last updated: {new Date().toLocaleDateString()}</Text>
</View>
<View style={styles.content}>
<Section title="1. Acceptance of Terms">
<Text style={styles.paragraph}>
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.
</Text>
</Section>
<Section title="2. User Responsibilities">
<Text style={styles.paragraph}>
You are responsible for maintaining the confidentiality of your account information and
for all activities that occur under your account. You agree to notify us immediately
of any unauthorized use of your account.
</Text>
</Section>
<Section title="3. Intellectual Property">
<Text style={styles.paragraph}>
All content included on our platform, such as text, graphics, logos, and software, is
the property of our company and protected by intellectual property laws. You may not
reproduce, distribute, or create derivative works without our express permission.
</Text>
</Section>
<Section title="4. Limitation of Liability">
<Text style={styles.paragraph}>
Our company shall not be liable for any indirect, incidental, special, or consequential
damages resulting from the use or inability to use our services, even if we have been
advised of the possibility of such damages.
</Text>
</Section>
<Section title="5. Privacy Policy">
<Text style={styles.paragraph}>
Your use of our services is also governed by our Privacy Policy, which explains how we
collect, use, and protect your personal information. Please review our Privacy Policy
to understand our practices.
</Text>
</Section>
<Section title="6. Changes to Terms">
<Text style={styles.paragraph}>
We reserve the right to modify these terms at any time. We will notify you of any changes
by posting the new Terms and Conditions on this page. Your continued use of the service
after such modifications constitutes your acceptance of the new terms.
</Text>
</Section>
<Section title="7. Governing Law">
<Text style={styles.paragraph}>
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.
</Text>
</Section>
<Text style={styles.footerText}>
If you have any questions about these Terms, please contact us at legal@company.com.
</Text>
</View>
{/* Accept Button */}
<TouchableOpacity
style={styles.acceptButton}
onPress={() => {
onClose();
onChecked();
}}
>
<CheckCircle color={colors.white} size={28} />
<Text style={styles.acceptButtonText}>I Accept</Text>
</TouchableOpacity>
</ScrollView>
</View>
</Modal>
);
};
// Reusable Section component
type SectionProps = {
title: string;
children: ReactNode;
};
const Section: React.FC<SectionProps> = ({ title, children }) => (
<View style={styles.section}>
<Text style={styles.sectionTitle}>{title}</Text>
{children}
</View>
);
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.lightGray, // Updated with your colors
},
scrollContainer: {
paddingBottom: 120, // Space for accept button
flexGrow: 1,
},
header: {
backgroundColor: colors.primary, // Orange header
paddingVertical: 32,
paddingHorizontal: 24,
borderBottomLeftRadius: 24,
borderBottomRightRadius: 24,
marginBottom: 24,
shadowColor: colors.shadow,
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.25,
shadowRadius: 12,
elevation: 8,
},
title: {
fontSize: 28,
fontWeight: '800',
color: colors.white,
marginBottom: 4,
},
subtitle: {
fontSize: 14,
color: 'rgba(255,255,255,0.85)',
},
content: {
paddingHorizontal: 24,
paddingBottom: 40,
},
section: {
marginBottom: 28,
},
sectionTitle: {
fontSize: 20,
fontWeight: '700',
color: colors.darkText,
marginBottom: 12,
},
paragraph: {
fontSize: 16,
lineHeight: 25,
color: colors.grayText,
},
footerText: {
fontSize: 15,
fontStyle: 'italic',
color: colors.lightText,
marginTop: 24,
textAlign: 'center',
lineHeight: 22,
},
acceptButton: {
position: 'absolute',
bottom: 40,
left: 24,
right: 24,
backgroundColor: colors.primary,
borderRadius: 16,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 18,
paddingHorizontal: 20,
shadowColor: colors.shadow,
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.35,
shadowRadius: 16,
elevation: 12,
},
acceptButtonText: {
color: colors.white,
fontSize: 18,
fontWeight: '700',
marginLeft: 12,
letterSpacing: 0.5,
},
});
export default TermsAndConditionsModal;
+1
View File
@@ -7,6 +7,7 @@ const colors = {
// Secondary (Warm contrast) // Secondary (Warm contrast)
secondary: '#1B4B66', // Deep blue for balance secondary: '#1B4B66', // Deep blue for balance
secondaryLight: '#639599', // Muted blue secondaryLight: '#639599', // Muted blue
secondaryHighLight: '#e1eff2', // Very light blue (backgrounds)
// Status colors // Status colors
success: '#22C55E', // Green success: '#22C55E', // Green
+3
View File
@@ -15,6 +15,9 @@ const NavigationString = {
ProceedToPurTender:'ProceedToPurTender', ProceedToPurTender:'ProceedToPurTender',
TenderBidding:'TenderBidding', TenderBidding:'TenderBidding',
ProceedToBidDetail:'ProceedToBidDetail', ProceedToBidDetail:'ProceedToBidDetail',
Signup:'Signup',
Registration:'Registration',
Partnership:'Partnership',
LiveTenderDetail:'LiveTenderDetail', LiveTenderDetail:'LiveTenderDetail',
AuctionDetail: 'AuctionDetail', AuctionDetail: 'AuctionDetail',
} }
+18
View File
@@ -0,0 +1,18 @@
export const formatDateInput = (text: string) => {
// Remove any non-digit characters
const cleaned = text.replace(/\D/g, '');
let formatted = '';
if (cleaned.length <= 2) {
formatted = cleaned;
} else if (cleaned.length <= 4) {
formatted = `${cleaned.slice(0, 2)}/${cleaned.slice(2)}`;
} else {
formatted = `${cleaned.slice(0, 2)}/${cleaned.slice(
2,
4,
)}/${cleaned.slice(4, 8)}`;
}
return formatted;
};
+9
View File
@@ -0,0 +1,9 @@
import { Platform, ToastAndroid } from "react-native";
export const showToast = (message: string) => {
if (Platform.OS === 'android') {
ToastAndroid.show(message, ToastAndroid.SHORT);
} else {
console.log(message); // Fallback for iOS or other platforms
}
};
+5 -1
View File
@@ -29,8 +29,10 @@ export default function MainStack() {
}} }}
> >
<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.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={Screens.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} />
@@ -40,8 +42,10 @@ export default function MainStack() {
<Stack.Screen name={NavigationString.ProceedToPurTender} component={Screens.ProceedToPurTender} options={option} /> <Stack.Screen name={NavigationString.ProceedToPurTender} component={Screens.ProceedToPurTender} options={option} />
<Stack.Screen name={NavigationString.TenderBidding} component={Screens.TenderBidding} options={option} /> <Stack.Screen name={NavigationString.TenderBidding} component={Screens.TenderBidding} options={option} />
<Stack.Screen name={NavigationString.ProceedToBidDetail} component={Screens.ProceedToBidDetail} options={option} /> <Stack.Screen name={NavigationString.ProceedToBidDetail} component={Screens.ProceedToBidDetail} options={option} />
<Stack.Screen name={NavigationString.LiveTenderDetail} component={Screens.LiveTenderDetail} options={option} />
<Stack.Screen name={NavigationString.StartReverseBidding} component={Screens.StartReverseBidding} options={option} /> <Stack.Screen name={NavigationString.StartReverseBidding} component={Screens.StartReverseBidding} options={option} />
<Stack.Screen name={NavigationString.Registration} component={Screens.Registration} options={option} />
<Stack.Screen name={NavigationString.Partnership} component={Screens.Partnership} options={option} />
<Stack.Screen name={NavigationString.LiveTenderDetail} component={Screens.LiveTenderDetail} options={option} />
</Stack.Navigator> </Stack.Navigator>
); );
@@ -32,6 +32,7 @@ const PurchaseAuctionList = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [list, setList] = useState([]); const [list, setList] = useState([]);
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();
+1
View File
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet, TextInput, Alert } from 'react-native'; import { View, Text, FlatList, TouchableOpacity, StyleSheet, TextInput, Alert } from 'react-native';
import { pick } from '@react-native-documents/picker'; import { pick } from '@react-native-documents/picker';
import Animated, { FadeInDown, FadeOutUp, Layout } from 'react-native-reanimated'; import Animated, { FadeInDown, FadeOutUp, Layout } from 'react-native-reanimated';
// import BouncyCheckbox from 'react-native-bouncy-checkbox';
import BouncyCheckbox from 'react-native-bouncy-checkbox'; import BouncyCheckbox from 'react-native-bouncy-checkbox';
import { InsideHeader, Loader, Skelton } from '../../Component/Index'; import { InsideHeader, Loader, Skelton } from '../../Component/Index';
import colors from '../../Constant/Color'; import colors from '../../Constant/Color';
+204 -70
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { View, Text, Dimensions, TextInput, TouchableOpacity, StyleSheet, KeyboardAvoidingView, Platform, ScrollView } from 'react-native'; import { View, Text, Dimensions, TextInput, TouchableOpacity, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, Alert } from 'react-native';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import { Formik } from 'formik'; import { Formik } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -7,17 +7,18 @@ import Animated, { FadeInUp, FadeInDown } from 'react-native-reanimated';
import { Mail, Lock, Eye, EyeOff, ArrowRight } from 'lucide-react-native'; import { Mail, Lock, Eye, EyeOff, ArrowRight } from 'lucide-react-native';
import colors from '../../Constant/Color'; import colors from '../../Constant/Color';
import ImagePath from '../../Constant/ImagePath'; import ImagePath from '../../Constant/ImagePath';
import { useLogin } from '../../Services/BBPS/Hooks'; import { useAuctionLogin, usefetchMasterData, useGetDesignation, useLogin, useRagistrationState } from '../../Services/BBPS/Hooks';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { setToken } from '../../Redux/Slices/Token'; import { setToken } from '../../Redux/Slices/Token';
import { showErrorAlert } from '../../Constant/ShowDailog'; import { showErrorAlert } from '../../Constant/ShowDailog';
import { useNavigation, CommonActions } from '@react-navigation/native'; import { useNavigation, CommonActions } from '@react-navigation/native';
import NavigationString from '../../Constant/NavigationString'; import NavigationString from '../../Constant/NavigationString';
import ReactNativeHapticFeedback from "react-native-haptic-feedback"; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Loader } from '../../Component/Index'; import { Loader } from '../../Component/Index';
const hapticOptions = { const hapticOptions = {
enableVibrateFallback: true, enableVibrateFallback: true,
ignoreAndroidSystemSettings: false ignoreAndroidSystemSettings: false,
}; };
const { height, width } = Dimensions.get('window'); const { height, width } = Dimensions.get('window');
@@ -27,50 +28,141 @@ const LoginSchema = Yup.object().shape({
}); });
const Login = () => { const Login = () => {
const Navigation: any = useNavigation() const Navigation: any = useNavigation();
const Dispatch = useDispatch() const Dispatch = useDispatch();
const [passwordVisible, setPasswordVisible] = React.useState(false); const [passwordVisible, setPasswordVisible] = React.useState(false);
const [loading, setLoading] = useState(false) const [activeType, setActiveType] = useState<'tender' | 'auction'>('tender');
const { mutate } = useLogin() const [loginLoading, setLoginLoading] = useState(false);
const [auctionLoading, setAuctionLoading] = useState(false);
const { mutate: loginMutate } = useLogin();
const { mutate: auctionMutate } = useAuctionLogin();
const {data}=useGetDesignation();
const { mutate: registrationStateMutate } = useRagistrationState();
const HandleLogin = async (values: any) => { const { data: MasterData}=usefetchMasterData()
setLoading(true)
mutate({ password: values.password, emailAdd: values.email }, { const handleGetData = async () => {
onSuccess: (res) => { registrationStateMutate(undefined, {
if (res.statusCode == 200) { onSuccess: (res: any) => {
Dispatch(setToken({ ...res.data, token: res.token })) console.log('Registration status response try :', res);
Navigation.dispatch(
CommonActions.reset({ if (res?.statusCode === 200) {
index: 0, const { registrationStatus, contOtherId, contOrganization } = res.data;
routes: [ const params = { otherId: contOtherId, orgType: contOrganization };
{ name: NavigationString.Home },
],
}) switch (registrationStatus) {
); case null:
case undefined:
Navigation.replace(NavigationString.Registration);
break;
case 'Pending':
// handleRoute(params);
break;
case 'Complete':
case 'Completed':
Navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: NavigationString.Home }],
}),
);
break;
default:
{
console.log("attempting navigation to Ragistration")
Navigation.replace(NavigationString.Registration);}
break;
} }
setLoading(false) return;
},
onError: (err) => {
showErrorAlert(err.message)
setLoading(false)
} }
})
} if (res?.statusCode === 404) {
console.warn('Registration API returned 404');
Navigation.replace('Registration');
return;
}
Navigation.replace('Registration');
},
onError: (error: any) => {
console.error('Registration status error catch :', error);
if (error?.response?.status === 500) {
Alert.alert('Server error', 'Something went wrong. Please try again later.');
return;
}
if (error?.request) {
Alert.alert('Network Error', 'Please check your internet connection.');
return;
}
Alert.alert('Error', error?.message || 'Something went wrong');
Navigation.replace('Registration');
},
});
};
const HandleLogin = async (values: any) => {
if (activeType === 'tender') {
setLoginLoading(true);
loginMutate(
{ password: values.password, emailAdd: values.email },
{
onSuccess: res => {
if (res.statusCode == 200) {
Dispatch(setToken({ ...res.data, token: res.token }));
handleGetData();
}
setLoginLoading(false);
},
onError: err => {
showErrorAlert(err.message);
setLoginLoading(false);
},
},
);
} else {
setAuctionLoading(true);
auctionMutate(
{ password: values.password, emailAdd: values.email },
{
onSuccess: res => {
if (res.statusCode == 200) {
Dispatch(setToken({ ...res.data, token: res.token }));
Navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: NavigationString.Home }],
}),
);
}
setAuctionLoading(false);
},
onError: err => {
showErrorAlert(err.message);
setAuctionLoading(false);
},
},
);
}
};
const isLoading = activeType === 'tender' ? loginLoading : auctionLoading;
return ( return (
<View style={styles.container}> <View style={styles.container}>
<KeyboardAvoidingView <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={{ flex: 1 }}>
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<ScrollView contentContainerStyle={{ flexGrow: 1 }} bounces={false}> <ScrollView contentContainerStyle={{ flexGrow: 1 }} bounces={false}>
{/* Header */}
<View style={styles.header}> <View style={styles.header}>
<LinearGradient <LinearGradient colors={[colors.primary, colors.primaryLight || '#4c669f']} style={styles.gradient}>
colors={[colors.primary, colors.primaryLight || '#4c669f']}
style={styles.gradient}
>
<Animated.Image <Animated.Image
entering={FadeInDown.delay(200).duration(800)} entering={FadeInDown.delay(200).duration(800)}
source={ImagePath.Icon.Logo} source={ImagePath.Icon.Logo}
@@ -80,24 +172,42 @@ const Login = () => {
</LinearGradient> </LinearGradient>
</View> </View>
<Animated.View <Animated.View entering={FadeInUp.springify().damping(15)} style={styles.formContainer}>
entering={FadeInUp.springify().damping(15)}
style={styles.formContainer}
>
<View style={styles.sheetHandle} /> <View style={styles.sheetHandle} />
<View style={styles.toggleWrapper}>
<View style={styles.toggleContainer}>
<TouchableOpacity
activeOpacity={0.8}
style={[styles.toggleButton, activeType === 'tender' && styles.toggleButtonActive]}
onPress={() => setActiveType('tender')}
disabled={isLoading}
>
<Text style={[styles.toggleText, activeType === 'tender' && styles.toggleTextActive]}>Tender</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.8}
style={[styles.toggleButton, activeType === 'auction' && styles.toggleButtonActive]}
onPress={() => setActiveType('auction')}
disabled={isLoading}
>
<Text style={[styles.toggleText, activeType === 'auction' && styles.toggleTextActive]}>Auction</Text>
</TouchableOpacity>
</View>
</View>
<Text style={styles.title}>Welcome Back</Text> <Text style={styles.title}>Welcome Back</Text>
<Text style={styles.subtitle}>Enter your details to access your account</Text> <Text style={styles.subtitle}>Enter your details to access your account</Text>
<Formik <Formik
initialValues={{ email: '', password: '' }} initialValues={{ email: '', password: '' }}
validationSchema={LoginSchema} validationSchema={LoginSchema}
onSubmit={(values) => { onSubmit={values => {
HandleLogin(values) HandleLogin(values);
}} }}
> >
{({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => ( {({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => (
<View style={{}}> <View>
<Text style={styles.label}>Email Address</Text> <Text style={styles.label}>Email Address</Text>
<View style={[styles.inputWrapper, touched.email && errors.email && styles.inputError]}> <View style={[styles.inputWrapper, touched.email && errors.email && styles.inputError]}>
<Mail size={20} color={touched.email && !errors.email ? colors.primary : colors.grayText} /> <Mail size={20} color={touched.email && !errors.email ? colors.primary : colors.grayText} />
@@ -139,11 +249,11 @@ const Login = () => {
<TouchableOpacity <TouchableOpacity
activeOpacity={0.8} activeOpacity={0.8}
onPress={() => { onPress={() => {
ReactNativeHapticFeedback.trigger("impactLight", hapticOptions); ReactNativeHapticFeedback.trigger('impactLight', hapticOptions);
handleSubmit(); handleSubmit();
}} }}
style={styles.buttonShadow} style={styles.buttonShadow}
disabled={isLoading}
> >
<LinearGradient <LinearGradient
colors={[colors.primary, colors.primaryLight || '#4c669f']} colors={[colors.primary, colors.primaryLight || '#4c669f']}
@@ -151,13 +261,25 @@ const Login = () => {
end={{ x: 1, y: 0 }} end={{ x: 1, y: 0 }}
style={styles.button} style={styles.button}
> >
{ {isLoading ? (
loading ? <Loader color={colors.white} size='small' /> : <> <Loader color={colors.white} size="small" />
) : (
<>
<Text style={styles.buttonText}>Sign In</Text> <Text style={styles.buttonText}>Sign In</Text>
<ArrowRight size={20} color={colors.white} style={{ marginLeft: 8, marginTop: 5 }} /></> <ArrowRight size={20} color={colors.white} style={{ marginLeft: 8, marginTop: 5 }} />
} </>
)}
</LinearGradient> </LinearGradient>
</TouchableOpacity> </TouchableOpacity>
<View style={{ alignItems: 'center', marginTop: 16 }}>
<View style={{ flexDirection: 'row' }}>
<Text>Don&apos;t have an account? </Text>
<TouchableOpacity onPress={() => Navigation.navigate('Signup')}>
<Text style={{ color: colors.primary, fontWeight: '700' }}>Sign up</Text>
</TouchableOpacity>
</View>
</View>
</View> </View>
)} )}
</Formik> </Formik>
@@ -194,12 +316,10 @@ const styles = StyleSheet.create({
borderTopRightRadius: 35, borderTopRightRadius: 35,
paddingHorizontal: 28, paddingHorizontal: 28,
paddingTop: 10, paddingTop: 10,
// Shadow for iOS
shadowColor: '#000', shadowColor: '#000',
shadowOffset: { width: 0, height: -10 }, shadowOffset: { width: 0, height: -10 },
shadowOpacity: 0.1, shadowOpacity: 0.1,
shadowRadius: 10, shadowRadius: 10,
// Elevation for Android
elevation: 20, elevation: 20,
}, },
sheetHandle: { sheetHandle: {
@@ -208,8 +328,37 @@ const styles = StyleSheet.create({
backgroundColor: '#E0E0E0', backgroundColor: '#E0E0E0',
borderRadius: 10, borderRadius: 10,
alignSelf: 'center', alignSelf: 'center',
marginBottom: 16,
},
toggleWrapper: {
alignItems: 'center',
marginBottom: 20, marginBottom: 20,
}, },
toggleContainer: {
flexDirection: 'row',
backgroundColor: '#F1F3F5',
borderRadius: 20,
padding: 4,
width: width * 0.5,
},
toggleButton: {
flex: 1,
paddingVertical: 8,
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
},
toggleButtonActive: {
backgroundColor: colors.primary,
},
toggleText: {
fontSize: 14,
fontWeight: '600',
color: '#7C7C7C',
},
toggleTextActive: {
color: colors.white,
},
title: { title: {
fontSize: 28, fontSize: 28,
fontWeight: '800', fontWeight: '800',
@@ -286,21 +435,6 @@ const styles = StyleSheet.create({
fontSize: 18, fontSize: 18,
fontWeight: '700', fontWeight: '700',
}, },
footer: {
flexDirection: 'row',
justifyContent: 'center',
marginTop: 30,
paddingBottom: 20,
},
footerText: {
color: '#7C7C7C',
fontSize: 15,
},
signUpText: {
color: colors.primary,
fontWeight: '700',
fontSize: 15,
},
}); });
export default Login; export default Login;
+278
View File
@@ -0,0 +1,278 @@
import {
KeyboardAvoidingView,
SafeAreaView,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
Platform,
} from 'react-native';
import React, { use, useState } from 'react';
import { useNavigation, useRoute, NavigationProp, RouteProp } from '@react-navigation/native';
import colors from '../../Constant/Color';
import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton';
import MyInput from '../../Component/MyInput';
import { showToast } from '../../Constant/ShowDailog';
import { PARTNER_API_ENDPOINT } from '../../Services/BBPS/ApiUrls';
import { usePartnerShipSubmit } from '../../Services/BBPS/Hooks';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Navigation } from 'lucide-react-native';
import { NavigationString } from '../../Constant/AllImports';
type Partner = {
partnerName: string;
percentage: string;
partnerPan: string;
aadharNumber: string;
mobileNumber: string;
};
type Navigation = {
replace: (route: string) => void;
};
type PartnershipParams = {
otherId: string | number
};
const Partnership = () => {
const route = useRoute<RouteProp<{ params: PartnershipParams }, 'params'>>();
const navigation = useNavigation() as Navigation;
const [partners, setPartners] = useState<Partner[]>([
{
partnerName: '',
percentage: '',
partnerPan: '',
aadharNumber: '',
mobileNumber: '',
},
]);
const [focusedField, setFocusedField] = useState<string>('');
const [loading, setLoading] = useState(false)
const { mutateAsync:partnerShipSubmit } = usePartnerShipSubmit();
const insets = useSafeAreaInsets();
const handleChange = (index: number, name: keyof Partner, value: string) => {
const updated = [...partners];
updated[index][name] = value;
setPartners(updated);
};
const handleAddMore = () => {
setPartners(prev => [
...prev,
{
partnerName: '',
percentage: '',
partnerPan: '',
aadharNumber: '',
mobileNumber: '',
},
]);
};
const handleRemove = (index: number) => {
const updated = [...partners];
updated.splice(index, 1);
setPartners(
updated.length > 0
? updated
: [
{
partnerName: '',
percentage: '',
partnerPan: '',
aadharNumber: '',
mobileNumber: '',
},
]
);
};
const handleSubmit = async () => {
setLoading(true)
try {
// Convert percentage strings to numbers and validate
const payload = partners.map((p, i) => {
const percentage = Number(p.percentage);
if (isNaN(percentage) || percentage < 0 || percentage > 100) {
throw new Error(`Invalid percentage value for partner ${p.partnerName}`);
}
return {
contOtherId: route?.params?.otherId,
ptnrAadharNo: p.aadharNumber,
ptnrMobNo: p.mobileNumber,
ptnrName: p.partnerName,
ptnrPop: percentage, // Ensure this is a number
ptnrPANNo: p.partnerPan,
ptnrNo: i,
};
});
// Validate total percentage equals 100%
const totalPercentage = payload.reduce((sum, p) => sum + p.ptnrPop, 0);
if (totalPercentage !== 100) {
showToast(`Total percentage must equal 100% (currently ${totalPercentage}%)`);
return;
}
console.log("Partner ship payload:", payload);
const response = await partnerShipSubmit(payload);
console.log("first ====== ", response)
if (response?.statusCode === 200) {
console.log('✅ Partners submitted successfully:', response);
showToast(response.message || 'Submitted successfully!');
navigation.replace(NavigationString.Home);
}
} catch (error: any) {
console.error('❌ Submission failed:', error.message);
showToast(error.message || 'Submission failed. Please check partner details.');
}finally{
setLoading(false)
}
};
return (
<View style={{ flex: 1 }}>
<Text style={[styles.heading, { paddingTop: insets.top }]}>Partnership Details</Text>
<SafeAreaView style={{ flex: 1 }}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={40}
>
<ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled">
{partners.map((partner, index) => (
<View style={{ marginBottom: 20 }} key={index}>
<Text style={styles.partnerTitle}>Partner {index + 1}</Text>
<MyInput
icon="user"
placeholder="Name of Partner *"
value={partner.partnerName}
onChangeText={text => handleChange(index, 'partnerName', text)}
onFocus={() => setFocusedField(`partnerName-${index}`)}
onBlur={() => setFocusedField('')}
focused={focusedField === `partnerName-${index}`}
/>
<MyInput
icon="percent"
placeholder="Partnership Percentage *"
value={partner.percentage}
onChangeText={text => handleChange(index, 'percentage', text)}
onFocus={() => setFocusedField(`percentage-${index}`)}
onBlur={() => setFocusedField('')}
focused={focusedField === `percentage-${index}`}
keyboardType="numeric"
maxlength={3}
/>
<MyInput
icon="id-card"
placeholder="PAN of Partner *"
value={partner.partnerPan}
onChangeText={text => handleChange(index, 'partnerPan', text)}
onFocus={() => setFocusedField(`partnerPan-${index}`)}
onBlur={() => setFocusedField('')}
focused={focusedField === `partnerPan-${index}`}
autoCapitalize="characters"
maxlength={10}
/>
<MyInput
icon="credit-card"
placeholder="Aadhar Number *"
value={partner.aadharNumber}
onChangeText={text => handleChange(index, 'aadharNumber', text)}
onFocus={() => setFocusedField(`aadharNumber-${index}`)}
onBlur={() => setFocusedField('')}
focused={focusedField === `aadharNumber-${index}`}
keyboardType="numeric"
maxlength={12}
/>
<MyInput
icon="phone"
placeholder="Mobile Number *"
value={partner.mobileNumber}
onChangeText={text => handleChange(index, 'mobileNumber', text)}
onFocus={() => setFocusedField(`mobileNumber-${index}`)}
onBlur={() => setFocusedField('')}
focused={focusedField === `mobileNumber-${index}`}
keyboardType="phone-pad"
maxlength={10}
/>
{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}>
<Text style={styles.addText}>+ Add Partner</Text>
</TouchableOpacity>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
<PrimaryButton title={loading ? "Loading..." : "Submit" }onPress={handleSubmit} />
</View>
);
};
export default Partnership;
const styles = StyleSheet.create({
heading: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
marginTop: 10,
textAlign: 'center',
},
partnerTitle: {
fontWeight: '600',
fontSize: 16,
marginBottom: 10,
},
scrollContainer: {
// backgroundColor: color.background,
flexGrow: 1,
},
addButton: {
backgroundColor: colors.primary,
padding: 10,
marginTop: 10,
borderRadius: 10,
alignItems: 'center',
width: 140,
alignSelf: 'flex-end',
},
addText: {
color: 'white',
fontWeight: '600',
},
deleteButton: {
backgroundColor: '#FF5C5C',
padding: 10,
marginTop: 10,
borderRadius: 10,
alignItems: 'center',
width: 140,
alignSelf: 'flex-end',
},
deleteText: {
color: 'white',
fontWeight: '600',
},
});
+867
View File
@@ -0,0 +1,867 @@
import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Platform, KeyboardAvoidingView, SafeAreaView, ScrollView, BackHandler, ToastAndroid, Button, Alert } from 'react-native';
// import AntDesign from 'react-native-vector-icons/AntDesign';
import { ArrowLeft } from 'lucide-react-native';
import { NavigationProp, RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import AsyncStorage from '@react-native-async-storage/async-storage';
// import { sendAadhaarOtp, verifyAadhaarOtp, verifyGstNumber, verifyPanHandlerRes } from '../../utils/helper/ragisterationApiCalls';
// import { getAllMsMeOrg, getCityDropdown, getClassDropdown, getMsmeDropdown, getOrgDropdown, getSocialDropdown, getStateDropdown } from '../../utils/helper/dropdownApis';
import { ApiResponse, DocumentsType, FormDataType, OrgType, UserData } from './types/types';
import { pick } from '@react-native-documents/picker';
// import { REJ_STAGE_TWO } from '../../utils/services/apiUrl';
// import LoaderModel from '../../component/LoaderModal';
import axios from 'axios';
import StageOne from './ragistrationStages/StageOne';
import StageTwo from './ragistrationStages/StageTwo';
import StageZero from './ragistrationStages/StageZero';
import StageThree from './ragistrationStages/StageThree';
import StageFour from './ragistrationStages/StageFour';
import colors from '../../Constant/Color';
import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton';
import StepProgressBar from '../../Component/StepProgressBar';
import { formatDateInput } from '../../Helper/formatDateInput';
import { showToast } from '../../Constant/ShowDailog';
import { useCityDropdown, useContractorStageTwoSubmit, usefetchMasterData, useSendAadhaarOtp, useVerifyAadhaarOtp, useVerifyGstNumber, useVerifyPan } from '../../Services/BBPS/Hooks';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useSelector } from 'react-redux';
type RouteParams = {
userData?: {
firstName?: string;
lastName?: string;
mobileNo?: string;
middleName?: string;
email?: string;
};
};
const Registration: React.FC = () => {
const navigation = useNavigation<any>();
const { params } = useRoute<RouteProp<Record<string, RouteParams>, string>>();
const userData = params as RouteParams;
const [stage, setStage] = useState(0);
const insets = useSafeAreaInsets();
const [focusedField, setFocusedField] = useState<null | string>(null);
const [selectedOrg, setSelectedOrg] = useState<OrgType>(0);
const [documents, setDocuments] = useState<DocumentsType>({
panCard: null,
gstnCertificate: null,
registrationCertificate: null,
msmefile: null,
constractorClass: null,
aadharCard: null,
});
const [formData, setFormData] = useState<FormDataType>({
firstName: '',
middleName: '',
lastName: '',
email: '',
mobileNumber: '',
organizationName: '',
officialWebsite: '',
registrationNumber: '',
contractorClass: '',
orgType: '',
registrationDetail: '',
socialCategory: '',
orgPanNumber: '',
dateOfIncorp: '',
orgPanName: '',
gstnNo: '',
aadhaarNumber: '',
aadhaarOtp: '',
address: '',
state: '',
city: '',
pinCode: '',
agentContact2: '',
expiryDate: '',
});
const [loader, setloader] = useState(false);
const [aadhaarVerifying, setAadhaarVerifying] = useState(false);
const [aadhaarVerified, setAadhaarVerified] = useState(false);
const [panVerifying, setPanVerifying] = useState(false);
const [panVerified, setPanVerified] = useState(false);
const [contractorClassItems, setContractorClassItems] = useState([]);
const [allOrganizations, setAllOrganizations] = useState([]);
const [allSocialCategories, setAllSocialCategories] = useState([]);
const [allMsmeOrgs, setAllMsmeOrgs] = useState([]);
const [adharreference, setadharreference] = useState('');
const [adharotpsend, setadharotpsend] = useState(false);
const [gstverifying, setgstverifying] = useState(false);
const [gstverified, setgstverified] = useState(false);
const [states, setstates] = useState([]);
const [cities, setCities] = useState([]);
const [allMSME, setAllMSME] = useState([]);
const [loading, setLoading] = useState(false);
const { mutateAsync: verifyPanHandlerRes } = useVerifyPan();
const { mutateAsync: sendAadhaarOtp } = useSendAadhaarOtp();
const { mutateAsync: verifyAadhaarOtp } = useVerifyAadhaarOtp();
const { mutateAsync: verifyGstNumber } = useVerifyGstNumber();
const { mutateAsync: contractorStageTwoSubmit } = useContractorStageTwoSubmit();
const { data: masterData } = usefetchMasterData();
console.log("master",masterData)
useEffect(() => {
if (!masterData?.data) return;
const contractorClasses = masterData.data.contractorClasses || [];
const msmeOrgs = masterData.data.msmeOrgs || [];
const mappedmsmeOrgs = msmeOrgs.map((item:any)=> ({
label: item.comName,
value: item.msmeId,
}));
const orgs = masterData.data.organizationTypes || [];
const mappedcontractorClasses = contractorClasses.map((item:any)=> ({
label: item.contClassName || '',
value: item.contClassId,
}));
const mapped = orgs.map((item:any)=> ({
label: item.organizationType,
value: item.orgId,
}));
const socialCategories = masterData.data.socialCategories || [];
const mappedsocialCategories = socialCategories.map((item:any)=> ({
label: item.categoryName,
value: item.socialCategoryId,
}));
const stateList = masterData.data.states || [];
const mappedStates = stateList.map((item:any)=> ({
label: item.stateName,
value: item.stateId,
}));
setAllMsmeOrgs(mappedmsmeOrgs);
setAllSocialCategories(mappedsocialCategories);
setContractorClassItems(mappedcontractorClasses);
setAllOrganizations(mapped);
setstates(mappedStates);
}, [masterData?.data]);
const { mutate: cityDropdownMutate } = useCityDropdown();
const { data: MasterData}=usefetchMasterData()
const user = useSelector((state: any) => state.token.token);
const steps = ['', '', '', '', ''];
const dropdownStyle = {
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 12,
paddingHorizontal: 14,
height: 50,
backgroundColor: '#fff',
marginBottom: 10,
}
useEffect(() => {
const fetchUserData = async (user:any) => {
console.log("hitting it ")
try {
console.log("hitting it 1 ")
console.log("object====",user)
if (user) {
const parsedUser = user;
console.log(parsedUser);
setFormData(prev => ({
...prev,
firstName: parsedUser?.firstName || userData?.userData?.firstName,
lastName: parsedUser?.lastName || userData?.userData?.lastName,
mobileNumber: parsedUser?.mobileNo || userData?.userData?.mobileNo,
middleName: parsedUser?.middleName || userData?.userData?.middleName,
email: parsedUser?.email || userData?.userData?.email,
}));
console.log('User Data from Storage:', parsedUser);
} else {
console.log('No user data found');
}
} catch (error) {
console.error('Error fetching user data:', error);
}
};
fetchUserData(user);
}, []);
const isPANLinkedToGSTIN = (pan: string, gstin: string): { valid: boolean; message: string } => {
pan = pan.toUpperCase();
gstin = gstin.toUpperCase();
const panRegex = /^[A-Z]{5}[0-9]{4}[A-Z]$/;
const gstinRegex = /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/;
if (!panRegex.test(pan)) {
return { valid: false, message: "Invalid PAN format. It should be like 'ABCDE1234F'." };
}
// if (!gstinRegex.test(gstin)) {
// return { valid: false, message: "Invalid GSTIN format. It should be like '22ABCDE1234F1Z5'." };
// }
// const gstinPAN = gstin.slice(2, 12); // Extract PAN part from GSTIN
// if (pan !== gstinPAN) {
// return {
// valid: false,
// message: "PAN does not match the PAN embedded in GSTIN.",
// };
// }
return { valid: true, message: 'PAN and GSTIN are correctly linked.' };
};
const handleChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const verifyPanHandler = async () => {
setPanVerifying(true); // Start loading
const payload = {
dob: formData?.dateOfIncorp.trim(),
name: formData?.orgPanName.trim(),
panNumber: formData?.orgPanNumber.trim(),
};
const result = await verifyPanHandlerRes(payload);
if (result) {
setPanVerified(true);
} else {
setPanVerified(false);
}
setPanVerifying(false); // Stop loading
};
const sendAdharOTPHandler = async (text: string) => {
console.log('Sending Aadhaar OTP...');
setAadhaarVerifying(true);
setadharreference('');
const payload = {
aadhaarNumber: text.trim(),
};
const response = await sendAadhaarOtp(payload);
if (response) {
setadharotpsend(true);
setadharreference(response);
} else {
setadharotpsend(false);
ToastAndroid.show('Failed to send Aadhaar OTP', ToastAndroid.SHORT);
}
setAadhaarVerifying(false);
};
const verifyAdharOtpHandler = async () => {
if (formData?.aadhaarOtp?.length !== 6) {
ToastAndroid.show('Please enter a valid 6-digit OTP', ToastAndroid.SHORT);
return;
}
setAadhaarVerifying(true);
const payload = {
otp: formData.aadhaarOtp,
refNumber: adharreference,
};
const response = await verifyAadhaarOtp(payload);
if (response) {
setAadhaarVerified(true);
ToastAndroid.show('Aadhaar Verified', ToastAndroid.SHORT);
} else {
ToastAndroid.show('Verification failed', ToastAndroid.SHORT);
}
setAadhaarVerifying(false);
};
const verifyGSTHandler = async () => {
setgstverifying(true);
const payload = {
gstNumber: formData?.gstnNo.trim(),
};
const response = await verifyGstNumber(payload);
if (response) {
setgstverified(true);
} else {
ToastAndroid.show('GST verification failed', ToastAndroid.SHORT);
}
setgstverifying(false);
};
const handleDocumentPick = async (field: string) => {
try {
const response = await pick({
type: 'application/pdf',
allowMultiSelection: false,
presentationStyle: 'fullScreen',
});
if (response && response?.length > 0) {
const selectedFile = response[0];
setDocuments(prev => ({
...prev,
[field]: selectedFile,
}));
}
} catch (err) {
console.error('Document pick error:', err);
}
};
const handleSubmit = async () => {
setLoading(true);
const dtoData = {
agentAddress: formData.address,
agentCity: formData.city,
agentState: formData.state,
agentPincode: formData.pinCode,
agentWebsite: formData.officialWebsite,
agentContact2: formData.agentContact2,
indianOrgName: formData.organizationName,
contractorClass: formData.contractorClass,
contOrganization: formData.orgType,
expiryDate: formData.expiryDate,
msmeOrg: formData.registrationDetail,
socialCategory: formData.socialCategory,
agentRegno: formData.registrationNumber,
orgPANdob: formData.dateOfIncorp,
orgPANname: formData.orgPanName,
orgPAN: formData.orgPanNumber,
comAadharNo: formData.aadhaarNumber,
gistnNo: formData.gstnNo,
stateName: MasterData?.data?.states?.find((state:any)=>state.stateId==formData.state)?.stateName || '',
};
console.log('Form Data for registration',formData);
try {
const fileData = new FormData();
fileData.append('requestDto', JSON.stringify(dtoData));
fileData.append('aadharcardfile', documents.aadharCard as any);
fileData.append('conclassfile', documents.constractorClass as any);
fileData.append('gistnfile', documents.gstnCertificate as any);
fileData.append('msmefile', documents.registrationCertificate as any);
fileData.append('pancardfile', documents.panCard as any);
fileData.append('regfile', documents.registrationCertificate as any);
console.log('File Data for registration',fileData);
const res = await contractorStageTwoSubmit(fileData);
console.log('Register contractor Response : ', res);
if (res.status === 200) {
if (res) {
showToast('Form submitted successfully!');
console.log('response?.data?.contOtherId : ', res?.data?.contOtherId);
const orgType = selectedOrg;
const params = { otherId: res?.data?.contOtherId, orgType: orgType };
if ([4, 5, 6].includes(orgType)) {
navigation.replace('StageThree' as never, params as never);
} else if (orgType === 7) {
console.log('org type else if : ', orgType);
navigation.replace('PartnerShip' as never, { otherId: res?.data?.contOtherId } as never);
} else {
console.log('org type else : ', orgType);
navigation.replace('Dashboard' as never);
}
} else {
showToast('Form submission failed');
}
} else {
showToast(res.message || 'Form submission failed. Please try again.');
}
} catch (error: any) {
if (axios.isAxiosError(error)) {
console.log('11111', error.response?.status);
console.log('22222', error.response?.data);
console.log('3333333', error.response?.headers);
} else {
console.error('Unexpected error', error);
}
console.error('Form submission failed:', error);
showToast(error.message || 'Form submission failed. Please try again.');
} finally {
setLoading(false);
}
};
// 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) => {
cityDropdownMutate(stateId, {
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);
},
onError: (error) => {
setCities([]);
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);
// 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(() => {
if (formData?.state) {
fetchCity(Number(formData.state));
}
}, [formData?.state]);
useEffect(() => {
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
if (stage > 1) {
setStage(prev => prev - 1);
return true;
} else {
return false;
}
});
return () => backHandler.remove();
}, [stage]);
const validateStage = (stage: number) => {
console.log(stage, 'validateStage');
switch (stage) {
case 0:
if (!formData.organizationName?.trim()) {
showToast('Please fill all required details.');
return false;
}
return true;
case 1:
if (selectedOrg == 8) {
if (!formData.orgType || !formData.socialCategory) {
showToast('Please enter all valid details.');
return false;
}
} else {
if (
!formData.registrationNumber ||
!formData.registrationDetail ||
!formData.socialCategory ||
!formData.orgType
) {
showToast('Please enter all valid details.');
return false;
}
}
return true;
case 2:
if (
!formData.orgPanNumber ||
!formData.dateOfIncorp ||
!formData.orgPanName ||
(selectedOrg == 8 && !formData.gstnNo) ||
!formData.aadhaarNumber ||
!formData.aadhaarOtp
) {
Alert.alert('aadhar otp : ', formData?.aadhaarOtp);
showToast('Please fill all required KYC and Aadhaar details.');
return false;
}
if (formData.aadhaarNumber.length !== 12) {
showToast('Aadhaar number must be 12 digits.');
return false;
}
if (formData.aadhaarOtp.length !== 6) {
showToast('OTP must be 6 digits.');
return false;
}
return true;
case 3:
if (!formData.address || !formData.state || !formData.city || !formData.pinCode) {
showToast('Please fill all required address details.');
return false;
}
if (formData.pinCode.length !== 6) {
showToast('Please enter a valid 6-digit pin code.');
return false;
}
return true;
case 4:
if (selectedOrg == 8) {
if (!documents.panCard || !documents.aadharCard) {
showToast('Please upload all required documents.');
return false;
}
} else {
if (formData?.contractorClass !== 1) {
if (!documents.constractorClass) {
showToast('Please upload CONTRACTOR CLASS documents.');
return false;
}
}
if (formData?.registrationDetail !== 3) {
if (!documents.msmefile) {
showToast('Please upload MSME FILE.');
return false;
}
}
if (!documents.gstnCertificate || !documents.registrationCertificate) {
showToast('Please upload all required documents.');
return false;
}
}
return true;
default:
return true;
}
};
return (
<View style={{ flex: 1, backgroundColor: colors.white }}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={40}
>
<ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled" bounces={false}>
<View style={{ paddingBottom: 20, marginTop: insets.top, flex: 1 }}>
<TouchableOpacity
onPress={() => {
if (stage > 0) {
setStage(prev => prev - 1);
}
}}
style={styles.backBtn}
>
{/* Replaced AntDesign with Lucide ArrowLeft */}
<ArrowLeft size={25} color={colors.primary} />
</TouchableOpacity>
<StepProgressBar currentStep={stage} steps={steps} />
{stage === 0 && (
<StageZero
formData={formData}
handleChange={(key: string, value: string) =>
handleChange(key as keyof FormDataType, value)
}
focusedField={focusedField}
setFocusedField={setFocusedField}
styles={styles}
/>
)}
{stage === 1 && (
<StageOne
formData={formData}
handleChange={(key: string, value: string) =>
handleChange(key as keyof FormDataType, value)
}
focusedField={focusedField}
setFocusedField={setFocusedField}
contractorClassItems={contractorClassItems}
allOrganizations={allOrganizations}
allMsmeOrgs={allMsmeOrgs}
allSocialCategories={allSocialCategories}
allMSME={allMSME}
dropdownStyle={dropdownStyle}
setSelectedOrg={setSelectedOrg}
selectedOrg={selectedOrg}
/>
)}
{stage === 2 && (
<StageTwo
formData={formData}
handleChange={(key: string, value: string) =>
handleChange(key as keyof FormDataType, value)
}
focusedField={focusedField}
setFocusedField={setFocusedField}
panVerified={panVerified}
setPanVerified={setPanVerified}
verifyPanHandler={verifyPanHandler}
panVerifying={panVerifying}
aadhaarVerified={aadhaarVerified}
setAadhaarVerified={setAadhaarVerified}
sendAdharOTPHandler={sendAdharOTPHandler}
adharotpsend={adharotpsend}
setadharotpsend={setadharotpsend}
verifyAdharOtpHandler={verifyAdharOtpHandler}
aadhaarVerifying={aadhaarVerifying}
verifyGSTHandler={verifyGSTHandler}
gstverifying={gstverifying}
color={colors}
styles={styles}
formatDateInput={formatDateInput}
selectedOrg={selectedOrg}
/>
)}
{stage === 3 && (
<StageThree
formData={formData}
handleChange={(key: string, value: string) =>
handleChange(key as keyof FormDataType, value)
}
setFocusedField={setFocusedField}
focusedField={focusedField}
states={states}
cities={cities}
fetchCity={() => {
fetchCity(Number(formData.state))}}
dropdownStyle={dropdownStyle}
styles={styles}
/>
)}
{stage === 4 && (
<StageFour
formData={formData}
handleDocumentPick={handleDocumentPick}
documents={documents}
styles={styles}
selectedOrg={selectedOrg}
/>
)}
</View>
<PrimaryButton
title={stage === 4 ? (loading ? 'Loading...' : 'Submit') : 'Next'}
onPress={() => {
if (validateStage(stage)) {
switch (stage) {
case 0:
case 1:
setStage(prev => prev + 1);
break;
case 2:
if (!panVerified) {
showToast('Please verify PAN details.');
return;
}
if (!aadhaarVerified) {
showToast('Please verify Aadhar Number.');
return;
}
const { valid, message } = isPANLinkedToGSTIN(
formData.orgPanNumber,
formData.gstnNo,
);
if (!valid) {
showToast(message);
return;
}
setStage(prev => prev + 1);
break;
case 3:
setStage(prev => prev + 1);
break;
case 4:
handleSubmit();
break;
default:
console.log(stage, 'stage');
setStage(prev => prev + 1);
break;
}
} else {
showToast('Please fill all required fields.');
}
}}
/>
</ScrollView>
</KeyboardAvoidingView>
</View>
);
};
export default Registration;
const styles = StyleSheet.create({
scrollContainer: {
flexGrow: 1,
paddingHorizontal:14
},
label: {
fontSize: 15,
fontWeight: '600',
marginVertical: 10,
color: colors.black,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 10,
},
backBtn: {
padding: 5,
width: 50,
marginBottom: 5,
},
sectionTitle: {
fontSize: 24,
fontWeight: '700',
color: colors.black,
marginBottom: 20,
marginTop: 10,
textAlign: 'center',
},
uploadGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
},
uploadCard: {
width: '48%',
backgroundColor: '#fdfdfd',
borderRadius: 20,
padding: 20,
alignItems: 'center',
marginBottom: 20,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.08,
shadowRadius: 6,
elevation: 4,
borderWidth: 1,
borderColor: '#ddd',
},
iconCircle: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: colors.primary,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 10,
},
cardTitle: {
fontSize: 15,
fontWeight: '600',
color: colors.black,
marginBottom: 4,
textAlign: 'center',
},
cardHint: {
fontSize: 12,
color: '#888',
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,
},
});
+257
View File
@@ -0,0 +1,257 @@
import {
KeyboardAvoidingView,
SafeAreaView,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
Platform,
} from 'react-native';
import React, { useState } from 'react';
import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
import colors from '../../Constant/Color';
import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton';
import MyInput from '../../Component/MyInput';
import { showToast } from '../../Constant/ShowDailog';
import { STAGETHREE } from '../../Services/BBPS/ApiUrls';
import { useContractorStageThreeSubmit } from '../../Services/BBPS/Hooks';
type Company = {
director: string;
DINNumber: string;
panNumber: string;
aadharNumber: string;
mobileNumber: string;
};
type StageThreeRouteParams = {
orgType: string;
otherId: string | number
};
const StageThreeScreen = () => {
const route = useRoute<RouteProp<{ params: StageThreeRouteParams }, 'params'>>();
const navigation = useNavigation<any>();
const [company, setCompany] = useState<Company[]>([
{
director: '',
DINNumber: '',
panNumber: '',
aadharNumber: '',
mobileNumber: '',
},
]);
const [focusedField, setFocusedField] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const { mutateAsync: stageThreeSubmit } = useContractorStageThreeSubmit();
const handleChange = (index: number, field: keyof Company, value: string) => {
const updated = [...company];
updated[index][field] = value;
setCompany(updated);
};
const handleAddMore = () => {
setCompany(prev => [
...prev,
{
director: '',
DINNumber: '',
panNumber: '',
aadharNumber: '',
mobileNumber: '',
},
]);
};
console.log("first company route : ", route);
const handleSubmit = async () => {
setLoading(true);
try {
const payload = company.map(p => ({
contOtherId: route?.params?.otherId,
dinNo: p.DINNumber,
shAadharNo: p.aadharNumber,
shDirName: p.director,
shMobNo: p.mobileNumber,
shPANNo: p.panNumber,
}));
console.log('stage 3 payload : ', payload);
const response = await stageThreeSubmit(payload);
console.log('✅ company submitted successfully:', response);
if (response.statusCode === 200 || response.statusCode === 201) {
navigation.replace('Dashboard');
showToast('Submitted successfully!');
}
} catch (error: any) {
console.error('❌ Submission failed:', error.message);
showToast('Submission failed. Please try again.');
} finally {
setLoading(false);
}
};
const handleRemove = (index: number) => {
const updated = [...company];
updated.splice(index, 1);
setCompany(
updated.length > 0
? updated
: [
{
director: '',
DINNumber: '',
panNumber: '',
aadharNumber: '',
mobileNumber: '',
},
]
);
};
return (
<View style={{ flex: 1 }}>
<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>
<SafeAreaView style={{ flex: 1 }}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={40}
>
<ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled">
{company.map((partner, index) => (
<View style={{ marginBottom: 20 }} key={index}>
<Text style={styles.partnerTitle}>Company {index + 1}</Text>
<MyInput
icon="user"
placeholder="Name of Director *"
value={partner.director}
onChangeText={(text:any) => handleChange(index, 'director', text)}
onFocus={() => setFocusedField(`director-${index}`)}
onBlur={() => setFocusedField(null)}
focused={focusedField === `director-${index}`}
/>
<MyInput
icon="id-card"
placeholder="DIN Number in Firm *"
value={partner.DINNumber}
onChangeText={(text:any) => handleChange(index, 'DINNumber', text)}
onFocus={() => setFocusedField(`DINNumber-${index}`)}
onBlur={() => setFocusedField(null)}
focused={focusedField === `DINNumber-${index}`}
keyboardType="numeric"
maxlength={8}
/>
<MyInput
icon="id-card"
placeholder="PAN of Director *"
value={partner.panNumber}
onChangeText={(text:any) => handleChange(index, 'panNumber', text)}
onFocus={() => setFocusedField(`panNumber-${index}`)}
onBlur={() => setFocusedField(null)}
focused={focusedField === `panNumber-${index}`}
maxlength={12}
autoCapitalize="characters"
/>
<MyInput
icon="credit-card"
placeholder="Aadhar Number *"
value={partner.aadharNumber}
onChangeText={(text:any) => handleChange(index, 'aadharNumber', text)}
onFocus={() => setFocusedField(`aadharNumber-${index}`)}
onBlur={() => setFocusedField(null)}
focused={focusedField === `aadharNumber-${index}`}
keyboardType="numeric"
maxlength={12}
/>
<MyInput
icon="phone"
placeholder="Mobile Number *"
value={partner.mobileNumber}
onChangeText={(text:any) => handleChange(index, 'mobileNumber', text)}
onFocus={() => setFocusedField(`mobileNumber-${index}`)}
onBlur={() => setFocusedField(null)}
focused={focusedField === `mobileNumber-${index}`}
keyboardType="phone-pad"
maxlength={10}
/>
{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}>
<Text style={styles.addText}>+ Add Company</Text>
</TouchableOpacity>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
<PrimaryButton title={loading ? 'Loading...' : 'Submit'} onPress={handleSubmit} />
</View>
);
};
export default StageThreeScreen;
const styles = StyleSheet.create({
heading: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
marginTop: 10,
textAlign: 'center',
},
partnerTitle: {
fontWeight: '600',
fontSize: 16,
marginBottom: 10,
},
scrollContainer: {
// backgroundColor: color.background,
flexGrow: 1,
},
addButton: {
backgroundColor: colors.primary,
padding: 10,
marginTop: 10,
borderRadius: 10,
alignItems: 'center',
width: 140,
alignSelf: 'flex-end',
},
addText: {
color: 'white',
fontWeight: '600',
},
deleteButton: {
backgroundColor: '#FF5C5C',
padding: 10,
marginTop: 10,
borderRadius: 10,
alignItems: 'center',
width: 140,
alignSelf: 'flex-end',
},
deleteText: {
color: 'white',
fontWeight: '600',
},
});
@@ -0,0 +1,110 @@
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import {
IdCard,
FileText,
FileBadge,
ShieldCheck,
Building2,
ScrollText,
} from 'lucide-react-native'; // lucide icons
interface DocumentItem {
key: string;
label: string;
icon: 'pan' | 'aadhar' | 'gst' | 'contractor' | 'msme' | 'registration';
}
interface UploadDocumentsSectionProps {
handleDocumentPick: (key: string) => void;
documents: { [key: string]: { name: string } | null };
styles: any;
formData: any;
selectedOrg: number | string;
}
const StageFour: React.FC<UploadDocumentsSectionProps> = ({
handleDocumentPick,
documents,
styles,
formData,
selectedOrg,
}) => {
const documentItems: DocumentItem[] = [
{ key: 'panCard', label: 'PAN Card', icon: 'pan' },
{ key: 'aadharCard', label: 'Aadhar Card', icon: 'aadhar' },
{ key: 'gstnCertificate', label: 'GSTN Certificate', icon: 'gst' },
{ key: 'constractorClass', label: 'Contractor Class File', icon: 'contractor' },
{ key: 'msmefile', label: 'MSME Organization', icon: 'msme' },
{ key: 'registrationCertificate', label: 'Registration Certificate', icon: 'registration' },
];
const shouldRenderItem = (itemKey: string): boolean => {
if (formData.registrationDetail === 3 && itemKey === 'msmefile') return false;
if (formData.contractorClass == 1 && itemKey === 'constractorClass') return false;
if (
selectedOrg == 8 &&
['msmefile', 'constractorClass', 'gstnCertificate', 'registrationCertificate'].includes(
itemKey,
)
) {
return false;
}
return true; // pan + aadhar + rest (unless filtered above)
};
const renderIcon = (iconType: DocumentItem['icon']) => {
const commonProps = { size: 26, color: '#fff', strokeWidth: 2 };
switch (iconType) {
case 'pan':
return <IdCard {...commonProps} />; // PAN → ID card feel [web:19]
case 'aadhar':
return <IdCard {...commonProps} />; // Same icon, different label
case 'gst':
return <FileBadge {...commonProps} />; // File with badge [web:20]
case 'contractor':
return <ShieldCheck {...commonProps} />; // Safety / verification feel
case 'msme':
return <Building2 {...commonProps} />; // Organization / company
case 'registration':
return <ScrollText {...commonProps} />; // Certificate / document scroll
default:
return <FileText {...commonProps} />;
}
};
return (
<>
<Text style={styles.sectionTitle}>Upload Required Documents</Text>
<View style={styles.uploadGrid}>
{documentItems.map(item => {
if (!shouldRenderItem(item.key)) return null;
return (
<TouchableOpacity
key={item.key}
style={styles.uploadCard}
onPress={() => handleDocumentPick(item.key)}
>
<View style={styles.iconCircle}>{renderIcon(item.icon)}</View>
<Text style={styles.cardTitle}>{item.label}</Text>
<Text style={styles.cardHint}>
{documents[item.key]
? `${documents[item.key]?.name} ✅`
: 'Tap to upload PDF'}
</Text>
</TouchableOpacity>
);
})}
</View>
</>
);
};
export default StageFour;
@@ -0,0 +1,195 @@
import React, { useState } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import DatePicker from 'react-native-date-picker'
import MyInput from '../../../Component/MyInput';
import MyDropdown from '../../../Component/Dropdown/MyDropdown';
import colors from '../../../Constant/Color';
import { DropdownOption } from '../types/types';
interface Props {
formData: {
registrationNumber: string;
contractorClass: string | number;
orgType: string;
registrationDetail: string | number;
socialCategory: string;
expiryDate: string
};
handleChange: (key: string, value: string) => void;
focusedField: string | null;
setFocusedField: (field: string | null | any) => void;
contractorClassItems: DropdownOption[];
allOrganizations: DropdownOption[];
allMsmeOrgs: DropdownOption[];
allSocialCategories: DropdownOption[];
allMSME: DropdownOption[];
dropdownStyle: object;
setSelectedOrg: (val: number) => void;
selectedOrg: string | number
}
const StageOne: React.FC<Props> = ({
formData,
handleChange,
focusedField,
setFocusedField,
contractorClassItems,
allOrganizations,
allMsmeOrgs,
allSocialCategories,
dropdownStyle,
setSelectedOrg,
selectedOrg
}) => {
const [selectedAllMSME, setSelectedAllMSME] = useState()
const [open, setOpen] = useState(false)
const [date, setDate] = useState(new Date())
const handleDateConfirm = (selectedDate: Date) => {
setOpen(false);
setDate(selectedDate);
handleChange('expiryDate', selectedDate.toISOString().split('T')[0]); // Format as YYYY-MM-DD
};
return (
<View>
<Text style={styles.label}>Registration & Type Details</Text>
<MyDropdown
label="Type of Organization"
data={allOrganizations}
labelField="label"
valueField="value"
placeholder="Select Organization Type"
value={formData.orgType}
onChange={item => {
setSelectedOrg(item.value);
handleChange('orgType', item.value);
}}
style={dropdownStyle}
onFocus={() => setFocusedField('orgType')}
onBlur={() => setFocusedField(null)}
focusedField={focusedField === 'orgType'}
/>
{
selectedOrg !== 8 &&
<MyInput
icon="file"
placeholder="Registration Number *"
value={formData.registrationNumber}
onChangeText={text => handleChange('registrationNumber', text)}
onFocus={() => setFocusedField('registrationNumber')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'registrationNumber'}
autoCapitalize='characters' />
}
{selectedOrg &&
<MyDropdown
label="Contractor Class"
data={contractorClassItems}
labelField="label"
valueField="value"
placeholder="Select Class"
value={selectedOrg == 8 ? contractorClassItems[0]?.value : formData.contractorClass}
style={dropdownStyle}
disable={selectedOrg == 8 ? true : false}
onChange={item => {
handleChange('contractorClass', item.value);
}}
onFocus={() => setFocusedField('contractorClass')}
onBlur={() => setFocusedField(null)}
focusedField={focusedField === 'contractorClass'}
/>}
{selectedOrg !== 8 &&
<MyDropdown
label="Registration Detail (MSME/NSIC etc.)"
data={allMsmeOrgs}
labelField="label"
valueField="value"
placeholder="Select Registration Type"
value={formData.registrationDetail}
onChange={item => {
setSelectedAllMSME(item.value)
console.log('first', item)
handleChange('registrationDetail', item.value)
}}
style={dropdownStyle}
onFocus={() => setFocusedField('registrationDetail')}
onBlur={() => setFocusedField(null)}
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
label="Social Category"
data={allSocialCategories}
labelField="label"
valueField="value"
placeholder="Select Social Category"
value={formData.socialCategory}
onChange={item => handleChange('socialCategory', item.value)}
style={dropdownStyle}
onFocus={() => setFocusedField('socialCategory')}
onBlur={() => setFocusedField(null)}
focusedField={focusedField === 'socialCategory'}
/>
{
selectedAllMSME !== 3 && selectedOrg !== 8 &&
<>
<MyInput
icon="calendar-alt"
placeholder="Expiry Date *"
value={formData.expiryDate}
onChangeText={text => handleChange('expiryDate', text)}
onFocus={() => setFocusedField('expiryDate')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'expiryDate'}
rightIcon='wallet'
editable={true}
rightIconPress={() => setOpen(true)}
/>
<DatePicker
modal
open={open}
date={date}
onConfirm={handleDateConfirm}
onCancel={() => setOpen(false)}
mode="date" // Ensure you're using date mode
/>
</>
}
</View>
);
};
const styles = StyleSheet.create({
label: {
fontSize: 16,
fontWeight: '600',
color: colors.black,
marginBottom: 10,
},
});
export default StageOne;
@@ -0,0 +1,120 @@
import React from 'react';
import { Text } from 'react-native';
import { DropdownOption } from '../types/types';
import MyInput from '../../../Component/MyInput';
import MyDropdown from '../../../Component/Dropdown/MyDropdown';
interface AddressDetailsSectionProps {
formData: {
address: string;
agentContact2: string;
pinCode: string;
state: string;
city: string;
};
handleChange: (field: string, value: string) => void;
setFocusedField: (field: string | null | any) => void;
focusedField: string | null;
states: DropdownOption[];
cities: DropdownOption[];
fetchCity: (stateValue: number) => void;
dropdownStyle?: object;
styles?: any; // Optional styles prop for custom styling
}
const StageThree: React.FC<AddressDetailsSectionProps> = ({
formData,
handleChange,
setFocusedField,
focusedField,
states,
cities,
fetchCity,
styles
}) => {
const dropdownStyle = {
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 12,
paddingHorizontal: 14,
height: 50,
backgroundColor: '#fff',
marginBottom: 10,
};
return (
<>
<Text style={styles.label}>Address Details</Text>
<MyInput
icon="home"
placeholder="Enter Address *"
value={formData.address}
onChangeText={text => handleChange('address', text)}
onFocus={() => setFocusedField('address')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'address'}
/>
<MyInput
maxlength={10}
icon="phone"
placeholder="Contact Number *"
value={formData.agentContact2}
onChangeText={text => handleChange('agentContact2', text)}
onFocus={() => setFocusedField('agentContact2')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'agentContact2'}
keyboardType="numeric"
/>
<MyInput
icon="lock"
placeholder="Pin Code *"
keyboardType="numeric"
maxlength={6}
value={formData.pinCode}
onChangeText={text => handleChange('pinCode', text)}
onFocus={() => setFocusedField('pinCode')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'pinCode'}
/>
<MyDropdown
label="State"
data={states}
labelField="label"
valueField="value"
placeholder="Select State"
value={formData.state}
onChange={item => {
handleChange('state', item.value);
handleChange('city', '');
fetchCity(item.value);
}}
style={dropdownStyle}
onFocus={() => setFocusedField('state')}
onBlur={() => setFocusedField(null)}
focusedField={focusedField === 'state'}
/>
<MyDropdown
label="City"
data={cities}
labelField="label"
valueField="value"
placeholder="Select City"
value={formData.city}
onChange={item => handleChange('city', item.value)}
style={dropdownStyle}
onFocus={() => setFocusedField('city')}
onBlur={() => setFocusedField(null)}
focusedField={focusedField === 'city'}
/>
</>
);
};
export default StageThree;
@@ -0,0 +1,264 @@
import React from 'react';
import {
Text,
TouchableOpacity,
ActivityIndicator,
View,
} from 'react-native';
import MyInput from '../../../Component/MyInput';
// Adjust the path to your custom input
interface Props {
formData: {
orgPanNumber: string;
dateOfIncorp: string;
orgPanName: string;
aadhaarNumber: string;
aadhaarOtp: string;
gstnNo: string;
};
handleChange: (key: string, value: string) => void;
focusedField: string | null;
setFocusedField: (field: string | null | any) => void;
// PAN
panVerified: boolean;
setPanVerified: (value: boolean) => void;
verifyPanHandler: () => void;
panVerifying: boolean;
// Aadhaar
aadhaarVerified: boolean;
setAadhaarVerified: (value: boolean) => void;
sendAdharOTPHandler: (aadhaarNumber: string) => void;
adharotpsend: boolean;
setadharotpsend: (value: boolean) => void;
verifyAdharOtpHandler: () => void;
aadhaarVerifying: boolean;
// GST
verifyGSTHandler: () => void;
gstverifying: boolean;
color: any;
styles: any;
formatDateInput: (text: string) => string;
selectedOrg:string|number
}
const StageTwo: React.FC<Props> = ({
formData,
handleChange,
focusedField,
setFocusedField,
panVerified,
setPanVerified,
verifyPanHandler,
panVerifying,
aadhaarVerified,
setAadhaarVerified,
sendAdharOTPHandler,
adharotpsend,
setadharotpsend,
verifyAdharOtpHandler,
aadhaarVerifying,
verifyGSTHandler,
gstverifying,
color,
styles,
formatDateInput,
selectedOrg
}) => {
return (
<>
{/* ---------- PAN Details ---------- */}
<Text style={styles.label}>PAN Details</Text>
<MyInput
icon="file"
placeholder={selectedOrg == 8 ? "Pan Number *" : "Organization PAN Number *"}
value={formData.orgPanNumber}
onChangeText={(text) => {
setPanVerified(false);
handleChange('orgPanNumber', text);
}}
isVarify={panVerified}
onFocus={() => setFocusedField('pan')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'pan'}
editable={!panVerified}
autoCapitalize="characters"
maxlength={10}
/>
<MyInput
icon="calendar"
placeholder={selectedOrg == 8 ? "Date of Birth *" :"Date of Incorporation *"}
value={formData.dateOfIncorp}
onChangeText={(text) => {
const formatted = formatDateInput(text);
setPanVerified(false);
handleChange('dateOfIncorp', formatted);
}}
isVarify={panVerified}
onFocus={() => setFocusedField('dob')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'dob'}
editable={!panVerified}
keyboardType='numeric'
/>
<MyInput
icon="file-signature"
placeholder="PAN Name *"
value={formData.orgPanName}
onChangeText={(text) => {
setPanVerified(false);
handleChange('orgPanName', text);
}}
isVarify={panVerified}
onFocus={() => setFocusedField('panName')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'panName'}
editable={!panVerified}
autoCapitalize="characters"
/>
{formData.orgPanNumber &&
formData.dateOfIncorp &&
formData.orgPanName &&
!panVerified && (
<TouchableOpacity
style={{
backgroundColor: color.primary,
padding: 10,
marginTop: 10,
borderRadius: 10,
alignItems: 'center',
width: 120,
alignSelf: 'flex-end',
}}
disabled={panVerifying}
onPress={verifyPanHandler}
>
<Text style={{ color: 'white', fontWeight: '600' }}>
{panVerifying ? 'Verifying' : 'Verify PAN'}
</Text>
</TouchableOpacity>
)}
{/* ---------- Aadhaar Details ---------- */}
<Text style={styles.label}>Aadhaar Details</Text>
<MyInput
icon="address-card"
placeholder="Aadhaar Number *"
keyboardType="numeric"
maxlength={12}
value={formData.aadhaarNumber}
onChangeText={(text) => {
handleChange('aadhaarNumber', text);
setAadhaarVerified(false);
setadharotpsend(false);
handleChange('aadhaarOtp', '');
}}
isVarify={aadhaarVerified}
onFocus={() => setFocusedField('adhar')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'adhar'}
editable={!aadhaarVerified}
/>
{formData.aadhaarNumber.length === 12 && !adharotpsend && (
<TouchableOpacity
style={{
backgroundColor: color.primary,
padding: 10,
marginTop: 10,
borderRadius: 10,
alignItems: 'center',
width: 120,
alignSelf: 'flex-end',
}}
onPress={() => sendAdharOTPHandler(formData.aadhaarNumber)}
disabled={aadhaarVerifying}
>
{aadhaarVerifying ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={{ color: 'white', fontWeight: '600' }}>Send OTP</Text>
)}
</TouchableOpacity>
)}
{adharotpsend && !aadhaarVerified && (
<>
<MyInput
icon="lock"
placeholder="Enter OTP *"
keyboardType="numeric"
maxlength={6}
value={formData.aadhaarOtp}
onChangeText={(text) => handleChange('aadhaarOtp', text)}
onFocus={() => setFocusedField('adharOtp')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'adharOtp'}
/>
<TouchableOpacity
style={{
backgroundColor: color.primary,
padding: 10,
marginTop: 10,
borderRadius: 10,
alignItems: 'center',
width: 120,
alignSelf: 'flex-end',
}}
onPress={verifyAdharOtpHandler}
disabled={aadhaarVerifying}
>
<Text style={{ color: 'white', fontWeight: '600' }}>
{aadhaarVerifying ? 'Verifying...' : 'Verify'}
</Text>
</TouchableOpacity>
</>
)}
{/* ---------- GST Section ---------- */}
{selectedOrg !== 11 &&
<>
<MyInput
icon="file-invoice"
placeholder="GSTN No"
value={formData.gstnNo}
onChangeText={(text) => handleChange('gstnNo', text)}
onFocus={() => setFocusedField('gstNumber')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'gstNumber'}
autoCapitalize="characters"
/>
{/* {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;
@@ -0,0 +1,124 @@
import React, { useEffect } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import MyInput from '../../../Component/MyInput';
interface Props {
formData: {
agentContact2: string;
organizationName: string;
officialWebsite: string;
mobileNumber: string;
email: string;
firstName: string;
middleName: string;
lastName: string
};
handleChange: (key: string, value: string) => void;
focusedField: string | null;
setFocusedField: (field: string | null | any) => void;
styles: any;
}
const StageZero: React.FC<Props> = ({
formData,
handleChange,
focusedField,
setFocusedField,
styles,
}) => {
return (
<>
<Text style={styles.label}>Personal & Contact Details</Text>
{/* <View style={styles.row}> */}
<MyInput
icon="user"
placeholder="First Name *"
value={formData.firstName}
onChangeText={(text: string) => handleChange('firstName', text)}
onFocus={() => setFocusedField('firstname')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'firstname'}
editable={false}
/>
<View style={{ width: 10 }} />
{
formData.middleName ?
<MyInput
icon="user"
placeholder="Middle Name"
value={formData.middleName}
onChangeText={(text: string) => handleChange('middleName', text)}
onFocus={() => setFocusedField('middleName')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'middleName'}
editable={false}
/>
: null}
<MyInput
icon="user"
placeholder="Last Name *"
value={formData.lastName}
onChangeText={(text: string) => handleChange('lastName', text)}
onFocus={() => setFocusedField('lastName')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'lastName'}
editable={false}
/>
<MyInput
icon="envelope"
placeholder="Email *"
keyboardType="email-address"
value={formData.email}
onChangeText={(text: string) => handleChange('email', text)}
onFocus={() => setFocusedField('email')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'email'}
editable={false}
/>
<MyInput
icon="phone"
placeholder="Mobile Number *"
keyboardType="phone-pad"
value={formData.mobileNumber}
onChangeText={(text: string) => handleChange('mobileNumber', text)}
onFocus={() => setFocusedField('mobileNumber')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'mobileNumber'}
editable={false}
/>
<MyInput
icon="building"
placeholder="Organization Name *"
value={formData.organizationName}
onChangeText={(text: string) => handleChange('organizationName', text)}
onFocus={() => setFocusedField('organizationName')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'organizationName'}
autoCapitalize="none"
/>
<MyInput
icon="globe"
placeholder="Official Website (optional)"
value={formData.officialWebsite}
onChangeText={(text: string) => handleChange('officialWebsite', text)}
onFocus={() => setFocusedField('officialWebsite')}
onBlur={() => setFocusedField(null)}
focused={focusedField === 'officialWebsite'}
autoCapitalize="none"
/>
</>
);
};
export default StageZero;
+63
View File
@@ -0,0 +1,63 @@
export type FormDataType = {
firstName: string;
middleName: string;
lastName: string;
email: string;
mobileNumber: string;
organizationName: string;
officialWebsite: string;
registrationNumber: string;
contractorClass: string | number;
orgType: string;
registrationDetail: string | number;
socialCategory: string;
orgPanNumber: string;
dateOfIncorp: string;
orgPanName: string;
gstnNo: string;
aadhaarNumber: string;
aadhaarOtp: string;
address: string;
state: string;
city: string;
pinCode: string;
agentContact2: string;
expiryDate:string
};
export type UserData = {
firstName: string;
lastName: string;
middleName?: string;
email: string;
mobileNumber: string;
};
export type DocumentFile = {
uri: string;
type: string;
name: string;
};
export type DocumentsType = {
panCard: DocumentFile | null;
gstnCertificate: DocumentFile | null;
registrationCertificate: DocumentFile | null;
msmefile: DocumentFile | null;
constractorClass: DocumentFile | null;
aadharCard: DocumentFile | null;
};
export type OrgType = 4 | 5 | 6 | 7 | number;
export type ApiResponse = {
status: number|string;
message?: string;
data?: any;
contOtherId : number|any
};
export type DropdownOption = {
label: string;
value: string | number;
};
+511
View File
@@ -0,0 +1,511 @@
import { useNavigation } from '@react-navigation/native';
import React, { useEffect, useState } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
Alert,
Platform,
KeyboardAvoidingView,
SafeAreaView,
ScrollView,
ToastAndroid,
} from 'react-native';
import colors from '../../Constant/Color';
import { User, Phone, Mail, Lock, Eye, EyeOff, ArrowLeft, CheckSquare, Square } from 'lucide-react-native';
import { useGetDesignation, useRegisterContractor } from '../../Services/BBPS/Hooks';
import MyDropdown from '../../Component/Dropdown/MyDropdown';
import PrimaryButton from '../../Component/PrimaryButton/PrimaryButton';
import TermsAndConditionsModal from '../../Component/TermsAndConditionsModal/TermsAndConditionsModal';
import { Formik } from 'formik';
import * as Yup from 'yup';
import Animated, { FadeInUp } from 'react-native-reanimated';
import { InsideHeader } from '../../Component/Index';
export type ContractorFormData = {
firstName: string;
middleName: string;
lastName: string;
dialCode: string;
mobNo: string;
emailAdd: string;
contractorType: string;
designation: number;
password: string;
conPassword: string;
otherDesignation: null;
};
type DesignationOption = {
label: string;
value: string; // string id from API
};
const SignupSchema = Yup.object().shape({
bidderType: Yup.string().required('Please select bidder type.'),
firstName: Yup.string().required('First name is required.'),
middleName: Yup.string().optional(),
lastName: Yup.string().required('Last name is required.'),
designation: Yup.string().required('Please select a designation.'), // string
mobileNumber: Yup.string()
.matches(/^[6-9]\d{9}$/, 'Invalid mobile number.')
.required('Mobile number is required.'),
email: Yup.string().email('Invalid email address.').required('Email is required.'),
password: Yup.string()
.matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
'Password must be at least 8 characters and include uppercase, lowercase, number, and special character.',
)
.required('Password is required.'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords do not match.')
.required('Confirm password is required.'),
});
const Signup = () => {
const navigation = useNavigation<any>();
const [designation, setDesignation] = useState<DesignationOption[]>([]);
const { data, isLoading: isDesignationLoading } = useGetDesignation();
const [checkModal, setCheckModal] = useState(false);
const [isCheck, setIsCheck] = useState(false);
const { mutate: registerContractor, isPending: isRegistering } = useRegisterContractor();
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
useEffect(() => {
const list = (data as any)?.data;
if (!Array.isArray(list)) return;
const formattedDesignation: DesignationOption[] = list.map((item: any) => ({
label: item.designationName,
value: String(item.designationid), // e.g. "3"
}));
setDesignation(formattedDesignation);
}, [data]);
const dropdownStyle = {
borderWidth: 1,
borderColor: colors.border,
borderRadius: 12,
paddingHorizontal: 14,
height: 50,
backgroundColor: colors.white,
marginBottom: 10,
};
const renderInputWithIcon = (
icon: 'user' | 'phone' | 'mail',
placeholder: string,
value: string,
onChangeText: (text: string) => void,
onBlur: () => void,
error?: string,
touched?: boolean,
props: object = {},
) => {
const IconComponent = icon === 'user' ? User : icon === 'phone' ? Phone : Mail;
return (
<>
<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) => {
console.log('values', values);
if (!isCheck) {
Alert.alert('Check terms and condition');
return;
}
const formData: ContractorFormData = {
firstName: values.firstName.trim(),
middleName: values.middleName.trim(),
lastName: values.lastName.trim(),
dialCode: '+91',
mobNo: values.mobileNumber.trim(),
emailAdd: values.email.trim(),
contractorType: values.bidderType,
designation: values.designation ? Number(values.designation) : 0, // convert string -> number
password: values.password.trim(),
otherDesignation: null,
conPassword: values.confirmPassword.trim(),
};
registerContractor(formData, {
onSuccess: (res: any) => {
if (res?.status === '201') {
ToastAndroid.show(`${res.message}`, ToastAndroid.SHORT);
navigation.pop();
} else {
ToastAndroid.show(`Registration failed : ${res.message}`, ToastAndroid.SHORT);
}
},
onError: (error: any) => {
ToastAndroid.show(error.message, ToastAndroid.SHORT);
},
});
};
return (
<View style={styles.container}>
<SafeAreaView style={styles.container}>
<InsideHeader title="Sign Up" showArrow />
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={40}
>
<Formik
initialValues={{
bidderType: '',
firstName: '',
middleName: '',
lastName: '',
designation: '', // string, not 0
mobileNumber: '',
email: '',
password: '',
confirmPassword: '',
}}
validationSchema={SignupSchema}
onSubmit={handleSubmitForm}
>
{({
handleChange,
handleBlur,
handleSubmit,
values,
errors,
touched,
setFieldValue,
}) => (
<ScrollView
contentContainerStyle={styles.scrollContainer}
keyboardShouldPersistTaps="handled"
bounces={false}
>
<Animated.View entering={FadeInUp.duration(450).delay(80)}>
<Text style={styles.label}>Bidder Type</Text>
<View style={styles.toggleContainer}>
{['Indian', 'Foreign'].map(type => (
<TouchableOpacity
key={type}
style={[
styles.toggleButton,
values.bidderType === type && styles.selectedToggle,
]}
onPress={() => setFieldValue('bidderType', type)}
>
<Text
style={[
styles.toggleText,
values.bidderType === type && styles.selectedToggleText,
]}
>
{type}
</Text>
</TouchableOpacity>
))}
</View>
{touched.bidderType && errors.bidderType && (
<Text style={styles.errorText}>{errors.bidderType}</Text>
)}
</Animated.View>
<Animated.View entering={FadeInUp.duration(450).delay(120)}>
<View style={styles.row}>
<View style={styles.halfInput}>
{renderInputWithIcon(
'user',
'First Name *',
values.firstName,
handleChange('firstName'),
() => 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>
{renderInputWithIcon(
'user',
'Last Name *',
values.lastName,
handleChange('lastName'),
() => handleBlur('lastName'),
errors.lastName,
touched.lastName,
)}
</Animated.View>
<Animated.View entering={FadeInUp.duration(450).delay(160)}>
<Text style={styles.label}>Designation</Text>
<MyDropdown
label="Select Designation"
labelField="label"
valueField="value"
data={designation}
value={values.designation}
onChange={(item: any) => {
setFieldValue('designation', item.value);
}}
placeholder={isDesignationLoading ? 'Loading...' : 'Choose Designation'}
style={dropdownStyle}
/>
{touched.designation && errors.designation && (
<Text style={styles.errorText}>{errors.designation}</Text>
)}
</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(
'mail',
'Email *',
values.email,
handleChange('email'),
() => handleBlur('email'),
errors.email,
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>
</Animated.View>
<Animated.View entering={FadeInUp.duration(450).delay(320)} style={styles.buttonWrapper}>
<PrimaryButton
title={isRegistering ? 'Submitting...' : 'Submit'}
disabled={isRegistering}
onPress={() => handleSubmit()}
/>
</Animated.View>
{checkModal && (
<TermsAndConditionsModal
visible={checkModal}
onClose={() => setCheckModal(false)}
onChecked={() => setIsCheck(true)}
/>
)}
</ScrollView>
)}
</Formik>
</KeyboardAvoidingView>
</SafeAreaView>
</View>
);
};
export default Signup;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.white,
},
scrollContainer: {
flexGrow: 1,
paddingHorizontal: 16,
paddingTop: 12,
paddingBottom: 24,
justifyContent: 'flex-start',
},
label: {
fontSize: 15,
fontWeight: '600',
marginTop: 6,
marginBottom: 4,
color: colors.darkText,
},
toggleContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 4,
},
toggleButton: {
flex: 1,
paddingVertical: 10,
borderRadius: 10,
borderWidth: 1,
borderColor: colors.primary,
alignItems: 'center',
marginHorizontal: 4,
backgroundColor: colors.white,
},
selectedToggle: {
backgroundColor: colors.primary,
borderColor: colors.primary,
},
toggleText: {
fontSize: 14,
color: colors.grayText,
},
selectedToggleText: {
color: colors.white,
fontWeight: '600',
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 6,
},
halfInput: {
width: '48%',
},
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.white,
borderRadius: 12,
borderWidth: 1,
borderColor: colors.border,
paddingHorizontal: 10,
marginTop: 8,
height: 52,
},
icon: {
marginRight: 10,
},
input: {
flex: 1,
fontSize: 15,
color: colors.darkText,
paddingVertical: 0,
},
errorText: {
color: colors.error,
fontSize: 12,
marginTop: 3,
marginLeft: 4,
},
termsRow: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 14,
marginHorizontal: 2,
},
termsText: {
flex: 1,
fontSize: 13,
color: colors.darkText,
marginLeft: 8,
},
buttonWrapper: {
marginTop: 18,
},
});
+211 -27
View File
@@ -1,40 +1,224 @@
import { View, Text } from 'react-native'; import React, { useEffect, useRef, useState, useCallback } from 'react';
import React, { useEffect } from 'react'; import { Animated, StyleSheet, View } from 'react-native';
import FontsFamily from '../../Constant/FontsFamily'; import { useNavigation, useIsFocused } from '@react-navigation/native';
import { useNavigation } from '@react-navigation/native';
import NavigationString from '../../Constant/NavigationString';
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
const Splash = () => { import { showToast } from '../../Constant/ShowDailog';
const navigation: any = useNavigation(); import { useRagistrationState } from '../../Services/BBPS/Hooks';
const Token = useSelector((state: any) => state.token.token) import NavigationString from '../../Constant/NavigationString';
const handleSplashNavigation = async () => { // Navigation type
try { export type Navigation = {
const isOnboardingDone = await AsyncStorage.getItem('onBoarding'); reset(arg0: {
if (isOnboardingDone != 'true') { index: number;
navigation.replace(NavigationString.Onboarding); routes: { name: string; }[];
} else if (Token.token) { }): unknown;
navigation.replace(NavigationString.Home); navigate: (route: string, params?: { [key: string]: any }) => void;
} else { replace: (route: string, params?: { [key: string]: any }) => void;
navigation.replace(NavigationString.Login); };
}
} catch (error) { const Splash: React.FC = () => {
navigation.replace(NavigationString.Onboarding); const navigation = useNavigation() as Navigation;
} const isFocused = useIsFocused();
}; const fadeAnim = useRef(new Animated.Value(0)).current;
const reduxToken = useSelector((state: any) => state.token?.token);
const store = useStore();
const { mutateAsync: registrationStateMutate } = useRagistrationState();
const [isHydrated, setIsHydrated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
// Wait for Redux hydration (works with/without redux-persist)
useEffect(() => { useEffect(() => {
handleSplashNavigation() let unsubscribe: any;
const checkHydration = () => {
const token = (store.getState() as any).token?.token;
if (token !== undefined) { // Defined = hydrated (null/empty/string ok)
setIsHydrated(true);
unsubscribe?.();
}
};
unsubscribe = store.subscribe(checkHydration);
checkHydration(); // Check immediately
return () => unsubscribe?.();
}, []); }, []);
const getToken = useCallback(async (): Promise<string | null> => {
try {
// Redux first (post-hydration), then AsyncStorage fallback
let token = reduxToken;
if (token === undefined) {
token = await AsyncStorage.getItem('token');
}
return token || null;
} catch (error) {
console.error('Token fetch error:', error);
return null;
}
}, [reduxToken]);
const routeByOrgType = (params: { otherId: any; orgType: any }) => {
const orgType = String(params.orgType);
console.log("hitting here ")
if (orgType === '4' || orgType === '5' || orgType === '6') {
navigation.replace('StageThree', params);
return;
}
if (orgType === '7') {
navigation.replace(NavigationString.Partnership, params);
return;
}
navigation.replace('Dashboard');
};
const handleGetData = async () => {
try {
const res = await registrationStateMutate();
console.log('Registration status response:', res);
if (res?.statusCode === 404 || res?.statusCode === 401) {
navigation.replace('Registration');
return;
}
if (res?.statusCode === 200 && res?.data) {
const { registrationStatus, contOtherId, contOrganization } = res.data;
const params = { otherId: contOtherId, orgType: contOrganization };
console.log('Registration status:', registrationStatus);
switch (registrationStatus) {
case null:
case undefined:
navigation.replace(NavigationString.Registration);
break;
case 'Pending':
routeByOrgType(params);
break;
case 'Complete':
case 'Completed':
navigation.replace(NavigationString.Home);
break;
default:
navigation.replace(NavigationString.Registration);
break;
}
return;
}
console.error('API error:', res);
showToast(res?.message || 'Something went wrong');
navigation.replace('Registration');
} catch (error: any) {
console.error('Registration status error:', error);
showToast(error?.message || 'Something went wrong');
if (error?.response?.status === 404) {
navigation.replace('Registration');
} else {
await AsyncStorage.removeItem('token');
navigation.replace('Signin');
}
}
};
useEffect(() => {
if (!isFocused || !isHydrated) return;
const runSplash = async () => {
try {
setIsLoading(true);
Animated.timing(fadeAnim, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
const token = await getToken();
console.log("Token after hydration -", token, typeof token, !!token);
if (!token) {
console.log('No valid token, navigating to Login');
navigation.reset({
index: 0,
routes: [{ name: NavigationString.Login || 'Signin' }], // Fallback
});
return;
}
console.log('Valid token, fetching registration data');
await handleGetData();
} catch (error) {
console.error('Splash Error:', error);
navigation.reset({
index: 0,
routes: [{ name: NavigationString.Login || 'Signin' }],
});
} finally {
setIsLoading(false);
}
};
// Small delay ensures hydration + native animation complete
const timeoutId = setTimeout(runSplash, 200);
return () => clearTimeout(timeoutId);
}, [isFocused, isHydrated, getToken]);
if (!isHydrated) {
return (
<View style={styles.loadingContainer}>
<Animated.Image
source={require('../../Images/logo.png')}
style={[styles.logo, { opacity: fadeAnim }]}
resizeMode="contain"
/>
</View>
);
}
return ( return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <View style={styles.appView}>
<Text style={{ fontFamily: FontsFamily.poppinsBlack }}> <View style={styles.container}>
Splash <Animated.Image
</Text> source={require('../../Images/logo.png')}
style={[styles.logo, { opacity: fadeAnim }]}
resizeMode="contain"
/>
{isLoading && (
<View style={{ marginTop: 20 }}>
{/* Optional spinner */}
</View>
)}
</View>
</View> </View>
); );
}; };
export default Splash; export default Splash;
const styles = StyleSheet.create({
appView: {
flex: 1,
backgroundColor: '#fff',
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
logo: {
width: 160,
height: 160,
marginBottom: 20,
},
loadingContainer: {
flex: 1,
backgroundColor: '#fff',
justifyContent: 'center',
alignItems: 'center',
},
});
+14 -14
View File
@@ -132,9 +132,9 @@ const ProceedToBidDetail = () => {
<View style={styles.section}> <View style={styles.section}>
<Text style={[styles.sectionTitle, { color: colors.black }]}>{`Envelope Submission Status`}</Text> <Text style={[styles.sectionTitle, { color: colors.black }]}>{`Envelope Submission Status`}</Text>
<View style={styles.statusGrid}> <View style={styles.statusGrid}>
<StatusIndicator status={tenderData.envelopeASubmittStatus} label="Envelope A" /> <StatusIndicator status={tenderData?.envelopeASubmittStatus} label="Envelope A" />
<StatusIndicator status={tenderData.envelopeBSubmittStatus} label="Envelope B" /> <StatusIndicator status={tenderData?.envelopeBSubmittStatus} label="Envelope B" />
<StatusIndicator status={tenderData.envelopeCSubmittStatus} label="Envelope C" /> <StatusIndicator status={tenderData?.envelopeCSubmittStatus} label="Envelope C" />
</View> </View>
</View> </View>
@@ -152,11 +152,11 @@ const ProceedToBidDetail = () => {
<View style={styles.section}> <View style={styles.section}>
<Text style={[styles.sectionTitle, { color: colors.black }]}>Tender Details</Text> <Text style={[styles.sectionTitle, { color: colors.black }]}>Tender Details</Text>
<View style={styles.detailsCard}> <View style={styles.detailsCard}>
<InfoRow label="Envelope Type" value={tenderData.envelopeType} /> <InfoRow label="Envelope Type" value={tenderData?.envelopeType} />
<InfoRow label="Tender Stages" value={tenderData.tenderStages} /> <InfoRow label="Tender Stages" value={tenderData?.tenderStages} />
<InfoRow label="Bid Withdrawal" value={tenderData.bidWithDraw} isHighlighted={tenderData.bidWithdrawStatus} /> <InfoRow label="Bid Withdrawal" value={tenderData?.bidWithDraw} isHighlighted={tenderData?.bidWithdrawStatus} />
<InfoRow label="Re-bid" value={tenderData.reBid} /> <InfoRow label="Re-bid" value={tenderData?.reBid} />
<InfoRow label="DSC Status" value={tenderData.dscStatus ? "Required" : "Not Required"} /> <InfoRow label="DSC Status" value={tenderData?.dscStatus ? "Required" : "Not Required"} />
</View> </View>
</View> </View>
@@ -179,12 +179,12 @@ const ProceedToBidDetail = () => {
<View style={styles.section}> <View style={styles.section}>
<Text style={[styles.sectionTitle, { color: colors.black }]}>Available Features</Text> <Text style={[styles.sectionTitle, { color: colors.black }]}>Available Features</Text>
<View style={styles.chipsContainer}> <View style={styles.chipsContainer}>
<FeatureChip label="View Bid" enabled={tenderData.viewBid} /> <FeatureChip label="View Bid" enabled={tenderData?.viewBid} />
<FeatureChip label="Proceed to Bid" enabled={tenderData.proceedtoBid} /> <FeatureChip label="Proceed to Bid" enabled={tenderData?.proceedtoBid} />
<FeatureChip label="Show Envelope B" enabled={tenderData.showEB} /> <FeatureChip label="Show Envelope B" enabled={tenderData?.showEB} />
<FeatureChip label="Show Envelope C" enabled={tenderData.showEC} /> <FeatureChip label="Show Envelope C" enabled={tenderData?.showEC} />
<FeatureChip label="QCBS" enabled={tenderData.showQCBS} /> <FeatureChip label="QCBS" enabled={tenderData?.showQCBS} />
<FeatureChip label="LCS" enabled={tenderData.showLCS} /> <FeatureChip label="LCS" enabled={tenderData?.showLCS} />
</View> </View>
</View> </View>
@@ -15,6 +15,9 @@ import MyInput from '../../../../src/Component/MyInput';
import { pick, types } from '@react-native-documents/picker'; import { pick, types } from '@react-native-documents/picker';
import { InsideHeader } from '../../../Component/Index'; import { InsideHeader } from '../../../Component/Index';
import colors from '../../../Constant/Color'; import colors from '../../../Constant/Color';
import { SUBMIT_ENVELOPE_A, SUBMIT_ENVELOPE_B, SUBMIT_ENVELOPE_FILE_A, SUBMIT_ENVELOPE_FILE_B } from '../../../Services/BBPS/ApiUrls';
import { CheckSquare, Square } from 'lucide-react-native';
import { apiCall } from '../../../Axios/Axios';
type EnvelopeType = 'A' | 'B' | 'C'; type EnvelopeType = 'A' | 'B' | 'C';
type EnvelopeFormState = { type EnvelopeFormState = {
@@ -33,28 +36,13 @@ interface InfoRowProps {
value: string; value: string;
} }
interface TenderBiddingData {
data?: {
tenderNumber?: string;
nitNo?: string;
emdInFig?: string;
tenderRebid?: boolean;
showEA?: boolean;
showEB?: boolean;
showEC?: boolean;
tenderEnvelopeAList?: any[];
tenderEnvelopeBList?: any[];
tenderEnvelopeCList?: any[];
};
}
const TenderBidding = () => { const TenderBidding = () => {
const route = useRoute(); const route = useRoute<any>();
const navigation = useNavigation(); const navigation = useNavigation();
const [bidData] = useState<any>(route.params?.tenderData || null); const [bidData] = useState<any>(route.params?.tenderData || null);
const tenderData = route.params?.bidData || null; const tenderData: any = route.params?.bidData || null;
const [activeEnvelope, setActiveEnvelope] = useState<EnvelopeType | null>('A'); const [activeEnvelope, setActiveEnvelope] = useState<EnvelopeType>('A');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [envelopForm, setEnvelopForm] = useState<EnvelopeFormState>({ const [envelopForm, setEnvelopForm] = useState<EnvelopeFormState>({
A: {}, A: {},
@@ -64,7 +52,7 @@ const TenderBidding = () => {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [loadingUpload, setLoadingUpload] = useState(false) const [loadingUpload, setLoadingUpload] = useState(false)
const [check, setCheck] = useState(false) const [check, setCheck] = useState(false)
const InfoRow = ({ title, value }: InfoRowProps) => ( const InfoRow = ({ title, value }: InfoRowProps) => (
<View style={styles.infoRow}> <View style={styles.infoRow}>
@@ -145,7 +133,7 @@ const TenderBidding = () => {
activeEnvelope === envelopeType !== 'C' && activeEnvelope === envelopeType !== 'C' &&
!bidData?.[`envelope${envelopeType}SubmittStatus`]; !bidData?.[`envelope${envelopeType}SubmittStatus`];
// console.log("tender ana : ", tenderData)
return ( return (
<View key={sequenceId}> <View key={sequenceId}>
<View style={styles.envelopeItem}> <View style={styles.envelopeItem}>
@@ -255,8 +243,9 @@ const TenderBidding = () => {
}; };
const buildEnvelopePayload = () => { const buildEnvelopePayload = () => {
const list = const list =
bidData?.[`tenderEnvelope${activeEnvelope}List`] || []; tenderData?.[`tenderEnvelope${activeEnvelope}List`] || [];
const formValues = envelopForm?.[activeEnvelope] || {}; const formValues = envelopForm?.[activeEnvelope] || {};
// 🔹 NON-FILE FIELDS (labelType !== '3') // 🔹 NON-FILE FIELDS (labelType !== '3')
@@ -306,17 +295,17 @@ const TenderBidding = () => {
}; };
}); });
return payload = { const payload: any = {
deptId: bidData?.deptId, deptId: tenderData?.deptId,
tenderId: bidData?.tenderId, tenderId: tenderData?.tenderId,
[`tenderEnvelope${activeEnvelope}NameId`]: [`tenderEnvelope${activeEnvelope}NameId`]:
bidData?.[`tenderEnvelope${activeEnvelope}List`]?.[0]?.[ tenderData?.[`tenderEnvelope${activeEnvelope}List`]?.[0]?.[
`tenderEnvelope${activeEnvelope}NameId` `tenderEnvelope${activeEnvelope}NameId`
], ],
[`envelope${activeEnvelope}TemplateName`]: [`envelope${activeEnvelope}TemplateName`]:
bidData?.[`tenderEnvelope${activeEnvelope}List`]?.[0]?.[ tenderData?.[`tenderEnvelope${activeEnvelope}List`]?.[0]?.[
`envelope${activeEnvelope}TemplateName` `envelope${activeEnvelope}TemplateName`
], ],
@@ -324,12 +313,13 @@ const TenderBidding = () => {
? fileData ? fileData
: saveData, : saveData,
}; };
// console.log("payload : ", payload)
return payload;
}; };
const validateEnvelope = () => { const validateEnvelope = () => {
const list = bidData?.[`tenderEnvelope${activeEnvelope}List`] || []; const list = tenderData?.[`tenderEnvelope${activeEnvelope}List`] || [];
const formValues = envelopForm[activeEnvelope]; const formValues = envelopForm[activeEnvelope];
const missing = list.some((item: any) => { const missing = list.some((item: any) => {
@@ -363,12 +353,13 @@ const TenderBidding = () => {
setLoading(true) setLoading(true)
if (envelopForm?.[activeEnvelope]?.[3]) { if (envelopForm?.[activeEnvelope]?.[3]) {
handleSubmitEnvelopeFile() handleSubmitEnvelopeFile()
return
} }
try { try {
const res = await apiCallForPostWithToken(SUBMIT_ENVELOPE_A, payload) const res = await apiCall('post',SUBMIT_ENVELOPE_A, payload)
console.log("res A envelop : ", res) console.log("res A envelop : ", res)
ToastAndroid.show(res.message, ToastAndroid.SHORT) ToastAndroid.show(res?.message, ToastAndroid.SHORT)
navigation.goBack() navigation.goBack()
@@ -388,11 +379,14 @@ const TenderBidding = () => {
setLoading(true) setLoading(true)
const payload = buildEnvelopePayload(); const payload = buildEnvelopePayload();
console.log('Envelope B Payload:', payload); console.log('Envelope B Payload:', payload);
if (envelopForm?.[activeEnvelope]?.[3]) { if (envelopForm?.[activeEnvelope]?.[3]) {
handleSubmitEnvelopeFile() handleSubmitEnvelopeFile()
return
} }
try { try {
const res = await apiCallForPostWithToken(SUBMIT_ENVELOPE_B, payload) const res = await apiCall('post', SUBMIT_ENVELOPE_B, payload)
console.log("res B envelop : ", res) console.log("res B envelop : ", res)
ToastAndroid.show(res.message, ToastAndroid.SHORT) ToastAndroid.show(res.message, ToastAndroid.SHORT)
@@ -421,8 +415,7 @@ const TenderBidding = () => {
console.log("reuestDto for fil doc : ", formData) console.log("reuestDto for fil doc : ", formData)
// return const res = await apiCall('post', activeEnvelope === 'B' ? SUBMIT_ENVELOPE_FILE_B : SUBMIT_ENVELOPE_FILE_A, payload)
const res = await apiCallForPostWithToken(activeEnvelope === 'B' ? SUBMIT_ENVELOPE_B : SUBMIT_ENVELOPE_A, payload)
console.log(`res file ${activeEnvelope} Payload:`, res) console.log(`res file ${activeEnvelope} Payload:`, res)
ToastAndroid.show(res.message, ToastAndroid.SHORT) ToastAndroid.show(res.message, ToastAndroid.SHORT)
// navigation.goBack() // navigation.goBack()
@@ -433,18 +426,8 @@ const TenderBidding = () => {
} }
} }
if (isLoading) {
return (
<>
<View style={styles.loadingContainer}>
<Text>Loading...</Text>
</View>
</>
);
}
// console.log("route for date : ", bidData)
// console.log("bid envelop : ", tenderData)
return ( return (
<View style={styles.container}> <View style={styles.container}>
<InsideHeader title="Tender Bidding Start" showArrow /> <InsideHeader title="Tender Bidding Start" showArrow />
@@ -508,15 +491,18 @@ const TenderBidding = () => {
renderItem={({ item }) => renderEnvelopeItem({ item, envelopeType: activeEnvelope })} renderItem={({ item }) => renderEnvelopeItem({ item, envelopeType: activeEnvelope })}
scrollEnabled={false} scrollEnabled={false}
/> />
{/* <View style={[styles.infoRow, { flexDirection: 'row', gap: 10 }]}> <View style={[styles.infoRow, { flexDirection: 'row', gap: 10 }]}>
<TouchableOpacity onPress={() => setCheck(!check)}> <TouchableOpacity onPress={() => setCheck(!check)}>
{check ? <Fontisto name="checkbox-active" size={24} color={colors.primary} /> : {check ? (
<Fontisto name="checkbox-passive" size={24} color={colors.primary} /> <CheckSquare size={24} color={colors.primary} />
}</TouchableOpacity> ) : (
<Square size={24} color={colors.primary} />
)}</TouchableOpacity>
<Text>Check First Terms&Condition</Text> <Text>Check First Terms&Condition</Text>
</View> */} </View>
</View> </View>
)} )}
<View style={styles.footer}> <View style={styles.footer}>
{hasEnvelopes() ? ( {hasEnvelopes() ? (
<TouchableOpacity <TouchableOpacity
@@ -566,7 +552,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
}, },
card: { card: {
backgroundColor: '#fff', backgroundColor: colors.primaryHighlight,
borderRadius: 12, borderRadius: 12,
padding: 16, padding: 16,
marginBottom: 16, marginBottom: 16,
@@ -607,7 +593,7 @@ const styles = StyleSheet.create({
flexWrap: 'wrap', flexWrap: 'wrap',
}, },
envelopeButton: { envelopeButton: {
backgroundColor: '#EEF2FF', backgroundColor: colors.secondaryHighLight,
paddingVertical: 10, paddingVertical: 10,
paddingHorizontal: 14, paddingHorizontal: 14,
borderRadius: 8, borderRadius: 8,
@@ -615,7 +601,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
}, },
envelopeButtonText: { envelopeButtonText: {
color: '#3F51B5', color: colors.primary,
fontWeight: '600', fontWeight: '600',
fontSize: 14, fontSize: 14,
}, },
@@ -625,7 +611,7 @@ const styles = StyleSheet.create({
borderRadius: 10, borderRadius: 10,
padding: 12, padding: 12,
marginBottom: 12, marginBottom: 12,
backgroundColor: '#f9f9f9', backgroundColor: colors.primaryHighlight,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between' justifyContent: 'space-between'
}, },
@@ -635,7 +621,7 @@ const styles = StyleSheet.create({
marginVertical: 20, marginVertical: 20,
}, },
primaryButton: { primaryButton: {
backgroundColor: '#3F51B5', backgroundColor: colors.primary,
paddingVertical: 14, paddingVertical: 14,
paddingHorizontal: 28, paddingHorizontal: 28,
borderRadius: 10, borderRadius: 10,
@@ -657,7 +643,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
}, },
secondaryButtonText: { secondaryButtonText: {
color: '#3F51B5', color: colors.primary,
fontWeight: '600', fontWeight: '600',
fontSize: 15, fontSize: 15,
}, },
+6
View File
@@ -7,6 +7,9 @@ import StartReverseBidding from "./Bidding/StartReverseBidding";
import Home from "./Home/Home"; import Home from "./Home/Home";
import Login from "./Login/Login"; import Login from "./Login/Login";
import Onboarding from "./Onboarding/Onboarding"; import Onboarding from "./Onboarding/Onboarding";
import Partnership from "./Ragistration/PartnerShip";
import Registration from "./Ragistration/Ragistration";
import Signup from "./SignUp/SignUp";
import Splash from "./Splash/Splash"; import Splash from "./Splash/Splash";
import LiveTenderDetail from "./Tender/LiveTenderDetail"; import LiveTenderDetail from "./Tender/LiveTenderDetail";
import ProceedToBidDetail from "./Tender/ProceedToBidDetail"; import ProceedToBidDetail from "./Tender/ProceedToBidDetail";
@@ -32,6 +35,9 @@ const Screens = {
ProceedToPurTender, ProceedToPurTender,
TenderBidding, TenderBidding,
ProceedToBidDetail, ProceedToBidDetail,
Signup,
Registration,
Partnership,
LiveTenderDetail LiveTenderDetail
} }
export default Screens export default Screens
+1
View File
@@ -71,6 +71,7 @@ const VIEW_AUCTION_DOCUMENTS = `${BASE_URL_Auction}api/auction/viewDocumentsForA
const AUCTION_DOCUMENT_DOWNLOAD = `${BASE_URL_Auction}api/auction/home/downloadDocumentForAuction` const AUCTION_DOCUMENT_DOWNLOAD = `${BASE_URL_Auction}api/auction/home/downloadDocumentForAuction`
const AUCTION_ITEM_DOCUMENT_DOWNLOAD = `${BASE_URL_Auction}api/auction/home/downloadDocument/auctionItemId` const AUCTION_ITEM_DOCUMENT_DOWNLOAD = `${BASE_URL_Auction}api/auction/home/downloadDocument/auctionItemId`
//tender //tender
const TENDER_LOGIN = `${BASE_URL}eproc/loginWithoutDecryption` const TENDER_LOGIN = `${BASE_URL}eproc/loginWithoutDecryption`
+134 -33
View File
@@ -1,13 +1,21 @@
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'; import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
import { fetchAuctionItem, fetchDashboard, fetchEmdCheck, fetchLiveAuction, fetchLiveTender, fetchPurchaseAuction, fetchPurchaseAuctionList, fetchPurchaseTender, getAuction, offlineAuctionPurchase, userLogin, offlineTenderPurchase, fetchTenderById, proceedToBid, viewAuctionDocumentDownload, viewAuctionDocuments, viewAuctionItemDocuments, fetchPurchaseAuctionPage } from './apis'; import { contractorStageThreeSubmit, contractorStageTwoSubmit, fetchAuctionItem, fetchCityDropdown, fetchDashboard, fetchEmdCheck, fetchLiveAuction, fetchLiveTender, fetchMasterData, fetchPurchaseAuction, fetchPurchaseAuctionList, fetchPurchaseTender, fetchTenderById, getAuction, getDesignation, getRagistrationState, offlineAuctionPurchase, offlineTenderPurchase, partnerShipSubmit, proceedToBid, registerContractor, sendAadhaarOtp, submitEnvelopeA, submitEnvelopeB, uploadEnvelopeFile, userAuctionLogin, userLogin, verifyAadhaarOtp, verifyGstNumber, verifyPanHandler, viewAuctionDocumentDownload, viewAuctionDocuments, viewAuctionItemDocuments, fetchPurchaseAuctionPage } from './apis';
import { apiCall } from '../../Axios/Axios';
// User // User
export const useLogin = () => { export const useLogin = () => {
console.log("hitting here")
return useMutation({ return useMutation({
mutationFn: (payload: { emailAdd: string, password: string }) => userLogin(payload), mutationFn: (payload: { emailAdd: string, password: string }) => userLogin(payload),
}); });
}; };
export const useAuctionLogin = () => {
console.log("hitting here")
return useMutation({
mutationFn: (payload: { emailAdd: string, password: string }) => userAuctionLogin(payload),
});
};
// Dashboard // Dashboard
export const useDashboard = () => { export const useDashboard = () => {
@@ -20,31 +28,30 @@ export const useDashboard = () => {
// Auction & Tender // Auction & Tender
export const useFetchPurchaseTender = () => { export const useFetchPurchaseTender = () => {
return useInfiniteQuery({ return useInfiniteQuery({
queryKey: ['fetchPurchaseTender'], queryKey: ['fetchPurchaseTender'],
queryFn: ({ pageParam = 0 }) => initialPageParam: 1,
fetchPurchaseTender({ queryFn: ({ pageParam }) =>
page: pageParam, fetchPurchaseTender({
size: 10, page: pageParam,
}), size: 10,
initialPageParam: 0, }),
getNextPageParam: (lastPage, allPages) => {
console.log('last page : ', lastPage)
console.log("all page : ", allPages)
const {totalCount } = lastPage;
const totalPages = Math.ceil(totalCount / 10); getNextPageParam: (lastPage) => {
const nextPage = allPages.length; const { page, size, totalCount } = lastPage;
return nextPage < totalPages ? nextPage : undefined;
},
select: (data) => ({ const totalPages = Math.ceil(totalCount / size);
...data,
result: data.pages.flatMap( return page < totalPages ? page + 1 : undefined;
(page: any) => page?.data ?? [] },
),
}), select: (data) => ({
}); ...data,
result: data.pages.flatMap(
(page: any) => page?.data ?? []
),
}),
});
}; };
@@ -141,19 +148,23 @@ export const useFetchTenderById = (tenderId: string) => {
return useQuery({ return useQuery({
queryKey: ['fetchTenderById', tenderId], queryKey: ['fetchTenderById', tenderId],
queryFn: () => fetchTenderById(tenderId) queryFn: () => fetchTenderById(tenderId)
}); });
} }
export const useProceedToBid = (tenderId?: string) => { export const useProceedToBid = (tenderId?: string) => {
return useQuery({ return useQuery({
queryKey: ['proceedToBid', tenderId], queryKey: ['proceedToBid', tenderId],
queryFn: () => proceedToBid(tenderId as string), queryFn: () => proceedToBid(tenderId as string),
enabled: false, // 👈 manual trigger enabled: false, // 👈 manual trigger
}); });
}; };
export const useAuctionItem = () => {
return useMutation({
mutationFn: (payload: any) => fetchAuctionItem(payload),
});
};
// EMD // EMD
@@ -163,12 +174,99 @@ export const useEmdCheck = () => {
}); });
}; };
export const useAuctionItem = () => { export const useGetDesignation = () => {
return useQuery({
queryKey: ['Designation'],
queryFn: () => getDesignation()
});
}
export const useRegisterContractor = () => {
return useMutation({ return useMutation({
mutationFn: (payload: { auctionNumber: string, status: string }) => fetchAuctionItem(payload), mutationFn: (payload: any) => registerContractor(payload),
}); });
}; };
export const useRagistrationState = () => {
return useMutation({
mutationFn: () => getRagistrationState(),
});
};
//////////////////////////////////////////////////////
export const usefetchMasterData = () => {
return useQuery({
queryKey: ['MasterData'],
queryFn: () => fetchMasterData()
});
}
export const useCityDropdown = () => {
return useMutation({
mutationFn: (stateId: number) => fetchCityDropdown(stateId),
});
};
export const useVerifyPan = () =>
useMutation({
mutationFn: verifyPanHandler,
});
export const useSendAadhaarOtp = () =>
useMutation({
mutationFn: sendAadhaarOtp,
});
export const useVerifyAadhaarOtp = () =>
useMutation({
mutationFn: verifyAadhaarOtp,
});
export const useVerifyGstNumber = () =>
useMutation({
mutationFn: verifyGstNumber,
});
export const useContractorStageTwoSubmit = () =>
useMutation({
mutationFn: contractorStageTwoSubmit,
});
export const useContractorStageThreeSubmit = () =>
useMutation({
mutationFn: contractorStageThreeSubmit,
});
export const usePartnerShipSubmit = () =>
useMutation({
mutationFn: partnerShipSubmit,
});
export const useSubmitEnvelopeA = () =>
useMutation({
mutationFn: submitEnvelopeA,
})
export const useSubmitEnvelopeB = () =>
useMutation({
mutationFn: submitEnvelopeB,
})
export const useUploadEnvelopeFile = () =>
useMutation({
mutationFn: uploadEnvelopeFile,
})
// Purchase Auction List // Purchase Auction List
export const usePurchaseAuctionList = () => { export const usePurchaseAuctionList = () => {
@@ -203,3 +301,6 @@ export const useAucitonDocDownload = () => {
mutationFn: (payload: { documentId: number, isAmendment: boolean }) => viewAuctionDocumentDownload(payload), mutationFn: (payload: { documentId: number, isAmendment: boolean }) => viewAuctionDocumentDownload(payload),
}); });
}; };
+204 -1
View File
@@ -1,8 +1,26 @@
import axios from "axios"; import axios from "axios";
import { AUCTION_DOCUMENT_DOWNLOAD, DASHBOARD_COUNT, EMD_CHECK, FORARD_AUCTION_ITEM, GET_AUCTION_DETAILS, GET_MY_AUCTION, GET_MY_TENDER, GET_TENDER_DETAILS, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION, PROCEED_TO_BID, PURCHASE_TENDER_OFFLINE, TENDER_LOGIN, PURCHASE_AUCTION_LIST, PURCHASE_OFFLINE_AUCTION, REVERSE_AUCTION_ITEM, VIEW_AUCTION_DOCUMENTS, VIEW_AUCTION_ITEM_DETAIL, PURCHASE_AUCTION_PAGE } from "./ApiUrls"; import { AUCTION_DOCUMENT_DOWNLOAD, DASHBOARD_COUNT, EMD_CHECK, FORARD_AUCTION_ITEM, GET_AUCTION_DETAILS, GET_MY_AUCTION, GET_MY_TENDER, GET_TENDER_DETAILS, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION, PROCEED_TO_BID, PURCHASE_TENDER_OFFLINE, TENDER_LOGIN, PURCHASE_AUCTION_LIST, PURCHASE_OFFLINE_AUCTION, REVERSE_AUCTION_ITEM, VIEW_AUCTION_DOCUMENTS, VIEW_AUCTION_ITEM_DETAIL, STAGETHREE, REJ_STAGE_TWO, GETCONTRACTORBYSTATE, GET_MASTER_DATA, ALL_DESIGNATION, GET_REGISTRATION_STATE, PARTNER_API_ENDPOINT, SAVE_CONTRACTOR_STAGE1, VERIFY_GST, VERIFY_ADHAR, SEND_ADHAROTP, SUBMIT_ENVELOPE_FILE_B, SUBMIT_ENVELOPE_FILE_A, SUBMIT_ENVELOPE_A, SUBMIT_ENVELOPE_B, VERIFY_ORGPAN, PURCHASE_AUCTION_PAGE } from "./ApiUrls";
import { apiCall } from "../../Axios/Axios"; import { apiCall } from "../../Axios/Axios";
// User // User
export type UploadArgs = {
activeEnvelope: 'A' | 'B'
file: {
uri: string
name: string
type: string
}
payload: any
}
type DropdownOption = { label: string; value: number | string };
type State = { stateId: number; stateName: string };
type ContractorClass = { contClassId: number; contClassName?: string };
type Org = { orgId: number; organizationType: string };
type MSME = { msmeId: number; comName: string };
type Social = { socialCategoryId: number; categoryName: string };
type Designation = { designationid: number; designationName: string };
type City = { id: number; name: string };
export const userLogin = async (payload: { emailAdd: string, password: string }): Promise<any> => { export const userLogin = async (payload: { emailAdd: string, password: string }): Promise<any> => {
try { try {
@@ -14,6 +32,15 @@ export const userLogin = async (payload: { emailAdd: string, password: string })
} }
}; };
export const userAuctionLogin = async (payload: { emailAdd: string, password: string }): Promise<any> => {
try {
const response = await axios.post(LOG_IN_URL, { ...payload, device: "mobileApp" });
return response.data;
} catch (error: any) {
throw error.response?.data || error.message;
}
};
export const fetchDashboard = async (): Promise<any> => { export const fetchDashboard = async (): Promise<any> => {
const data = await apiCall<any>('get', `${DASHBOARD_COUNT}`); const data = await apiCall<any>('get', `${DASHBOARD_COUNT}`);
return data return data
@@ -116,6 +143,19 @@ export const proceedToBid = async (tenderId: string): Promise<any> => {
return data; return data;
} }
export const verifyPanHandler = (
payload: {
dob: string;
name: string;
panNumber: string;
}
): Promise<any> => {
return apiCall<any>(
'post',
VERIFY_ORGPAN,
payload,
);
};
export const fetchPurchaseAuctionList = async (payload: any): Promise<any> => { export const fetchPurchaseAuctionList = async (payload: any): Promise<any> => {
@@ -138,4 +178,167 @@ export const viewAuctionDocuments = async (payload: { auctionId: number }) => {
export const viewAuctionDocumentDownload = async (payload: { documentId: number, isAmendment: boolean }) => { export const viewAuctionDocumentDownload = async (payload: { documentId: number, isAmendment: boolean }) => {
const selectedRes = await apiCall<any>('get', AUCTION_DOCUMENT_DOWNLOAD, {}, payload); const selectedRes = await apiCall<any>('get', AUCTION_DOCUMENT_DOWNLOAD, {}, payload);
return selectedRes.data; return selectedRes.data;
}
export const contractorStageThreeSubmit = (
payload: any
): Promise<any> => {
return apiCall<any>(
'post',
STAGETHREE,
payload,
);
};
export const contractorStageTwoSubmit = (
fileData: any
): Promise<any> => {
return apiCall<any>(
'post',
REJ_STAGE_TWO,
fileData,
undefined,
'multipart/form-data'
);
};
export const fetchCityDropdown = async (stateId: number): Promise<any> => {
const res = await apiCall<{ statusCode: number; data: City[] }>(
'get',
`${GETCONTRACTORBYSTATE}?stateId=${stateId}`,
);
console.log("--",res)
if (res.statusCode !== 200 || !res.data) {
throw new Error('Failed load cities');
}
return res.data.map(item => ({
label: item.name,
value: item.id,
}));
};
export const fetchMasterData = async (): Promise<any> => {
const data = await apiCall<any>(
'get',
`${GET_MASTER_DATA}`
);
return data;
};
export const getDesignation = async (): Promise<any> => {
const data = await axios.get(ALL_DESIGNATION).then((response) => response.data);
return data;
}
export const getRagistrationState = async (): Promise<any> => {
const data = await apiCall<any>(
'get',
`${GET_REGISTRATION_STATE}`
);
return data;
}
export const partnerShipSubmit = (
payload: any
): Promise<any> => {
return apiCall<any>(
'post',
PARTNER_API_ENDPOINT,
payload,
);
};
export const registerContractor = async (payload: any): Promise<any> => {
console.log("registerContractor payload : ", payload)
try {
const response = await axios.post(SAVE_CONTRACTOR_STAGE1, { ...payload, device: "mobileApp" });
return response.data;
} catch (error: any) {
throw error.response?.data || error.message;
}
};
export const sendAadhaarOtp = (
payload: any
): Promise<any> => {
return apiCall<any>('post', SEND_ADHAROTP, payload);
};
export type VerifyAadhaarOtpPayload = {
otp: string;
refNumber: string;
};
export const verifyAadhaarOtp = (
payload: {
otp: string;
refNumber: string;
}
): Promise<any> => {
return apiCall<any>(
'post',
VERIFY_ADHAR,
payload,
);
};
export type VerifyGstPayload = {
gstNumber: string;
};
export const verifyGstNumber = (
payload: {
gstNumber: string;
}
): Promise<any> => {
return apiCall<any>(
'post',
VERIFY_GST,
payload,
);
};
export const submitEnvelopeA = async (payload: any) => {
console.log('SUBMIT ENVELOPE A API CALLED', payload)
const { data } = await apiCall<any>('post', SUBMIT_ENVELOPE_A, payload)
console.log("data : ", data)
return data
}
export const submitEnvelopeB = async (payload: any) => {
console.log('SUBMIT ENVELOPE B API CALLED', payload)
const { data } = await apiCall<any>('post', SUBMIT_ENVELOPE_B, payload)
console.log("data : ", data)
return data
}
export const uploadEnvelopeFile = async ({
activeEnvelope,
file,
payload,
}: UploadArgs) => {
const formData = new FormData()
formData.append('files', {
uri: file.uri,
name: file.name,
type: file.type,
} as any)
formData.append('requestDto', JSON.stringify(payload))
const url =
activeEnvelope === 'B' ? SUBMIT_ENVELOPE_FILE_B : SUBMIT_ENVELOPE_FILE_A
const data = await apiCall('post', url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
return data
} }
View File