before merge
This commit is contained in:
@@ -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,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user