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