before merge

This commit is contained in:
Abin Toppo
2026-01-12 15:21:14 +05:30
parent ed11bb479e
commit 360097da70
28 changed files with 4821 additions and 177 deletions
+208 -71
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { View, Text, Dimensions, TextInput, TouchableOpacity, StyleSheet, StatusBar, 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 { Formik } from 'formik';
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 colors from '../../Constant/Color';
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 { setToken } from '../../Redux/Slices/Token';
import { showErrorAlert } from '../../Constant/ShowDailog';
import { useNavigation, CommonActions } from '@react-navigation/native';
import NavigationString from '../../Constant/NavigationString';
import ReactNativeHapticFeedback from "react-native-haptic-feedback";
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Loader } from '../../Component/Index';
const hapticOptions = {
enableVibrateFallback: true,
ignoreAndroidSystemSettings: false
ignoreAndroidSystemSettings: false,
};
const { height, width } = Dimensions.get('window');
@@ -27,51 +28,145 @@ const LoginSchema = Yup.object().shape({
});
const Login = () => {
const Navigation: any = useNavigation()
const Dispatch = useDispatch()
const Navigation: any = useNavigation();
const Dispatch = useDispatch();
const [passwordVisible, setPasswordVisible] = React.useState(false);
const [loading, setLoading] = useState(false)
const { mutate, isPending } = useLogin()
const [activeType, setActiveType] = useState<'tender' | 'auction'>('tender');
const [loginLoading, setLoginLoading] = useState(false);
const [auctionLoading, setAuctionLoading] = useState(false);
const { mutate: loginMutate } = useLogin();
const { mutate: auctionMutate } = useAuctionLogin();
const {data}=useGetDesignation();
const { mutate: registrationStateMutate } = useRagistrationState();
const HandleLogin = async (values: any) => {
setLoading(true)
mutate({ password: values.password, emailAdd: values.email }, {
onSuccess: (res) => {
if (res.statusCode == 200) {
console.log("login token : ", res)
Dispatch(setToken({ ...res.data, token: res.token }))
Navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{ name: NavigationString.Home },
],
})
);
const { data: MasterData}=usefetchMasterData()
console.log("master data ",MasterData)
const handleGetData = async () => {
registrationStateMutate(undefined, {
onSuccess: (res: any) => {
console.log('Registration status response try :', res);
if (res?.statusCode === 200) {
const { registrationStatus, contOtherId, contOrganization } = res.data;
const params = { otherId: contOtherId, orgType: contOrganization };
console.log('==========pop', registrationStatus);
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)
},
onError: (err) => {
showErrorAlert(err.message)
setLoading(false)
return;
}
})
}
if (res?.statusCode === 404) {
console.warn('Registration API returned 404');
Navigation.replace('Registration');
return;
}
Navigation.replace('Registration');
},
onError: (error: any) => {
console.error('Registration status error catch :', error);
if (error?.response?.status === 500) {
Alert.alert('Server error', 'Something went wrong. Please try again later.');
return;
}
if (error?.request) {
Alert.alert('Network Error', 'Please check your internet connection.');
return;
}
Alert.alert('Error', error?.message || 'Something went wrong');
Navigation.replace('Registration');
},
});
};
const 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 (
<View style={styles.container}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={{ flex: 1 }}>
<ScrollView contentContainerStyle={{ flexGrow: 1 }} bounces={false}>
{/* Header */}
<View style={styles.header}>
<LinearGradient
colors={[colors.primary, colors.primaryLight || '#4c669f']}
style={styles.gradient}
>
<LinearGradient colors={[colors.primary, colors.primaryLight || '#4c669f']} style={styles.gradient}>
<Animated.Image
entering={FadeInDown.delay(200).duration(800)}
source={ImagePath.Icon.Logo}
@@ -81,24 +176,42 @@ const Login = () => {
</LinearGradient>
</View>
<Animated.View
entering={FadeInUp.springify().damping(15)}
style={styles.formContainer}
>
<Animated.View entering={FadeInUp.springify().damping(15)} style={styles.formContainer}>
<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.subtitle}>Enter your details to access your account</Text>
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={LoginSchema}
onSubmit={(values) => {
HandleLogin(values)
onSubmit={values => {
HandleLogin(values);
}}
>
{({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => (
<View style={{}}>
<View>
<Text style={styles.label}>Email Address</Text>
<View style={[styles.inputWrapper, touched.email && errors.email && styles.inputError]}>
<Mail size={20} color={touched.email && !errors.email ? colors.primary : colors.grayText} />
@@ -140,11 +253,11 @@ const Login = () => {
<TouchableOpacity
activeOpacity={0.8}
onPress={() => {
ReactNativeHapticFeedback.trigger("impactLight", hapticOptions);
ReactNativeHapticFeedback.trigger('impactLight', hapticOptions);
handleSubmit();
}}
style={styles.buttonShadow}
disabled={isLoading}
>
<LinearGradient
colors={[colors.primary, colors.primaryLight || '#4c669f']}
@@ -152,13 +265,25 @@ const Login = () => {
end={{ x: 1, y: 0 }}
style={styles.button}
>
{
loading ? <Loader color={colors.white} size='small'/> : <>
{isLoading ? (
<Loader color={colors.white} size="small" />
) : (
<>
<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>
</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>
)}
</Formik>
@@ -195,12 +320,10 @@ const styles = StyleSheet.create({
borderTopRightRadius: 35,
paddingHorizontal: 28,
paddingTop: 10,
// Shadow for iOS
shadowColor: '#000',
shadowOffset: { width: 0, height: -10 },
shadowOpacity: 0.1,
shadowRadius: 10,
// Elevation for Android
elevation: 20,
},
sheetHandle: {
@@ -209,8 +332,37 @@ const styles = StyleSheet.create({
backgroundColor: '#E0E0E0',
borderRadius: 10,
alignSelf: 'center',
marginBottom: 16,
},
toggleWrapper: {
alignItems: 'center',
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: {
fontSize: 28,
fontWeight: '800',
@@ -287,21 +439,6 @@ const styles = StyleSheet.create({
fontSize: 18,
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;
+866
View File
@@ -0,0 +1,866 @@
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,
};
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,
},
});
+255
View File
@@ -0,0 +1,255 @@
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';
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 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 apiCallForPostWithToken (STAGETHREE, 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 } from 'react';
import FontsFamily from '../../Constant/FontsFamily';
import { useNavigation } from '@react-navigation/native';
import NavigationString from '../../Constant/NavigationString';
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import { useNavigation, useIsFocused } from '@react-navigation/native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useSelector } from 'react-redux';
import { useStore } from 'react-redux'; // For hydration check
const Splash = () => {
const navigation: any = useNavigation();
const Token = useSelector((state: any) => state.token.token)
import { showToast } from '../../Constant/ShowDailog';
import { useRagistrationState } from '../../Services/BBPS/Hooks';
import NavigationString from '../../Constant/NavigationString';
const handleSplashNavigation = async () => {
try {
const isOnboardingDone = await AsyncStorage.getItem('onBoarding');
if (isOnboardingDone != 'true') {
navigation.replace(NavigationString.Onboarding);
} else if (Token.token) {
navigation.replace(NavigationString.Home);
} else {
navigation.replace(NavigationString.Login);
}
} catch (error) {
navigation.replace(NavigationString.Onboarding);
}
};
// Navigation type
export type Navigation = {
reset(arg0: {
index: number;
routes: { name: string; }[];
}): unknown;
navigate: (route: string, params?: { [key: string]: any }) => void;
replace: (route: string, params?: { [key: string]: any }) => void;
};
const Splash: React.FC = () => {
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(() => {
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);
if (orgType === '4' || orgType === '5' || orgType === '6') {
navigation.replace('StageThree', params);
return;
}
if (orgType === '7') {
navigation.replace('PartnerShip', params);
return;
}
navigation.replace('Dashboard');
};
const handleGetData = async () => {
try {
const res = await registrationStateMutate();
console.log('Registration status response:', res?.statusCode);
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 (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontFamily: FontsFamily.poppinsBlack }}>
Splash
</Text>
<View style={styles.appView}>
<View style={styles.container}>
<Animated.Image
source={require('../../Images/logo.png')}
style={[styles.logo, { opacity: fadeAnim }]}
resizeMode="contain"
/>
{isLoading && (
<View style={{ marginTop: 20 }}>
{/* Optional spinner */}
</View>
)}
</View>
</View>
);
};
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',
},
});
+7 -1
View File
@@ -7,6 +7,8 @@ import StartReverseBidding from "./Bidding/StartReverseBidding";
import Home from "./Home/Home";
import Login from "./Login/Login";
import Onboarding from "./Onboarding/Onboarding";
import Registration from "./Ragistration/Ragistration";
import Signup from "./SignUp/SignUp";
import Splash from "./Splash/Splash";
import ProceedToBidDetail from "./Tender/ProceedToBidDetail";
import ProceedToPurTender from "./Tender/ProceedToPurTender";
@@ -30,6 +32,10 @@ const Screens = {
ProceedToPurTender,
TenderBidding,
ProceedToBidDetail,
StartReverseBidding
StartReverseBidding,
Signup,
Registration
}
export default Screens