first commit

This commit is contained in:
rohit-kumar-fullstack
2026-01-02 17:56:16 +05:30
parent b94f98a764
commit 59fb7e0df4
149 changed files with 9362 additions and 12549 deletions
+48
View File
@@ -0,0 +1,48 @@
import axios from 'axios';
import { NavigationString, showErrorAlert, Variable } from '../Constant/AllImports';
import { setToken } from '../Redux/Slices/Token';
import { store } from '../Redux/Store';
export const apiCall = async <R, D = {}>(
method: 'get' | 'post' | 'patch' | 'delete' | 'put',
url: string,
data?: D,
params?: any,
contentType?: string
): Promise<R> => {
try {
const token: any = store.getState().token.token;
const response = await axios({
method,
url,
data,
params,
// withCredentials: true,
baseURL: Variable.Main_Base,
headers: {
Authentication: `Bearer ${token?.token}`,
'Content-Type': contentType || 'application/json',
},
});
return response.data;
} catch (error: any) {
const errorMessage = error?.response?.data?.error;
{ error?.response?.data?.error && showErrorAlert(error?.response?.data?.error || '') }
console.log(errorMessage, '---------------url error');
if (error?.response?.status === 401) {
store.dispatch(setToken({}));
return {
success: false,
message: 'JWT Error - Token expired or malformed',
} as R;
}
return {
success: false,
message: error?.response?.data?.message || 'No Response From Server',
} as R;
}
};
+170
View File
@@ -0,0 +1,170 @@
import React, { FC, useRef, useEffect } from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Animated,
Platform,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { BlurView } from '@react-native-community/blur';
import colors from '../../Constant/Color';
interface CardProps {
title: string;
leftLabel: string;
leftValue: React.ReactNode;
rightLabel: string;
rightValue: React.ReactNode;
buttonText: string;
onPress: () => void;
}
const Card: FC<CardProps> = ({
title,
leftLabel,
leftValue,
rightLabel,
rightValue,
buttonText,
onPress,
}) => {
const scale = useRef(new Animated.Value(0.95)).current;
useEffect(() => {
Animated.spring(scale, {
toValue: 1,
friction: 8,
tension: 90,
useNativeDriver: true,
}).start();
}, []);
return (
<Animated.View style={[styles.outer, { transform: [{ scale }] }]}>
{/* Glass Base */}
{Platform.OS === 'ios' && (
<BlurView
style={StyleSheet.absoluteFill}
blurType="light"
blurAmount={10}
reducedTransparencyFallbackColor="#ffffff"
/>
)}
{/* Card Body */}
<LinearGradient
colors={[
'rgba(255,255,255,0.85)',
'rgba(255,255,255,0.65)',
]}
style={styles.card}
>
{/* Title */}
<Text style={styles.title}>{title}</Text>
{/* Stats */}
<View style={styles.statsRow}>
<View style={styles.statBox}>
<Text style={styles.statValue}>{leftValue}</Text>
<Text style={styles.statLabel}>{leftLabel}</Text>
</View>
<View style={styles.verticalDivider} />
<View style={styles.statBox}>
<Text style={styles.statValue}>{rightValue}</Text>
<Text style={styles.statLabel}>{rightLabel}</Text>
</View>
</View>
{/* CTA */}
<TouchableOpacity activeOpacity={0.85} onPress={onPress}>
<LinearGradient
colors={[colors.primary, colors.primaryLight]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.ctaButton}
>
<Text style={styles.ctaText}>{buttonText}</Text>
</LinearGradient>
</TouchableOpacity>
</LinearGradient>
</Animated.View>
);
};
export default Card;
const styles = StyleSheet.create({
outer: {
marginBottom: 20,
borderRadius: 20,
overflow: 'hidden',
shadowColor: '#000',
shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.12,
shadowRadius: 18,
elevation: 8,
},
card: {
padding: 22,
borderRadius: 20,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.6)',
},
title: {
fontSize: 18,
fontWeight: '800',
color: '#0f172a',
marginBottom: 16,
},
statsRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 18,
},
statBox: {
flex: 1,
alignItems: 'center',
},
statValue: {
fontSize: 26,
fontWeight: '900',
color: '#020617',
},
statLabel: {
fontSize: 13,
fontWeight: '600',
color: colors.secondary,
marginTop: 4,
},
verticalDivider: {
width: 1,
height: 42,
backgroundColor: 'rgba(15,23,42,0.15)',
},
ctaButton: {
height: 46,
borderRadius: 14,
justifyContent: 'center',
alignItems: 'center',
},
ctaText: {
color: '#fff',
fontSize: 14,
fontWeight: '800',
letterSpacing: 0.6,
},
});
+75
View File
@@ -0,0 +1,75 @@
import React, { useEffect, useState, useCallback } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import colors from '../../Constant/Color';
import moment from 'moment';
interface CountdownTimerProps {
endDate: string; // Expected format: "DD-MM-YYYY HH:mm:ss"
}
const CountdownTimer: React.FC<CountdownTimerProps> = React.memo(({ endDate }) => {
const calculateTimeLeft = useCallback(() => {
if (!endDate) return null;
const end = moment(endDate, 'DD-MM-YYYY HH:mm:ss');
if (!end.isValid()) return null;
const now = moment();
const diff = end.diff(now);
if (diff <= 0) return null;
return {
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
minutes: Math.floor((diff / (1000 * 60)) % 60),
seconds: Math.floor((diff / 1000) % 60),
};
}, [endDate]);
const [timeLeft, setTimeLeft] = useState(calculateTimeLeft);
useEffect(() => {
const timerId = setInterval(() => setTimeLeft(calculateTimeLeft()), 1000);
return () => clearInterval(timerId);
}, [calculateTimeLeft]);
if (!timeLeft) {
return (
<View style={styles.container}>
<Text style={styles.expiredText}>Time's up!</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.timerText}>
{timeLeft.days}d {timeLeft.hours}h {timeLeft.minutes}m {timeLeft.seconds}s
</Text>
</View>
);
});
export default CountdownTimer;
const styles = StyleSheet.create({
container: {
backgroundColor: colors.white,
paddingVertical: 5,
paddingHorizontal: 10,
borderWidth: 1,
borderColor: colors.primary,
borderRadius: 25,
alignItems: 'center',
justifyContent: 'center',
},
timerText: {
fontSize: 14,
fontWeight: 'bold',
color: colors.primary,
},
expiredText: {
fontSize: 12,
color: 'red',
},
});
+45
View File
@@ -0,0 +1,45 @@
import React, { useEffect, useRef, useState } from 'react';
import { View, Text, Animated, StyleSheet } from 'react-native';
import colors from '../../Constant/Color';
const Counter = ({ toValue = 1000, duration = 2000 }) => {
const animatedValue = useRef(new Animated.Value(0)).current;
const [displayValue, setDisplayValue] = useState(0);
useEffect(() => {
Animated.timing(animatedValue, {
toValue,
duration,
useNativeDriver: false,
}).start();
const listener = animatedValue.addListener(({ value }) => {
setDisplayValue(Math.floor(value));
});
return () => {
animatedValue.removeListener(listener);
};
}, [toValue]);
return (
<View style={styles.container}>
<Text style={styles.counterText}>{displayValue}</Text>
</View>
);
};
export default Counter
const styles = StyleSheet.create({
container: {
backgroundColor: '#f2f2f2',
padding: 5,
borderRadius: 5,
},
counterText: {
fontSize: 20,
fontWeight: 'bold',
color: colors.primary,
},
});
+97
View File
@@ -0,0 +1,97 @@
import React, { useState } from 'react';
import {
View,
Text,
TouchableOpacity,
Image,
StyleSheet,
Dimensions,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Menu } from 'lucide-react-native';
import colors from '../../Constant/Color';
import ImagePath from '../../Constant/ImagePath';
import FontsFamily from '../../Constant/FontsFamily';
import SideDrawer from '../SideDrawer/SideDrawer';
const { width } = Dimensions.get('window');
interface HeaderProps {
title: string;
showDrawer?: boolean;
}
const Header: React.FC<HeaderProps> = ({ title, showDrawer = true }) => {
const insets = useSafeAreaInsets();
const [drawerVisible, setDrawerVisible] = useState(false);
return (
<>
<LinearGradient
colors={[colors.white, 'rgba(255,255,255,0.8)']}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
style={[styles.container, { paddingTop: insets.top + 10 }]}
>
<View style={styles.row}>
<View style={styles.left}>
{showDrawer && (
<TouchableOpacity
onPress={() => setDrawerVisible(true)}
style={styles.drawerIcon}
activeOpacity={0.7}
>
<Menu size={28} color={colors.black} strokeWidth={2.5} />
</TouchableOpacity>
)}
<Text style={styles.title}>{title}</Text>
</View>
<Image
source={ImagePath.Icon.Logo2}
style={styles.logo}
resizeMode="contain"
/>
</View>
</LinearGradient>
{/* Drawer Modal */}
<SideDrawer
visible={drawerVisible}
onClose={() => setDrawerVisible(false)}
/>
</>
);
};
export default Header;
const styles = StyleSheet.create({
container: {
width,
paddingBottom: 5,
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
},
left: {
flexDirection: 'row',
alignItems: 'center',
},
drawerIcon: {
marginRight: 12,
},
title: {
fontSize: 20,
color: colors.black,
fontFamily: FontsFamily.poppinsSemiBold,
},
logo: {
width: 55,
height: 55,
},
});
+110
View File
@@ -0,0 +1,110 @@
import React from 'react';
import {
View,
Text,
TouchableOpacity,
Image,
StyleSheet,
Dimensions,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { ArrowLeft } from 'lucide-react-native';
import colors from '../../Constant/Color';
import ImagePath from '../../Constant/ImagePath';
import { useNavigation } from '@react-navigation/native';
import FontsFamily from '../../Constant/FontsFamily';
import NavigationString from '../../Constant/NavigationString';
const { width } = Dimensions.get('window');
interface HeaderProps {
title: string;
showArrow?: boolean;
}
const InsideHeader: React.FC<HeaderProps> = ({ title, showArrow = false }) => {
const navigation:any = useNavigation();
const insets = useSafeAreaInsets();
return (
<View
style={[
styles.container,
{
paddingTop: insets.top - 10,
},
]}
>
<View style={styles.row}>
<View style={styles.leftRow}>
{showArrow && (
<TouchableOpacity
onPress={() => navigation.goBack()}
style={styles.backButton}
activeOpacity={0.7}
>
<ArrowLeft size={26} color={colors.black} strokeWidth={2.5} />
</TouchableOpacity>
)}
<Text style={styles.title}>{title}</Text>
</View>
<TouchableOpacity onPress={() => { navigation.navigate(NavigationString.Emd) }}>
<Image
source={ImagePath.Icon.Logo2}
style={styles.logo}
resizeMode="contain"
/>
</TouchableOpacity>
</View>
</View>
);
};
export default InsideHeader;
const styles = StyleSheet.create({
container: {
width,
backgroundColor: colors.white, // IMPORTANT for shadow
shadowColor: '#000', // iOS shadow
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.12,
shadowRadius: 10,
elevation: 6,
zIndex: 10,
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 10,
},
leftRow: {
flexDirection: 'row',
alignItems: 'center',
},
backButton: {
width: 40,
height: 40,
borderRadius: 20,
alignItems: 'center',
justifyContent: 'center',
marginRight: 8,
},
title: {
fontSize: 20,
color: colors.black,
fontFamily: FontsFamily.poppinsSemiBold,
},
logo: {
height: 60,
width: 60,
},
});
+6
View File
@@ -0,0 +1,6 @@
import Header from "./Header/Header";
import InsideHeader from "./Header/InsideHeader";
import Loader from "./Loader/Loader";
import Skelton from "./Skelton/Skelton";
export { Header , Skelton , Loader , InsideHeader}
@@ -0,0 +1,188 @@
import React from "react";
import { View, Text, StyleSheet, TouchableOpacity, Dimensions } from "react-native";
import LottieView from "lottie-react-native";
import LinearGradient from "react-native-linear-gradient";
const { width } = Dimensions.get("window");
const InternetConnectivity = () => {
return (
<View style={styles.container}>
<View style={styles.card}>
<View style={styles.lottieContainer}>
<LottieView
source={require("../../lottie/NoInternet.json")}
autoPlay
loop
style={styles.lottie}
/>
</View>
<View style={styles.content}>
<Text style={styles.title}>No Internet Connection</Text>
<Text style={styles.subtitle}>
It looks like you're offline. Check your connection and try again to continue.
</Text>
<View style={styles.hintContainer}>
<View style={styles.hintRow}>
<View style={styles.hintDot} />
<Text style={styles.hintText}>Check your Wi-Fi or mobile data</Text>
</View>
<View style={styles.hintRow}>
<View style={styles.hintDot} />
<Text style={styles.hintText}>Try turning airplane mode on/off</Text>
</View>
</View>
</View>
<View style={styles.buttonContainer}>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {}}
style={styles.touchable}
>
<LinearGradient
colors={["#63adde", "#3089bd50"]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={styles.retryBtn}
>
<Text style={styles.retryText}>Try Again</Text>
</LinearGradient>
</TouchableOpacity>
</View>
</View>
</View>
);
};
export default InternetConnectivity;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#F8FAFF",
alignItems: "center",
justifyContent: "center",
padding: 20,
},
card: {
width: "100%",
maxWidth: 400,
backgroundColor: "#FFF",
borderRadius: 24,
paddingVertical: 32,
paddingHorizontal: 24,
alignItems: "center",
shadowColor: "#667EEA",
shadowOpacity: 0.1,
shadowOffset: { width: 0, height: 10 },
shadowRadius: 20,
elevation: 8,
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.8)",
},
lottieContainer: {
width: 180,
height: 180,
marginBottom: 8,
},
lottie: {
width: "100%",
height: "100%",
},
content: {
alignItems: "center",
width: "100%",
},
title: {
fontSize: 26,
fontWeight: "800",
color: "#1A1A1A",
marginTop: 8,
letterSpacing: -0.5,
textAlign: "center",
},
subtitle: {
fontSize: 16,
color: "#666",
textAlign: "center",
marginTop: 12,
marginBottom: 28,
lineHeight: 24,
paddingHorizontal: 10,
fontWeight: "400",
},
hintContainer: {
width: "100%",
backgroundColor: "#F8F9FF",
borderRadius: 16,
padding: 20,
marginBottom: 32,
borderWidth: 1,
borderColor: "#EDF2FE",
},
hintRow: {
flexDirection: "row",
alignItems: "center",
marginBottom: 12,
},
hintDot: {
width: 6,
height: 6,
borderRadius: 3,
backgroundColor: "#667EEA",
marginRight: 12,
},
hintText: {
fontSize: 14,
color: "#555",
fontWeight: "400",
flex: 1,
},
buttonContainer: {
width: "100%",
alignItems: "center",
},
touchable: {
width: "100%",
shadowColor: "#667EEA",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 5,
},
retryBtn: {
paddingVertical: 18,
paddingHorizontal: 30,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
width: "100%",
},
retryText: {
color: "#FFF",
fontSize: 17,
fontWeight: "700",
letterSpacing: 0.5,
},
secondaryButton: {
marginTop: 16,
paddingVertical: 14,
paddingHorizontal: 30,
borderRadius: 16,
borderWidth: 1.5,
borderColor: "#E2E8F0",
backgroundColor: "#FFF",
width: "100%",
alignItems: "center",
},
secondaryText: {
color: "#64748B",
fontSize: 15,
fontWeight: "600",
letterSpacing: 0.3,
},
});
@@ -0,0 +1,406 @@
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
ScrollView,
StatusBar,
} from 'react-native';
import LottieView from 'lottie-react-native';
import LinearGradient from 'react-native-linear-gradient';
import {
AlertCircle,
Clock,
Server,
Calendar,
RefreshCw,
MessageCircle,
Twitter,
Activity,
MessageSquare,
Bell,
WifiOff,
} from 'lucide-react-native';
import Colors from '../../Styles/Color';
import FontsFamily from '../../Constant/FontsFamily';
const {width} = Dimensions.get('window');
const ServerDown = () => {
return (
<>
<StatusBar
// translucent={true}
// backgroundColor="transparent"
barStyle="dark-content"
/>
<ScrollView
contentContainerStyle={styles.scrollContainer}
showsVerticalScrollIndicator={false}>
<View style={styles.container}>
<View style={styles.card}>
{/* Status Indicator */}
<View style={styles.statusIndicator}>
<View style={styles.statusPulse} />
<View style={styles.statusDot}>
<AlertCircle size={24} color="#FFF" />
</View>
</View>
{/* Animated Illustration */}
<View style={styles.animationContainer}>
<LottieView
source={require('../../lottie/MaintenanceServer.json')}
autoPlay
loop
style={styles.animation}
/>
</View>
{/* Main Content */}
<View style={styles.content}>
<Text style={styles.title}>Server Unavailable</Text>
<View style={styles.statusMessage}>
<Clock size={18} color="#FF6B6B" style={styles.statusIcon} />
<Text style={styles.statusText}>
Temporarily down for maintenance
</Text>
</View>
<Text style={styles.description}>
Our servers are currently undergoing maintenance. This usually
takes 15-30 minutes. We appreciate your patience while we work
to improve your experience.
</Text>
{/* Action Buttons */}
<View style={styles.buttonGroup}>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {}}
style={styles.touchable}>
<LinearGradient
colors={[Colors.appColourDark, Colors.appColourDarkDim]}
start={{x: 0, y: 0}}
end={{x: 1, y: 0}}
style={styles.primaryButton}>
<RefreshCw
size={20}
color="#FFF"
style={styles.buttonIcon}
/>
<Text style={styles.primaryButtonText}>Refresh Status</Text>
</LinearGradient>
</TouchableOpacity>
</View>
{/* Live Status */}
<View style={styles.liveStatus}>
<View style={styles.liveIndicator}>
<View style={styles.livePulse} />
<View style={styles.liveDot} />
</View>
<Activity size={14} color="#10B981" style={styles.liveIcon} />
<Text style={styles.liveText}>Monitoring server status...</Text>
</View>
</View>
</View>
{/* Background Pattern */}
<View style={styles.backgroundPattern} />
</View>
</ScrollView>
</>
);
};
export default ServerDown;
const styles = StyleSheet.create({
scrollContainer: {
flexGrow: 1,
},
container: {
flex: 1,
backgroundColor: '#F8FAFF',
alignItems: 'center',
justifyContent: 'center',
padding: 20,
minHeight: Dimensions.get('window').height,
},
card: {
width: '100%',
maxWidth: 420,
backgroundColor: '#FFF',
borderRadius: 28,
paddingVertical: 36,
paddingHorizontal: 28,
alignItems: 'center',
shadowColor: Colors.appColourDark,
shadowOpacity: 0.08,
shadowOffset: {width: 0, height: 12},
shadowRadius: 24,
elevation: 10,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.9)',
zIndex: 2,
},
statusIndicator: {
position: 'absolute',
top: -20,
width: 60,
height: 60,
alignItems: 'center',
justifyContent: 'center',
},
statusPulse: {
position: 'absolute',
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: Colors.appColourDark,
opacity: 0.2,
},
statusDot: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: Colors.appColourDarkDim,
alignItems: 'center',
justifyContent: 'center',
shadowColor: Colors.appColourDark,
shadowOffset: {width: 0, height: 4},
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 6,
},
animationContainer: {
width: 200,
height: 200,
marginBottom: 8,
},
animation: {
width: '100%',
height: '100%',
marginLeft: -20,
},
content: {
alignItems: 'center',
width: '100%',
},
title: {
fontSize: 28,
fontWeight: '800',
color: '#1A1A1A',
marginTop: 8,
letterSpacing: -0.5,
textAlign: 'center',
marginBottom: 12,
},
statusMessage: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FFF5F5',
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 12,
marginBottom: 24,
borderWidth: 1,
borderColor: '#FED7D7',
},
statusIcon: {
marginRight: 8,
},
statusText: {
fontSize: 14,
color: '#C53030',
fontWeight: '600',
},
description: {
fontSize: 12,
color: '#666',
textAlign: 'center',
lineHeight: 24,
marginBottom: 28,
paddingHorizontal: 8,
fontFamily: FontsFamily.poppinsMedium,
},
progressContainer: {
width: '100%',
marginBottom: 32,
},
progressBar: {
width: '100%',
height: 8,
backgroundColor: '#E2E8F0',
borderRadius: 4,
overflow: 'hidden',
marginBottom: 12,
},
progressFill: {
width: '60%',
height: '100%',
backgroundColor: '#8B5CF6',
borderRadius: 4,
},
progressText: {
fontSize: 13,
color: '#64748B',
textAlign: 'center',
fontWeight: '500',
},
infoContainer: {
width: '100%',
backgroundColor: '#F8FAFF',
borderRadius: 16,
padding: 20,
marginBottom: 32,
borderWidth: 1,
borderColor: '#EDF2FE',
},
infoRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 16,
paddingBottom: 16,
borderBottomWidth: 1,
borderBottomColor: '#EDF2F7',
},
infoLabel: {
fontSize: 14,
color: '#64748B',
fontWeight: '500',
marginLeft: 12,
marginRight: 8,
width: 90,
},
infoValue: {
fontSize: 14,
color: '#1A1A1A',
fontWeight: '600',
flex: 1,
},
statusBadge: {
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 20,
backgroundColor: '#F3E8FF',
},
statusMaintenance: {
backgroundColor: '#F3E8FF',
borderWidth: 1,
borderColor: '#D6BCFA',
},
statusBadgeText: {
fontSize: 12,
color: '#8B5CF6',
fontWeight: '700',
textTransform: 'uppercase',
letterSpacing: 0.5,
},
buttonGroup: {
width: '100%',
alignItems: 'center',
marginBottom: 24,
},
touchable: {
width: '100%',
shadowColor: '#FF6B6B',
shadowOffset: {width: 0, height: 4},
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 6,
marginBottom: 16,
},
primaryButton: {
paddingVertical: 18,
paddingHorizontal: 30,
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
width: '100%',
flexDirection: 'row',
},
buttonIcon: {
marginRight: 10,
},
primaryButtonText: {
color: '#FFF',
fontSize: 17,
fontWeight: '700',
letterSpacing: 0.5,
},
secondaryButtonGroup: {
flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
gap: 12,
},
secondaryButton: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 14,
paddingHorizontal: 20,
borderRadius: 12,
backgroundColor: '#FFF',
borderWidth: 1.5,
borderColor: '#E2E8F0',
gap: 8,
},
secondaryButtonText: {
color: '#64748B',
fontSize: 14,
fontWeight: '600',
},
liveStatus: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
liveIndicator: {
position: 'relative',
width: 24,
height: 24,
alignItems: 'center',
justifyContent: 'center',
marginRight: 8,
},
livePulse: {
position: 'absolute',
width: 24,
height: 24,
borderRadius: 12,
backgroundColor: '#10B981',
opacity: 0.4,
},
liveDot: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: '#10B981',
},
liveIcon: {
marginRight: 6,
},
liveText: {
fontSize: 13,
color: '#64748B',
fontWeight: '500',
},
backgroundPattern: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#F8FAFF',
opacity: 0.3,
zIndex: 1,
},
});
+17
View File
@@ -0,0 +1,17 @@
import React from 'react';
import { View, ActivityIndicator } from 'react-native';
interface LoaderType {
size?: 'small' | 'large';
color?: string;
style?: any;
}
const Loader = ({ size = "large", color = '#000', style }: LoaderType) => {
return (
<View style={[{ justifyContent: 'center', alignItems: 'center' , flex:1}, style]}>
<ActivityIndicator size={size} color={color} />
</View>
);
};
export default Loader;
+141
View File
@@ -0,0 +1,141 @@
import { Filter, Search } from "lucide-react-native";
import React from "react";
import { View, Text, TextInput, StyleSheet, TouchableOpacity, Platform } from "react-native";
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming,
interpolateColor
} from "react-native-reanimated";
import colors from "../../Constant/Color";
export interface SearchType {
placeholder?: string;
onChange: (value: string) => void;
label?: string;
filter?: boolean;
filterPress?: () => void;
}
const AnimatedView = Animated.createAnimatedComponent(View);
export const SearchInput: React.FC<SearchType> = ({
placeholder,
onChange,
label,
filter = true,
filterPress
}) => {
const isFocused = useSharedValue(0);
// Animate border color and shadow based on focus
const inputContainerStyle = useAnimatedStyle(() => {
const borderColor = interpolateColor(
isFocused.value,
[0, 1],
[colors.grayText + '50', colors.grayText] // Assuming colors.primary exists
);
return {
borderColor: borderColor,
borderWidth: withTiming(isFocused.value ? 1.5 : 1),
transform: [{ scale: withTiming(isFocused.value ? 1.01 : 1) }],
shadowOpacity: withTiming(isFocused.value ? 0.1 : 0),
};
});
return (
<View style={styles.container}>
{label && <Text style={styles.label}>{label}</Text>}
<View style={styles.row}>
<AnimatedView style={[styles.inputWrapper, inputContainerStyle]}>
<Search
size={18}
color={isFocused.value ? colors.grayText : colors.grayText}
style={styles.searchIcon}
/>
<TextInput
style={styles.input}
placeholder={placeholder}
onChangeText={onChange}
placeholderTextColor={colors.grayText}
onFocus={() => (isFocused.value = withTiming(1))}
onBlur={() => (isFocused.value = withTiming(0))}
selectionColor={colors.grayText}
/>
</AnimatedView>
{filter && (
<TouchableOpacity
activeOpacity={0.7}
style={styles.filterButton}
onPress={filterPress}
>
<Filter size={22} color={colors.black} />
</TouchableOpacity>
)}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginVertical: 12,
marginHorizontal: 16,
},
label: {
fontSize: 14,
fontWeight: '600',
marginBottom: 8,
color: colors.black,
letterSpacing: 0.3,
},
row: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
inputWrapper: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
height: 48,
backgroundColor: '#F9FAFB',
borderRadius: 12,
paddingHorizontal: 12,
borderWidth: 1,
// Shadow for iOS
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowRadius: 4,
// Elevation for Android
elevation: 2,
},
searchIcon: {
marginRight: 8,
},
input: {
flex: 1,
height: '100%',
fontSize: 15,
color: colors.black,
paddingVertical: Platform.OS === 'ios' ? 0 : 8,
},
filterButton: {
height: 48,
width: 48,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 12,
borderWidth: 1,
borderColor: colors.grayText + '30',
elevation: 2,
shadowColor: "#000",
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
}
});
+246
View File
@@ -0,0 +1,246 @@
import React, { useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Dimensions, Image } from 'react-native';
import Modal from 'react-native-modal';
import Animated, { useSharedValue, useAnimatedStyle, withTiming, withDelay } from 'react-native-reanimated';
import { X, Home, Gavel, FileText, User, LogOut, ChevronRight } from 'lucide-react-native';
import { CommonActions, useNavigation } from '@react-navigation/native';
import { useDispatch } from 'react-redux';
import { removeToken } from '../../Redux/Slices/Token';
import NavigationString from '../../Constant/NavigationString';
const { width } = Dimensions.get('window');
const DRAWER_WIDTH = width * 0.78;
interface Props { visible: boolean; onClose: () => void; }
const SideDrawer: React.FC<Props> = ({ visible, onClose }) => {
const navigation: any = useNavigation();
const dispatch = useDispatch();
const user = { name: 'Rohit Kumar', role: 'Procurement Manager', avatar: 'https://i.pravatar.cc/150?img=12', };
const translateX = useSharedValue(-DRAWER_WIDTH);
useEffect(() => {
translateX.value = visible
? withTiming(0, { duration: 300 })
: withTiming(-DRAWER_WIDTH, { duration: 250 });
}, [visible]);
const drawerStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
const menuItems = [
{
label: 'Home',
icon: Home,
onPress: () => navigation.navigate(NavigationString.Home),
},
{
label: 'Auctions',
icon: Gavel,
onPress: () => navigation.navigate(NavigationString.Auction),
},
{
label: 'Tenders',
icon: FileText,
onPress: () => navigation.navigate(NavigationString.Tender),
},
{
label: 'Profile',
icon: User,
onPress: () => { },
},
];
return (
<Modal
isVisible={visible}
onBackdropPress={onClose}
onBackButtonPress={onClose}
backdropOpacity={0.45}
animationIn="fadeIn"
animationOut="fadeOut"
style={styles.modal}
useNativeDriver
>
<Animated.View style={[styles.drawer, drawerStyle]}>
{/* ================= Header / Profile ================= */}
<View style={styles.profileHeader}>
<TouchableOpacity style={styles.closeBtn} onPress={onClose}>
<X size={20} color="#111827" />
</TouchableOpacity>
<Image source={{ uri: user.avatar }} style={styles.avatar} />
<Text style={styles.userName}>{user.name}</Text>
<Text style={styles.userRole}>{user.role}</Text>
</View>
{/* ================= Menu ================= */}
<View style={styles.menuContainer}>
{menuItems.map((item, index) => {
const Icon = item.icon;
const itemStyle = useAnimatedStyle(() => ({
opacity: withDelay(
index * 80,
withTiming(1, { duration: 300 }),
),
transform: [
{
translateX: withDelay(
index * 80,
withTiming(0, { duration: 300 }),
),
},
],
}));
return (
<Animated.View key={item.label} style={itemStyle}>
<TouchableOpacity
style={styles.menuItem}
activeOpacity={0.7}
onPress={() => {
onClose();
item.onPress();
}}
>
<View style={styles.menuLeft}>
<Icon size={20} color="#1F2937" />
<Text style={styles.menuText}>{item.label}</Text>
</View>
<ChevronRight size={18} color="#9CA3AF" />
</TouchableOpacity>
</Animated.View>
);
})}
</View>
{/* ================= Logout ================= */}
<View style={styles.logoutContainer}>
<TouchableOpacity
style={styles.logoutBtn}
onPress={() => {
dispatch(removeToken());
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: NavigationString.Login }],
}),
);
}}
>
<LogOut size={20} color="#EF4444" />
<Text style={styles.logoutText}>Logout</Text>
</TouchableOpacity>
</View>
</Animated.View>
</Modal>
);
};
export default SideDrawer;
/* ================= Styles ================= */
const styles = StyleSheet.create({
modal: {
margin: 0,
justifyContent: 'flex-start',
},
drawer: {
width: DRAWER_WIDTH,
height: '100%',
backgroundColor: '#FFFFFF',
borderTopRightRadius: 24,
borderBottomRightRadius: 24,
elevation: 12,
},
/* -------- Profile -------- */
profileHeader: {
backgroundColor: '#F9FAFB',
paddingTop: 50,
paddingBottom: 24,
alignItems: 'center',
borderTopRightRadius: 24,
},
closeBtn: {
position: 'absolute',
top: 20,
right: 16,
},
avatar: {
width: 72,
height: 72,
borderRadius: 36,
marginBottom: 10,
},
userName: {
fontSize: 18,
fontWeight: '700',
color: '#111827',
},
userRole: {
fontSize: 13,
color: '#6B7280',
marginTop: 2,
},
/* -------- Menu -------- */
menuContainer: {
paddingHorizontal: 14,
paddingTop: 20,
},
menuItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 14,
paddingHorizontal: 12,
borderRadius: 14,
marginBottom: 6,
},
menuLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
menuText: {
fontSize: 16,
fontWeight: '600',
color: '#1F2937',
},
/* -------- Logout -------- */
logoutContainer: {
marginTop: 'auto',
paddingHorizontal: 14,
paddingBottom: 24,
borderTopWidth: 1,
borderTopColor: '#E5E7EB',
},
logoutBtn: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
paddingVertical: 16,
},
logoutText: {
fontSize: 16,
fontWeight: '600',
color: '#EF4444',
},
});
+34
View File
@@ -0,0 +1,34 @@
import { View, Dimensions } from 'react-native';
import React from 'react';
import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
const Skeleton = () => {
const { width } = Dimensions.get('screen');
const cardWidth = width - 30; // full width with some padding
return (
<View style={{ padding: 10 }}>
{[...Array(6)].map((_, index) => (
<SkeletonPlaceholder key={index}>
<SkeletonPlaceholder.Item
width={cardWidth}
marginBottom={20}
borderRadius={12}
overflow="hidden"
>
{/* Image */}
<SkeletonPlaceholder.Item width={cardWidth} height={150} borderRadius={12} />
{/* Title */}
<SkeletonPlaceholder.Item marginTop={10} width={cardWidth * 0.7} height={18} borderRadius={6} />
{/* Subtitle / Price */}
<SkeletonPlaceholder.Item marginTop={6} width={cardWidth * 0.5} height={14} borderRadius={6} />
</SkeletonPlaceholder.Item>
</SkeletonPlaceholder>
))}
</View>
);
};
export default Skeleton;
+19
View File
@@ -0,0 +1,19 @@
import { View, Dimensions } from 'react-native';
import React from 'react';
import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
const SkeltonList = () => {
const { width, height } = Dimensions.get('screen');
return (
<View style={{ width: width - 30, height }}>
{[...Array(10)].map((_, index) => (
<SkeletonPlaceholder key={index} borderRadius={5}>
<SkeletonPlaceholder.Item width={"100%"} height={50} marginBottom={12} />
</SkeletonPlaceholder>
))}
</View>
);
};
export default SkeltonList;
+54
View File
@@ -0,0 +1,54 @@
import React, { useEffect, useRef } from 'react';
import { Animated, Text, StyleSheet } from 'react-native';
import colors from '../../Constant/Color';
interface AuctionItemProps {
item: any;
index: number;
}
const AuctionItem: React.FC<AuctionItemProps> = ({ item, index }) => {
const scale = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.spring(scale, {
toValue: 1,
friction: 8,
useNativeDriver: true,
delay: index * 50,
}).start();
}, []);
return (
<Animated.View style={[styles.itemCard, { transform: [{ scale }] }]}>
<Text style={styles.itemTitle}>{item.title || `${item.auctionNumber}`}</Text>
<Text style={styles.itemDate}>End Date: {item.purchaseEnd || 'N/A'} </Text>
</Animated.View>
);
};
export default AuctionItem;
const styles = StyleSheet.create({
itemCard: {
backgroundColor: '#fff',
padding: 18,
borderRadius: 16,
marginBottom: 14,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.12,
shadowRadius: 8,
elevation: 4,
},
itemTitle: {
fontSize: 16,
fontWeight: '700',
marginBottom: 4,
color: '#020617',
},
itemDate: {
fontSize: 13,
color: '#64748b',
},
});
+10
View File
@@ -0,0 +1,10 @@
import colors from './Color';
import AllUrls from './AllUrls';
import FontsFamily from './FontsFamily';
import ImagePath from './ImagePath';
import NavigationString from './NavigationString';
import { showErrorAlert, showSuccessAlert, showToast } from './ShowDailog';
import Variable from './Variable';
export { AllUrls, colors, ImagePath, NavigationString, showErrorAlert, showSuccessAlert, showToast, Variable ,FontsFamily};
+69
View File
@@ -0,0 +1,69 @@
import Variable from "./Variable"
// Customer
const Login = `${Variable.Main_Base}/customer/login`
const OtpVerify = `${Variable.Main_Base}/customer/verify_otp`
const userUpdate = `${Variable.Main_Base}/customer`
const userLogout = `${Variable.Main_Base}/customer/logout`
const userInfo = `${Variable.Main_Base}/customer/info`
// Category
const categoryApi = `${Variable.Main_Base}/category`
// Brand
const brandsApi = `${Variable.Main_Base}/brand`
// Brand
const bannersApi = `${Variable.Main_Base}/banner/position`
// Vehicle Segment
const segmentApi = `${Variable.Main_Base}/vehicleSegmentType/without-page`
const vehicleApi = `${Variable.Main_Base}/vehicle`
// Products
const productApi = `${Variable.Main_Base}/product`
const brandProductApi = `${Variable.Main_Base}/product/assigned`
const ProductDetailsApi = `${Variable.Main_Base}/product`
const ProductWishlistApi = `${Variable.Main_Base}/wish-list`
const recentViewedProductApi = `${Variable.Main_Base}/view`
const updateAddToCartApi = `${Variable.Main_Base}/add-to-cart`
const brandCategoryApi = `${Variable.Main_Base}/category/brand`
// Rating
const ratingApi = `${Variable.Main_Base}/rating`
// Coupon
const couponApi = `${Variable.Main_Base}/coupon`
// Orders
const orderApi = `${Variable.Main_Base}/order`
// Address
const addressApi = `${Variable.Main_Base}/address`
const addressStatusApi = `${Variable.Main_Base}/address/status`
// Recharge
const rechrgeWalletApi = `${Variable.Main_Base}/wallets/create-order`
const rechrgeWalletVerifyApi = `${Variable.Main_Base}/wallets/verify`
const rechrgeWalletHistoryApi = `${Variable.Main_Base}/wallets`
// Order Payment
const orderPaymentApi = `${Variable.Main_Base}/order/init`
const orderConfirmApi = `${Variable.Main_Base}/order/callback`
// Charges
const chargesApi = `${Variable.Main_Base}/charges`
// FAQs
const FAQsApi = `${Variable.Main_Base}/faq`
// offers
const offersApi = `${Variable.Main_Base}/recharge`
// PrivacyPolicy
const privacyPolicyApi = `${Variable.Main_Base}/documents`
const AllUrls = {
Login, OtpVerify, userUpdate, userLogout, categoryApi, brandsApi, bannersApi, segmentApi, productApi, brandProductApi, ProductDetailsApi, ratingApi,
ProductWishlistApi, recentViewedProductApi, updateAddToCartApi, vehicleApi, brandCategoryApi, userInfo, couponApi, orderApi, addressApi, addressStatusApi,
rechrgeWalletApi, orderPaymentApi, orderConfirmApi, rechrgeWalletVerifyApi, chargesApi, FAQsApi, rechrgeWalletHistoryApi, offersApi,privacyPolicyApi
};
export default AllUrls;
+39
View File
@@ -0,0 +1,39 @@
const colors = {
// Primary (Orange-based)
primary: '#F97316', // Main orange (buttons, highlights)
primaryLight: '#FDBA74', // Soft orange (chips, badges)
primaryHighlight: '#f4f1efff', // Very light orange (backgrounds)
// Secondary (Warm contrast)
secondary: '#1B4B66', // Deep blue for balance
secondaryLight: '#639599', // Muted blue
// Status colors
success: '#22C55E', // Green
error: '#EF4444', // Red
warning: '#F59E0B', // Amber
info: '#3B82F6', // Blue
golden: '#FFD700',
// Neutral colors
white: '#FFFFFF',
black: '#000000',
darkText: '#2B2B2B',
grayText: '#6B7280',
lightText: '#9CA3AF',
border: '#E5E7EB',
lightGray: '#F9FAFB',
disabled: '#D1D5DB',
// Additional
placeholder: '#9CA3AF',
backdrop: 'rgba(0,0,0,0.5)',
shadow: 'rgba(0,0,0,0.12)',
// Aliases (optional)
Orange: '#F97316',
OrangeLight: '#FDBA74',
OrangeSoft: '#FFF3E8',
};
export default colors;
+22
View File
@@ -0,0 +1,22 @@
const FontsFamily = {
poppinsRegular: 'Poppins-Regular',
poppinsThin: 'Poppins-Thin',
poppinsThinItalic: 'Poppins-ThinItalic',
poppinsExtraLight: 'Poppins-ExtraLight',
poppinsExtraLightItalic: 'Poppins-ExtraLightItalic',
poppinsLight: 'Poppins-Light',
poppinsLightItalic: 'Poppins-LightItalic',
poppinsItalic: 'Poppins-Italic',
poppinsMedium: 'Poppins-Medium',
poppinsMediumItalic: 'Poppins-MediumItalic',
poppinsSemiBold: 'Poppins-SemiBold',
poppinsSemiBoldItalic: 'Poppins-SemiBoldItalic',
poppinsBold: 'Poppins-Bold',
poppinsBoldItalic: 'Poppins-BoldItalic',
poppinsExtraBold: 'Poppins-ExtraBold',
poppinsExtraBoldItalic: 'Poppins-ExtraBoldItalic',
poppinsBlack: 'Poppins-Black',
poppinsBlackItalic: 'Poppins-BlackItalic',
};
export default FontsFamily;
+21
View File
@@ -0,0 +1,21 @@
import OnBoard1 from '../Images/Onboarding/undraw_1.svg'
import OnBoard2 from '../Images/Onboarding/undraw_2.svg'
import OnBoard3 from '../Images/Onboarding/undraw_3.svg'
import Diamond from '../Images/diamond.svg'
import Hill from '../Images/Svg/Hill.svg'
const Logo = require('../Images/logo.png')
const Logo2 = require('../Images/logo2.png')
const Icon = {
Logo,
Logo2
}
const ImagePath = {
Icon,
OnBoard1,
OnBoard2,
OnBoard3,
Diamond,
Hill
}
export default ImagePath;
+13
View File
@@ -0,0 +1,13 @@
const NavigationString = {
Login: 'Login',
Splash: 'Splash',
Home: 'Home',
Onboarding: 'Onboarding',
Auction: 'Auction',
Tender: 'Tender',
PurchaseList: 'PurchaseList',
Emd: 'Emd',
StartBidding: 'StartBidding',
PurchaseAuctionList:'PurchaseAuctionList'
}
export default NavigationString
+26
View File
@@ -0,0 +1,26 @@
import { ALERT_TYPE, Dialog, Root, Toast } from 'react-native-alert-notification';
const showSuccessAlert = (text: string) => {
Toast.show({
type: ALERT_TYPE.SUCCESS,
title: 'Success',
textBody: text,
});
};
const showErrorAlert = (text: string) => {
Toast.show({
type: ALERT_TYPE.DANGER,
title: 'Error',
textBody: text,
});
};
const showToast = (text: string) => {
Toast.show({
type: ALERT_TYPE.SUCCESS,
title: 'Success',
textBody: text,
});
}
export { showErrorAlert, showSuccessAlert, showToast }
+4
View File
@@ -0,0 +1,4 @@
const webSocketUrl = 'https://procurelinc.in/Auction/liveBid/';
const socketUrl = `https://procurelinc.in/Auction/api/`;
const Variables = { webSocketUrl, socketUrl }
export default Variables
+39
View File
@@ -0,0 +1,39 @@
const formatHtmlForWebView = (rawHtml: string) => {
if (!rawHtml) return '';
// Remove dangerous scripts
let safeHtml = rawHtml
.replace(/<script[\s\S]*?<\/script>/gi, '') // Remove all script tags
.replace(/on\w+="[^"]*"/g, '') // Remove inline JS events
.replace(/window\.[^;]*;/g, '') // Remove window.* calls
.replace(/alert\(.*?\);?/g, '') // Remove alert()
.replace(/history\.(forward|back)\(\);?/g, '') // Remove back button blocking
.replace(/document\.onkeydown[\s\S]*?=/g, '') // Remove key blocking
.replace(/contextmenu/gi, ''); // Remove right click disable
// Wrap inside full safe HTML structure
const finalHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
* {
box-sizing: border-box;
-webkit-box-sizing: border-box;
}
body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
</style>
</head>
<body>
${safeHtml}
</body>
</html>
`;
return finalHtml;
};
export default formatHtmlForWebView;
+15
View File
@@ -0,0 +1,15 @@
import { Platform } from 'react-native';
import * as Network from 'expo-network';
export const getDeviceDetails = async () => {
const ipAddress = await Network.getIpAddressAsync();
const detail = [
{ name: 'IMEI', value: '356938035643809' },
{ name: 'IP', value: ipAddress },
{ name: 'OS', value: Platform.OS },
{ name: 'APP', value: 'Globots' },
];
return {detail};
};
+3
View File
@@ -0,0 +1,3 @@
export const getThemeBgColor = (value: string) => {
return value === "light" ? "black" : "white";
};
+6
View File
@@ -0,0 +1,6 @@
let counter = 0;
export const uniqueIdGenerator = (prefix = 'id') => {
const uniquePart = Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
counter++;
return `${prefix}_${uniquePart}_${counter}`;
};
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 735 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 480 480'><path d='M197.6 42.4 42.4 197.6a60 60 0 0 0 0 84.8l155.2 155.2a60 60 0 0 0 84.8 0l155.2-155.2a60 60 0 0 0 0-84.8L282.4 42.4a60 60 0 0 0-84.8 0Z' fill='#fff'></path></svg>
Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

+182
View File
@@ -0,0 +1,182 @@
import React, { useEffect, useRef } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
Dimensions,
Animated,
} from 'react-native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import * as LucideIcons from 'lucide-react-native';
import Screens from '../Screens';
import colors from '../Constant/Color';
import NavigationString from '../Constant/NavigationString';
const { width } = Dimensions.get('window');
const TAB_BAR_HEIGHT = 60;
const TAB_BUTTON_WIDTH = width / 4;
const Tab = createBottomTabNavigator();
const TabButton = ({ route, isFocused, onPress }: any) => {
const icons: Record<string, { outline: any; filled: any }> = {
Home: { outline: "Home", filled: "Home" },
Auction: { outline: "Gavel", filled: "Gavel" },
Tender: { outline: "FileText", filled: "FileText" },
};
const IconName = LucideIcons[isFocused ? icons[route.name].filled : icons[route.name].outline];
const scaleAnim = useRef(new Animated.Value(1)).current;
const translateYAnim = useRef(new Animated.Value(0)).current;
const opacityAnim = useRef(new Animated.Value(1)).current;
useEffect(() => {
if (isFocused) {
Animated.parallel([
Animated.sequence([
Animated.timing(translateYAnim, {
toValue: -8,
duration: 120,
useNativeDriver: true,
}),
Animated.spring(translateYAnim, {
toValue: 0,
friction: 3,
tension: 80,
useNativeDriver: true,
}),
]),
Animated.spring(scaleAnim, {
toValue: 1.3,
friction: 5,
tension: 150,
useNativeDriver: true,
}),
Animated.timing(opacityAnim, {
toValue: 1,
duration: 250,
useNativeDriver: true,
}),
]).start();
} else {
Animated.parallel([
Animated.spring(scaleAnim, {
toValue: 1,
friction: 5,
tension: 150,
useNativeDriver: true,
}),
Animated.timing(opacityAnim, {
toValue: 0.6,
duration: 150,
useNativeDriver: true,
}),
]).start();
}
}, [isFocused]);
return (
<TouchableOpacity
onPress={onPress}
style={[styles.tabButton, { width: TAB_BUTTON_WIDTH }]}
activeOpacity={0.8}
>
<Animated.View
style={[
styles.iconContainer,
{
transform: [{ scale: scaleAnim }, { translateY: translateYAnim }],
opacity: opacityAnim,
},
]}
>
{IconName && (
<IconName
size={20}
color={isFocused ? colors.primary : '#888'}
strokeWidth={2}
/>
)}
</Animated.View>
<Text
style={[
styles.label,
{
color: isFocused ? colors.primary : '#888',
fontWeight: isFocused ? '600' : '400',
},
]}
>
{route.name === 'BrandDay' ? 'Coupons' : route.name}
</Text>
</TouchableOpacity>
);
};
const CustomTabBar = ({ state, descriptors, navigation }: any) => {
return (
<View style={styles.tabBar}>
{state.routes.map((route: any, index: number) => {
const isFocused = state.index === index;
return (
<TabButton
key={route.key}
route={route}
isFocused={isFocused}
onPress={() => navigation.navigate(route.name)}
/>
);
})}
</View>
);
};
export default function BottomTab() {
return (
<View style={styles.container}>
<Tab.Navigator
tabBar={(props) => <CustomTabBar {...props} />}
screenOptions={{
headerShown: false,
tabBarHideOnKeyboard: true,
}}
>
<Tab.Screen name="Home" component={Screens.Home} />
<Tab.Screen name={NavigationString.Auction} component={Screens.Auction} />
<Tab.Screen name={NavigationString.Tender} component={Screens.Tender} />
</Tab.Navigator>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
tabBar: {
flexDirection: 'row',
height: TAB_BAR_HEIGHT,
backgroundColor: '#fff',
borderTopWidth: 1,
borderTopColor: '#eee',
elevation: 5,
},
tabButton: {
justifyContent: 'center',
alignItems: 'center',
height: '100%',
},
iconContainer: {
marginBottom: 4,
justifyContent: 'center',
alignItems: 'center',
},
label: {
fontSize: 11,
textAlign: 'center',
includeFontPadding: false,
},
});
+42
View File
@@ -0,0 +1,42 @@
import * as React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import NavigationString from '../Constant/NavigationString';
import Screens from '../Screens';
import colors from '../Constant/Color';
import BottomTab from './Bottom';
export default function MainStack() {
const Stack = createStackNavigator();
const option = { headerShown: false };
const fadeTransition = {
gestureDirection: 'horizontal',
transitionSpec: {
open: { animation: 'timing', config: { duration: 300 } },
close: { animation: 'timing', config: { duration: 300 } },
},
cardStyleInterpolator: ({ current }) => ({
cardStyle: {
opacity: current.progress,
},
}),
};
return (
<Stack.Navigator
screenOptions={{
cardStyle: { backgroundColor: colors.white },
...fadeTransition
}}
>
<Stack.Screen name={NavigationString.Splash} component={Screens.Splash} 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} />
<Stack.Screen name={NavigationString.StartBidding} component={Screens.StartBidding} options={option} />
<Stack.Screen name={NavigationString.PurchaseAuctionList} component={Screens.PurchaseAuctionList} options={option} />
</Stack.Navigator>
);
}
+5
View File
@@ -0,0 +1,5 @@
import * as React from 'react';
import MainStack from './MainStack';
export default function Routes() {
return <>{MainStack()} </>
}
+116
View File
@@ -0,0 +1,116 @@
import {
check,
request,
RESULTS,
PermissionStatus,
openSettings,
PERMISSIONS,
Permission,
} from 'react-native-permissions';
import { Platform } from 'react-native';
// Normalize permission for platform
const normalizePermission = (perm: string): string => {
return Platform.select({
ios: (PERMISSIONS.IOS as Record<string, string>)[perm] || perm,
android: (PERMISSIONS.ANDROID as Record<string, string>)[perm] || perm,
default: perm,
})!;
};
// Generic permission requester
export const requestSinglePermission = async (
permission: string
): Promise<boolean> => {
const normalized = normalizePermission(permission);
const status: PermissionStatus = await check(normalized);
if (status === RESULTS.GRANTED) {
console.log(`✅ ${permission} already granted.`);
return true;
}
if (status === RESULTS.BLOCKED) {
console.log(`🚫 ${permission} is blocked. Prompt user to go to settings.`);
openSettings();
return false;
}
if (status === RESULTS.DENIED || status === RESULTS.LIMITED) {
const result: PermissionStatus = await request(normalized);
if (result === RESULTS.GRANTED) {
console.log(`✅ ${permission} granted after request.`);
return true;
} else {
console.log(`❌ ${permission} denied.`);
return false;
}
}
return false;
};
// Fallback definition
const POST_NOTIFICATIONS =
Platform.OS === 'android' ? 'android.permission.POST_NOTIFICATIONS' : '';
export const requestNotificationPermission = async (): Promise<boolean> => {
if (Platform.OS !== 'android' || !POST_NOTIFICATIONS) {
console.log('Notification permission not needed on this platform.');
return true;
}
const status: PermissionStatus = await check(POST_NOTIFICATIONS);
if (status === RESULTS.GRANTED) {
console.log('🔔 Notification permission already granted.');
return true;
}
if (status === RESULTS.BLOCKED) {
console.log('🔕 Notification permission is blocked. Prompt user to open settings.');
openSettings();
return false;
}
if (status === RESULTS.DENIED) {
const result: PermissionStatus = await request(POST_NOTIFICATIONS);
if (result === RESULTS.GRANTED) {
console.log('✅ Notification permission granted.');
return true;
} else {
console.log('❌ Notification permission denied.');
return false;
}
}
return false;
};
export const isAnyPermissionGranted = async (permissions: Permission[]): Promise<boolean> => {
for (const permission of permissions) {
const result = await check(permission);
if (result === RESULTS.GRANTED) {
return true; // At least one permission is granted
}
}
return false; // No permissions granted
};
export async function checkPermission(permission:any) {
try {
const result = await check(permission);
return result === RESULTS.GRANTED;
} catch (error) {
console.warn('Permission check error:', error);
return false;
}
}
+20
View File
@@ -0,0 +1,20 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
auction: '',
};
export const auctionSlice = createSlice({
name: 'auction',
initialState,
reducers: {
setAuction: (state, { payload }) => {
state.auction = payload;
},
removeAuction: (state) => {
state.auction = '';
}
},
});
export const { setAuction , removeAuction} = auctionSlice.actions;
export default auctionSlice.reducer;
+20
View File
@@ -0,0 +1,20 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
token: '',
};
export const tokenSlice = createSlice({
name: 'token',
initialState,
reducers: {
setToken: (state, { payload }) => {
state.token = payload;
},
removeToken: (state) => {
state.token = '';
}
},
});
export const { setToken , removeToken} = tokenSlice.actions;
export default tokenSlice.reducer;
@@ -0,0 +1,39 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface AuctionItem {
auctionNumber: string;
[key: string]: any;
}
// ✅ DEFAULT = EMPTY ARRAY
const initialState: AuctionItem[] = [];
export const selectPurchaseAuctionSlice = createSlice({
name: 'selectPurchaseAuction',
initialState,
reducers: {
togglePurchaseAuction: (
state,
action: PayloadAction<AuctionItem>
) => {
const index = state.findIndex(
item => item.auctionNumber === action.payload.auctionNumber
);
if (index !== -1) {
state.splice(index, 1);
} else {
state.push(action.payload);
}
},
clearPurchaseAuction: () => [],
},
});
export const {
togglePurchaseAuction,
clearPurchaseAuction,
} = selectPurchaseAuctionSlice.actions;
export default selectPurchaseAuctionSlice.reducer;
+27
View File
@@ -0,0 +1,27 @@
import {configureStore} from '@reduxjs/toolkit';
import {combineReducers} from 'redux';
import {persistStore, persistReducer} from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {tokenSlice} from './Slices/Token';
import {auctionSlice} from './Slices/SelectedAuction';
import selectPurchaseAuctionReducer from './Slices/selectPurchaseAuction'
const rootReducer = combineReducers({
token: tokenSlice.reducer,
auction: auctionSlice.reducer,
selectPurchaseAuction: selectPurchaseAuctionReducer,
});
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['token'],
};
const persistReducers = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistReducers,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export const persistor = persistStore(store);
+169
View File
@@ -0,0 +1,169 @@
import React, { useState, useRef } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Animated,
Dimensions,
} from 'react-native';
import { InsideHeader } from '../../Component/Index';
import {
useFetchLiveAuction,
useFetchPurchaseAuction,
} from '../../Services/BBPS/Hooks';
import colors from '../../Constant/Color';
import LiveAuction from './LiveAuction';
import PurchaseAuction from './PurchaseAuction';
import { ShoppingCart } from 'lucide-react-native';
import { useNavigation } from '@react-navigation/native';
import NavigationString from '../../Constant/NavigationString';
import FontsFamily from '../../Constant/FontsFamily';
import { useSelector } from 'react-redux';
const { width } = Dimensions.get('screen')
const TAB_WIDTH = 140;
const TAB_HEIGHT = 44;
const Auction = () => {
const Navigation = useNavigation() as any
const { data: liveAuctionData = {} }: any = useFetchLiveAuction();
const { data: purchaseAuctionData = {} } = useFetchPurchaseAuction();
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
const liveCount = Array.isArray(liveAuctionData?.result)
? liveAuctionData?.pages[0]?.totalCount
: 0;
const purchaseCount = Array.isArray(purchaseAuctionData?.data)
? purchaseAuctionData.data.length
: 0;
const [selectedTab, setSelectedTab] = useState<'live' | 'purchase'>('live');
const translateX = useRef(new Animated.Value(0)).current;
const onTabPress = (tab: 'live' | 'purchase', index: number) => {
setSelectedTab(tab);
Animated.spring(translateX, {
toValue: index * TAB_WIDTH,
useNativeDriver: true,
friction: 8,
}).start();
};
return (
<View style={styles.container}>
<InsideHeader title="Auction" showArrow />
{/* Switch Tabs */}
<View style={{ flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center' }}>
<View style={styles.switchWrapper}>
<Animated.View
pointerEvents="none"
style={[
styles.activeTab,
{
transform: [{ translateX }],
},
]}
/>
<TouchableOpacity
style={styles.tab}
activeOpacity={0.8}
onPress={() => onTabPress('live', 0)}
>
<Text
style={[
styles.tabText,
selectedTab === 'live' && styles.activeText,
]}
>
Live ({liveCount})
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.tab}
activeOpacity={0.8}
onPress={() => onTabPress('purchase', 1)}
>
<Text
style={[
styles.tabText,
selectedTab === 'purchase' && styles.activeText,
]}
>
Purchased ({purchaseCount})
</Text>
</TouchableOpacity>
</View>
{/* Cart */}
<View>
{SelectedPurchaseList.length && <View style={{ borderWidth: 2, borderColor: colors.white, backgroundColor: colors.primaryLight, position: 'absolute', borderRadius: 50, zIndex: 20, right: 0, top: -10, paddingHorizontal:2 }}>
<Text style={{ fontSize: 12, color: colors.black }}>{SelectedPurchaseList.length}</Text>
</View>}
<TouchableOpacity style={{ padding: 10, backgroundColor: colors.primaryLight, borderRadius: 20 }} onPress={() => { Navigation.navigate(NavigationString.PurchaseList) }}>
<ShoppingCart size={22} color={colors.black} strokeWidth={2} />
</TouchableOpacity>
</View>
</View>
{/* Content */}
{selectedTab === 'live' ? <LiveAuction /> : <PurchaseAuction />}
</View>
);
};
export default Auction;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.white,
},
/* ---------- Switch UI ---------- */
switchWrapper: {
flexDirection: 'row',
marginVertical: 14,
backgroundColor: '#f1f5f9',
borderRadius: 18,
padding: 2,
height: TAB_HEIGHT,
},
tab: {
width: TAB_WIDTH,
height: TAB_HEIGHT - 4,
alignItems: 'center',
justifyContent: 'center',
zIndex: 2,
},
tabText: {
fontSize: 14,
color: '#64748b',
lineHeight: 18,
fontFamily: FontsFamily.poppinsSemiBold
},
activeText: {
color: colors.primary,
},
activeTab: {
position: 'absolute',
top: 2,
left: 2,
width: TAB_WIDTH,
height: TAB_HEIGHT - 4,
backgroundColor: '#fff',
borderRadius: 16,
shadowColor: '#000',
shadowOpacity: 0.1,
shadowRadius: 6,
elevation: 3,
},
});
+139
View File
@@ -0,0 +1,139 @@
import React, { useCallback, useState, useEffect } from 'react';
import { View, FlatList, ActivityIndicator, RefreshControl, Text } from 'react-native';
import Animated, { FadeInDown, FadeOutUp } from 'react-native-reanimated';
import LiveAuctionTile from './LiveAuctionTile';
import { useNavigation } from '@react-navigation/native';
import { useFetchLiveAuction } from '../../Services/BBPS/Hooks';
import { SearchInput } from '../../Component/Serach/SeacrhInput';
import { Skelton } from '../../Component/Index';
import colors from '../../Constant/Color';
import { useDispatch, useSelector } from 'react-redux';
import { selectPurchaseAuctionSlice, togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
const LiveAuction = () => {
const navigation: any = useNavigation();
const Dispatch = useDispatch()
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch }: any = useFetchLiveAuction();
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
useEffect(() => {
if (isRefetching) {
setShowRefreshBanner(true);
} else {
const timer = setTimeout(() => {
setShowRefreshBanner(false);
}, 1000);
return () => clearTimeout(timer);
}
}, [isRefetching]);
const loadMore = useCallback(() => {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}, [hasNextPage, isFetchingNextPage]);
const renderFooter = useCallback(() => {
if (!isFetchingNextPage) return null;
return (
<View style={{ paddingVertical: 12 }}>
<ActivityIndicator size="small" color={colors.primary} />
</View>
);
}, [isFetchingNextPage]);
const renderItem = useCallback(
({ item, index }: any) => {
const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.auctionNumber === item.auctionNumber)
return <LiveAuctionTile
data={item}
index={index}
auctionId={item.auctionId}
auctionDispatchDate={item.auctionDispatchDate}
isPurchased={isPurchase !== -1 ? true : false}
purchaseEnd={item.purchaseEnd}
auctionOfWork={item.auctionOfWork}
auctionFees={item.auctionFees}
auctionNumber={item.auctionNumber}
onPurchasePress={() => { Dispatch(togglePurchaseAuction(item)) }}
onDetailsPress={() => { }}
/>
},
[SelectedPurchaseList]
);
if (isLoading) {
return <Skelton />;
}
return (
<View style={{ flex: 1 }}>
<SearchInput onChange={() => { }} placeholder="Search...." />
{showRefreshBanner && (
<Animated.View
entering={FadeInDown.duration(200)}
exiting={FadeOutUp.duration(500)}
style={{
alignSelf: 'center',
flexDirection: 'row',
alignItems: 'center',
gap: 8,
paddingVertical: 8,
paddingHorizontal: 14,
marginTop: 6,
borderRadius: 20,
backgroundColor: '#FFFFFF',
shadowColor: '#000',
shadowOpacity: 0.06,
shadowRadius: 8,
shadowOffset: { width: 0, height: 3 },
elevation: 4,
zIndex: 10, // Ensure it stays on top
position: 'absolute',
top: 60, // Adjust based on your SearchInput height
}}
>
<ActivityIndicator size="small" color={colors.primary} />
<Text
style={{
fontSize: 13,
color: '#374151',
fontWeight: '500',
}}
>
Refreshing auctions
</Text>
</Animated.View>
)}
<FlatList
data={data?.result ?? []}
keyExtractor={(item) => String(item.auctionId)}
renderItem={renderItem}
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: 50, paddingTop: 10 }}
onEndReached={loadMore}
onEndReachedThreshold={0.2}
ListFooterComponent={renderFooter}
refreshControl={
<RefreshControl
refreshing={isRefetching}
onRefresh={refetch}
tintColor="transparent" // Set to transparent if you only want your custom banner to show
colors={[colors.primary]}
/>
}
initialNumToRender={6}
maxToRenderPerBatch={6}
windowSize={7}
removeClippedSubviews
updateCellsBatchingPeriod={50}
/>
</View>
);
};
export default LiveAuction;
+242
View File
@@ -0,0 +1,242 @@
import React, { memo } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import moment from 'moment';
import colors from '../../Constant/Color';
import { LiveAuctionTileProps } from './types/types';
import AuctionDetail from './Modal/AuctionDetail';
import { Calendar, Tag, Info, ArrowRight } from 'lucide-react-native';
const LiveAuctionTile: React.FC<LiveAuctionTileProps> = ({
data,
isPurchased = false,
onPurchasePress,
auctionDispatchDate,
auctionOfWork,
purchaseEnd,
auctionFees,
auctionNumber,
}) => {
const [showDetail, setShowDetail] = React.useState(false);
const purchaseEnded = moment().isAfter(moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss'));
return (
<View style={styles.cardWrapper}>
{/* Status Tag */}
<View
style={[
styles.statusTag,
{ backgroundColor: isPurchased ? colors.error : purchaseEnded ? '#94a3b8' : '#4ade80' },
]}
/>
<TouchableOpacity
style={styles.container}
activeOpacity={0.9}
onPress={() => setShowDetail(true)}
>
{/* Header */}
<View style={styles.header}>
<View style={styles.badge}>
<Tag size={14} color={colors.primary} />
<Text style={styles.badgeText}>#{auctionNumber}</Text>
</View>
<Text style={styles.priceText}>₹{auctionFees}</Text>
</View>
{/* Work Title */}
<Text style={styles.workTitle} numberOfLines={2}>
{auctionOfWork ?? 'General Auction Work'}
</Text>
{/* Info Grid */}
<View style={styles.infoGrid}>
<View style={styles.infoItem}>
<Calendar size={16} color="#64748b" />
<View style={styles.infoTextGroup}>
<Text style={styles.infoLabel}>Start</Text>
<Text style={styles.infoValue}>{auctionDispatchDate ?? '-'}</Text>
</View>
</View>
<View style={styles.infoItem}>
<Calendar size={16} color={purchaseEnded ? "#ef4444" : "#22c55e"} />
<View style={styles.infoTextGroup}>
<Text style={styles.infoLabel}>Deadline</Text>
<Text style={[styles.infoValue, purchaseEnded && { color: '#ef4444' }]}>
{moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss').format('DD MMM, YY')}
</Text>
</View>
</View>
</View>
{/* Action Bar */}
<View style={styles.actionBar}>
<TouchableOpacity style={styles.iconBtn} onPress={() => setShowDetail(true)}>
<Info size={18} color={colors.primary} />
<Text style={styles.iconBtnText}>Details</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.mainButton,
purchaseEnded
? styles.btnDisabled
: isPurchased
? styles.btnPurchased
: styles.btnActive,
]}
onPress={onPurchasePress}
disabled={purchaseEnded}
>
<Text
style={[
styles.mainButtonText,
isPurchased && !purchaseEnded ? { color: colors.white } : { color: '#fff' },
]}
>
{purchaseEnded ? 'Expired' : isPurchased ? 'Remove' : 'Add to Cart'}
</Text>
{!purchaseEnded && (
<ArrowRight size={16} color={isPurchased ? colors.white : '#fff'} />
)}
</TouchableOpacity>
</View>
{/* Auction Detail Modal */}
<AuctionDetail visible={showDetail} data={data} onClose={() => setShowDetail(false)} />
</TouchableOpacity>
</View>
);
};
export default memo(LiveAuctionTile);
const styles = StyleSheet.create({
cardWrapper: {
marginVertical: 10,
marginHorizontal: 16,
backgroundColor: '#fff',
borderRadius: 24,
flexDirection: 'row',
overflow: 'hidden',
// Modern shadow
shadowColor: '#000',
shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.08,
shadowRadius: 20,
elevation: 8,
},
statusTag: {
width: 6,
height: '100%',
},
container: {
flex: 1,
padding: 20,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
badge: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.primary + '25',
paddingHorizontal: 12,
paddingVertical: 5,
borderRadius: 12,
gap: 6,
},
badgeText: {
fontSize: 12,
fontWeight: '700',
color: colors.primary,
},
priceText: {
fontSize: 20,
fontWeight: '800',
color: '#0f172a',
},
workTitle: {
fontSize: 16,
fontWeight: '600',
color: '#334155',
lineHeight: 24,
marginBottom: 16,
},
infoGrid: {
flexDirection: 'row',
justifyContent: 'space-between',
backgroundColor: '#f0f4f8',
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 16,
marginBottom: 18,
},
infoItem: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
},
infoTextGroup: {
flexDirection: 'column',
},
infoLabel: {
fontSize: 10,
color: '#94a3b8',
textTransform: 'uppercase',
fontWeight: '700',
},
infoValue: {
fontSize: 13,
fontWeight: '700',
color: '#1e293b',
},
actionBar: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 12,
},
iconBtn: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
paddingHorizontal: 10,
paddingVertical: 6,
borderRadius: 10,
backgroundColor: '#e2e8f0',
},
iconBtnText: {
fontSize: 14,
fontWeight: '700',
color: colors.primary,
},
mainButton: {
flex: 1,
height: 46,
borderRadius: 16,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
},
btnActive: {
backgroundColor: '#4ade80', // light green
},
btnPurchased: {
backgroundColor: colors.error,
borderWidth: 2,
borderColor: colors.error,
},
btnDisabled: {
backgroundColor: '#e2e8f0',
},
mainButtonText: {
fontSize: 14,
fontWeight: '800',
},
});
+345
View File
@@ -0,0 +1,345 @@
import React, { memo, useState } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
SafeAreaView,
Platform,
StatusBar,
} from 'react-native';
import Modal from 'react-native-modal';
import moment from 'moment';
import {
X,
Hash,
Briefcase,
Calendar,
CreditCard,
FileText,
Download,
Eye,
Layers,
ArrowLeft,
Info,
Clock
} from 'lucide-react-native';
import colors from '../../../Constant/Color';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import AuctionItemDetail from './AuctionItemDetail';
interface Props {
visible: boolean;
onClose: () => void;
data?: any;
}
const AuctionFullDetail: React.FC<Props> = ({ visible, onClose, data }) => {
const inset = useSafeAreaInsets()
const [allBoolean, setAllBoolean] = useState({ showItemDetail: false })
const formatDate = (date?: string) => {
if (!date) return '-';
return moment(date, 'DD-MM-YYYY HH:mm:ss').format('DD MMM YYYY, hh:mm A');
};
const isLive = data?.purchaseEnd
? moment().isBefore(moment(data.purchaseEnd, 'DD-MM-YYYY HH:mm:ss'))
: false;
// Internal Components
const Section = ({ title, icon: Icon, children }: any) => (
<View style={styles.sectionContainer}>
<View style={styles.sectionHeader}>
<Icon size={20} color={colors.primary} />
<Text style={styles.sectionTitle}>{title}</Text>
</View>
{children}
</View>
);
const InfoBox = ({ label, value, full = false }: any) => (
<View style={[styles.infoBox, full ? { width: '100%' } : { width: '48%' }]}>
<Text style={styles.label}>{label}</Text>
<Text style={styles.value}>{value || '-'}</Text>
</View>
);
return (
<Modal
isVisible={visible}
style={styles.fullModal}
animationIn="slideInRight"
animationOut="slideOutRight"
useNativeDriver
hideModalContentWhileAnimating
statusBarTranslucent
>
<SafeAreaView style={styles.safeArea}>
<StatusBar barStyle="dark-content" />
{/* Full Header */}
<View style={[styles.header, { paddingTop: inset.top }]}>
<TouchableOpacity onPress={onClose} style={styles.backBtn}>
<ArrowLeft size={24} color="#1e293b" />
</TouchableOpacity>
<View style={styles.headerTitleContainer}>
<Text style={styles.headerTitleText}>Auction Overview</Text>
<Text style={styles.headerIdText}>Ref: {data?.auctionNumber || 'N/A'}</Text>
</View>
<View style={styles.liveIndicator}>
<View style={[styles.dot, { backgroundColor: isLive ? '#22c55e' : '#94a3b8' }]} />
<Text style={styles.liveText}>{isLive ? 'LIVE' : 'CLOSED'}</Text>
</View>
</View>
{/* Scrollable Body */}
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={true}
>
{/* 1. Basic Info Grid */}
<Section title="Details" icon={Layers}>
<View style={styles.grid}>
<InfoBox label="Auction No." value={data?.auctionNumber} />
<InfoBox label="Pattern" value={data?.auctionPattern || 'Forward'} />
<InfoBox label="Mode" value={data?.auctionMode || 'Open'} />
<InfoBox label="Category" value={data?.categoryOfAuction || 'General'} />
<InfoBox label="EMD Type" value={data?.emd || 'Online'} />
<InfoBox label="EMD Figure" value={data?.emdFigure || 'Item Wise'} />
</View>
</Section>
{/* 2. Work Description */}
<Section title="Auction Details" icon={Briefcase}>
<View style={styles.grid}>
<InfoBox label="Auction Name" value={'Auction of cars'} />
<InfoBox label="Category Of Auction " value={'Live'} />
<InfoBox label="Auto Time Increment " value={'yes'} />
<InfoBox label="Auction Fees (In Figure) " value={'500'} />
<InfoBox label="Auction Fees (In Words)" value={'Five Hundred'} />
<InfoBox label="Auction Keyword" value={'Dev Furniture, Furnture'} />
</View>
</Section>
{/* 3. Timeline Section */}
<Section title="Key Dates" icon={Calendar}>
<View style={styles.timelineContainer}>
<View style={styles.timelineRow}>
<View style={styles.iconCircle}><Clock size={16} color="#64748b" /></View>
<View>
<Text style={styles.label}>Auction Purchase (Start)</Text>
<Text style={styles.value}>{formatDate(data?.auctionDispatchDate)}</Text>
</View>
</View>
<View style={styles.timelineConnector} />
<View style={styles.timelineRow}>
<View style={[styles.iconCircle, { backgroundColor: '#fee2e2' }]}><Clock size={16} color="#ef4444" /></View>
<View>
<Text style={styles.label}>Purchase Deadline (End)</Text>
<Text style={styles.value}>{formatDate(data?.purchaseEnd)}</Text>
</View>
</View>
</View>
</Section>
{/* 4. Item Table Section */}
<Section title="Auction Items" icon={FileText}>
<View style={styles.tableWrapper}>
<View style={styles.tableHead}>
<Text style={[styles.tableLabel, { flex: 2 }]}>Item</Text>
<Text style={styles.tableLabel}>Qty</Text>
<Text style={[styles.tableLabel, { textAlign: 'right' }]}>View</Text>
</View>
{/* Dummy Item Row */}
<View style={styles.tableRow}>
<View style={{ flex: 2 }}>
<Text style={styles.itemMain}>Sick Sari / Laundry</Text>
<Text style={styles.itemSub}>Category: Sari</Text>
</View>
<Text style={styles.itemQty}>120,000</Text>
<TouchableOpacity style={styles.actionIcon} onPress={() => { setAllBoolean((prev: any) => ({ ...prev, showItemDetail: true })) }}>
<Eye size={18} color={colors.primary} />
</TouchableOpacity>
</View>
</View>
</Section>
{/* 5. Downloads */}
<Section title="Downloads" icon={Download}>
<TouchableOpacity style={styles.fileCard}>
<View style={styles.fileIcon}><FileText size={20} color="#ef4444" /></View>
<View style={{ flex: 1 }}>
<Text style={styles.fileName}>Auction_Notice_Main.pdf</Text>
<Text style={styles.fileSize}>1.2 MB • PDF</Text>
</View>
<Download size={20} color="#94a3b8" />
</TouchableOpacity>
</Section>
</ScrollView>
{/* Sticky Footer */}
<View style={styles.footer}>
<TouchableOpacity
style={[styles.mainBtn, !isLive && styles.btnDisabled]}
disabled={!isLive}
>
<Text style={styles.mainBtnText}>
{isLive ? 'Add to Cart / Purchase' : 'Auction Closed'}
</Text>
</TouchableOpacity>
</View>
<AuctionItemDetail data={{
productName: "sick sari",
timeDuration: "112 YEAR",
category: "laundry",
subCategory: "sari",
reservePriceFig: "120000",
reservePriceWords: "One Lakh Twenty Thousand",
auctionStartFig: "120000",
auctionStartWords: "One Lakh Twenty Thousand",
bidVariationFig: "200",
bidVariationWords: "Two Hundred",
emdFig: "2000",
emdWords: "Two Thousand",
description: "Auction of cars",
documentName: "BBPS screens.pdf"
}} onClose={() => { setAllBoolean((prev: any) => ({ ...prev, showItemDetail: false })) }} visible={allBoolean.showItemDetail} />
</SafeAreaView>
</Modal>
);
};
const styles = StyleSheet.create({
fullModal: {
margin: 0,
backgroundColor: '#fff',
},
safeArea: {
flex: 1,
backgroundColor: '#fff',
},
header: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#f1f5f9',
backgroundColor: '#fff',
},
backBtn: {
padding: 8,
marginRight: 8,
},
headerTitleContainer: {
flex: 1,
},
headerTitleText: {
fontSize: 18,
fontWeight: '800',
color: '#0f172a',
},
headerIdText: {
fontSize: 13,
color: '#64748b',
},
liveIndicator: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#f8fafc',
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 20,
gap: 6,
borderWidth: 1,
borderColor: '#e2e8f0',
},
dot: { width: 8, height: 8, borderRadius: 4 },
liveText: { fontSize: 11, fontWeight: '800', color: '#475569' },
scrollView: { flex: 1, backgroundColor: '#f8fafc' },
scrollContent: { padding: 16, paddingBottom: 100 },
sectionContainer: {
backgroundColor: '#fff',
borderRadius: 16,
padding: 16,
marginBottom: 16,
borderWidth: 1,
borderColor: '#eef2f6',
},
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
marginBottom: 20,
paddingBottom: 10,
borderBottomWidth: 1,
borderBottomColor: '#f8fafc',
},
sectionTitle: {
fontSize: 14,
fontWeight: '800',
color: '#1e293b',
textTransform: 'uppercase',
letterSpacing: 0.5,
},
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
},
infoBox: { marginBottom: 16 },
label: { fontSize: 12, color: '#94a3b8', fontWeight: '600', marginBottom: 4 },
value: { fontSize: 15, fontWeight: '700', color: '#334155' },
spacer: { height: 12 },
timelineContainer: { paddingLeft: 8 },
timelineRow: { flexDirection: 'row', alignItems: 'center', gap: 15 },
iconCircle: { width: 34, height: 34, borderRadius: 10, backgroundColor: '#f1f5f9', alignItems: 'center', justifyContent: 'center' },
timelineConnector: { width: 2, height: 20, backgroundColor: '#f1f5f9', marginLeft: 16 },
tableWrapper: { backgroundColor: '#f8fafc', borderRadius: 12, padding: 12 },
tableHead: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: '#e2e8f0', paddingBottom: 8, marginBottom: 12 },
tableLabel: { fontSize: 11, fontWeight: '800', color: '#94a3b8', flex: 1 },
tableRow: { flexDirection: 'row', alignItems: 'center' },
itemMain: { fontSize: 14, fontWeight: '700', color: '#1e293b' },
itemSub: { fontSize: 12, color: '#64748b' },
itemQty: { flex: 1, fontSize: 14, fontWeight: '700', color: '#334155' },
actionIcon: { padding: 6 },
fileCard: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: '#f8fafc', padding: 12, borderRadius: 12 },
fileIcon: { width: 40, height: 40, borderRadius: 8, backgroundColor: '#fee2e2', alignItems: 'center', justifyContent: 'center' },
fileName: { fontSize: 14, fontWeight: '700', color: '#334155' },
fileSize: { fontSize: 12, color: '#94a3b8' },
footer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#fff',
padding: 20,
paddingBottom: Platform.OS === 'ios' ? 34 : 20,
borderTopWidth: 1,
borderTopColor: '#f1f5f9',
},
mainBtn: {
backgroundColor: colors.primary,
height: 56,
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
shadowColor: colors.primary,
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 8,
elevation: 4,
},
btnDisabled: { backgroundColor: '#cbd5e1' },
mainBtnText: { color: '#fff', fontSize: 16, fontWeight: '800' },
});
export default memo(AuctionFullDetail);
@@ -0,0 +1,274 @@
import React, { memo } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
SafeAreaView,
Platform,
StatusBar,
Dimensions
} from 'react-native';
import Modal from 'react-native-modal';
import {
X,
Package,
Clock,
Tag,
IndianRupee,
FileText,
Download,
ArrowLeft,
Info,
Layers,
ChevronRight
} from 'lucide-react-native';
import colors from '../../../Constant/Color';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const { width } = Dimensions.get('window');
// --- Exact Dummy Data Mapping ---
const ITEM_DUMMY_DATA = {
productName: "sick sari",
timeDuration: "112 YEAR",
category: "laundry",
subCategory: "sari",
reservePriceFig: "120000",
reservePriceWords: "One Lakh Twenty Thousand",
auctionStartFig: "120000",
auctionStartWords: "One Lakh Twenty Thousand",
bidVariationFig: "200",
bidVariationWords: "Two Hundred",
emdFig: "2000",
emdWords: "Two Thousand",
description: "Auction of cars",
documentName: "BBPS screens.pdf"
};
interface Props {
visible: boolean;
onClose: () => void;
data?: typeof ITEM_DUMMY_DATA;
}
const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data = ITEM_DUMMY_DATA }) => {
const inset = useSafeAreaInsets()
const Section = ({ title, icon: Icon, children }: any) => (
<View style={styles.sectionCard}>
<View style={styles.sectionHeader}>
<View style={styles.iconCircle}>
<Icon size={18} color={colors.primary} strokeWidth={2.5} />
</View>
<Text style={styles.sectionTitle}>{title}</Text>
</View>
{children}
</View>
);
const PriceTile = ({ label, fig, words, full = false }: any) => (
<View style={[styles.priceTile, full ? { width: '100%' } : { width: '48%' }]}>
<Text style={styles.label}>{label}</Text>
<Text style={styles.priceFig}>₹{Number(fig).toLocaleString('en-IN')}</Text>
<Text style={styles.priceWords}>{words}</Text>
</View>
);
return (
<Modal
isVisible={visible}
style={styles.fullModal}
animationIn="slideInUp"
animationOut="slideOutDown"
backdropOpacity={1}
useNativeDriver
statusBarTranslucent
>
<SafeAreaView style={styles.safeArea}>
{/* Navbar */}
<View style={[styles.header, { paddingTop: inset.top }]}>
<TouchableOpacity onPress={onClose} style={styles.backBtn}>
<ArrowLeft size={24} color="#1e293b" />
</TouchableOpacity>
<View>
<Text style={styles.headerTitle}>Item Information</Text>
<Text style={styles.headerSub}>Detail View</Text>
</View>
</View>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
{/* Product Identity Card */}
<Section title="Product Specification" icon={Package}>
<View style={styles.identityHeader}>
<Text style={styles.productNameText}>{data.productName.toUpperCase()}</Text>
<View style={styles.categoryBadge}>
<Text style={styles.categoryText}>{data.category}</Text>
</View>
</View>
<View style={styles.specGrid}>
<View style={styles.specItem}>
<Text style={styles.label}>Sub Category</Text>
<Text style={styles.specValue}>{data.subCategory}</Text>
</View>
<View style={styles.specItem}>
<Text style={styles.label}>Duration</Text>
<Text style={styles.specValue}>{data.timeDuration}</Text>
</View>
</View>
</Section>
{/* Pricing & Financials */}
<Section title="Reserve & Bid Values" icon={IndianRupee}>
<PriceTile
label="Reserve Price"
fig={data.reservePriceFig}
words={data.reservePriceWords}
full
/>
<View style={styles.hDivider} />
<PriceTile
label="Auction Start Value"
fig={data.auctionStartFig}
words={data.auctionStartWords}
full
/>
<View style={styles.hDivider} />
<View style={styles.row}>
<PriceTile label="Bid Variation" fig={data.bidVariationFig} words={data.bidVariationWords} />
<PriceTile label="EMD Amount" fig={data.emdFig} words={data.emdWords} />
</View>
</Section>
{/* Description Card */}
<Section title="Brief Description" icon={Info}>
<Text style={styles.descContent}>{data.description}</Text>
</Section>
{/* Documents Card */}
<Section title="View Documents" icon={FileText}>
<View style={styles.docTableHead}>
<Text style={styles.tableLabel}>Sr No.</Text>
<Text style={[styles.tableLabel, { flex: 2 }]}>Document Name</Text>
<Text style={styles.tableLabel}>Action</Text>
</View>
<TouchableOpacity style={styles.docRow} activeOpacity={0.7}>
<Text style={styles.docIndex}>1</Text>
<View style={{ flex: 2 }}>
<Text style={styles.docTitle} numberOfLines={1}>{data.documentName}</Text>
</View>
<View style={styles.downloadIconBox}>
<Download size={18} color={colors.primary} />
</View>
</TouchableOpacity>
</Section>
</ScrollView>
</SafeAreaView>
</Modal>
);
};
const styles = StyleSheet.create({
fullModal: { margin: 0, backgroundColor: '#f8fafc' },
safeArea: { flex: 1 },
header: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#fff',
borderBottomWidth: 1,
borderBottomColor: '#f1f5f9',
},
backBtn: { padding: 8, marginRight: 8 },
headerTitle: { fontSize: 18, fontWeight: '800', color: '#0f172a' },
headerSub: { fontSize: 12, color: '#94a3b8', fontWeight: '600', textTransform: 'uppercase' },
scrollView: { flex: 1 },
scrollContent: { padding: 16, paddingBottom: 50 },
sectionCard: {
backgroundColor: '#fff',
borderRadius: 24,
padding: 20,
marginBottom: 16,
...Platform.select({
ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.05, shadowRadius: 10 },
android: { elevation: 3 }
})
},
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
marginBottom: 20,
},
iconCircle: {
width: 36,
height: 36,
borderRadius: 12,
backgroundColor: colors.primary + '10',
alignItems: 'center',
justifyContent: 'center',
},
sectionTitle: {
fontSize: 14,
fontWeight: '800',
color: '#334155',
textTransform: 'uppercase',
letterSpacing: 0.5,
},
// Product Identity
identityHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 },
productNameText: { fontSize: 20, fontWeight: '900', color: '#1e293b', flex: 1 },
categoryBadge: { backgroundColor: '#f1f5f9', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20 },
categoryText: { fontSize: 11, fontWeight: '800', color: '#64748b', textTransform: 'uppercase' },
specGrid: { flexDirection: 'row', gap: 24, marginTop: 5 },
specItem: { flex: 1 },
specValue: { fontSize: 15, fontWeight: '700', color: '#334155', marginTop: 2 },
// Pricing
priceTile: { marginBottom: 16 },
label: { fontSize: 11, color: '#94a3b8', fontWeight: '700', textTransform: 'uppercase', marginBottom: 4 },
priceFig: { fontSize: 22, fontWeight: '900', color: colors.black },
priceWords: { fontSize: 13, color: '#64748b', fontStyle: 'italic', marginTop: 2 },
hDivider: { height: 1, backgroundColor: '#f1f5f9', marginBottom: 16 },
row: { flexDirection: 'row', justifyContent: 'space-between' },
// Description
descContent: { fontSize: 15, color: '#475569', lineHeight: 22, fontWeight: '500' },
// Table
docTableHead: { flexDirection: 'row', paddingBottom: 10, borderBottomWidth: 1, borderBottomColor: '#f1f5f9', marginBottom: 10 },
tableLabel: { fontSize: 11, fontWeight: '800', color: '#94a3b8', flex: 1 },
docRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#f8fafc', padding: 12, borderRadius: 12 },
docIndex: { flex: 1, fontSize: 14, fontWeight: '700', color: '#64748b' },
docTitle: { fontSize: 14, fontWeight: '700', color: '#1e293b' },
downloadIconBox: { backgroundColor: '#fff', padding: 8, borderRadius: 10, borderWidth: 1, borderColor: '#e2e8f0' },
// Footer
footer: {
position: 'absolute',
bottom: 0, width: '100%',
backgroundColor: '#fff',
padding: 20,
paddingBottom: Platform.OS === 'ios' ? 34 : 20,
borderTopWidth: 1, borderTopColor: '#f1f5f9'
},
primaryBtn: {
backgroundColor: colors.primary,
height: 56, borderRadius: 18,
flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10
},
primaryBtnText: { color: '#fff', fontSize: 16, fontWeight: '800' }
});
export default memo(AuctionItemDetail);
+259
View File
@@ -0,0 +1,259 @@
import React, { useState } from 'react';
import {
Modal,
View,
Text,
StyleSheet,
TouchableOpacity,
Pressable,
} from 'react-native';
import DatePicker from 'react-native-date-picker';
import moment from 'moment';
import colors from '../../../Constant/Color';
import FontsFamily from '../../../Constant/FontsFamily';
type AuctionStatus = 'RUNNING' | 'ENDED' | 'NOT_STARTED' | null;
interface Props {
visible: boolean;
onClose: () => void;
onApply: (filters: {
fromDate: Date | null;
toDate: Date | null;
status: AuctionStatus;
}) => void;
}
const FilterModal: React.FC<Props> = ({ visible, onClose, onApply }) => {
const [fromDate, setFromDate] = useState<Date | null>(null);
const [toDate, setToDate] = useState<Date | null>(null);
const [status, setStatus] = useState<AuctionStatus>(null);
const [openPicker, setOpenPicker] = useState(false);
const [pickerType, setPickerType] = useState<'FROM' | 'TO'>('FROM');
const openDatePicker = (type: 'FROM' | 'TO') => {
setPickerType(type);
setOpenPicker(true);
};
const handleConfirm = (date: Date) => {
if (pickerType === 'FROM') {
setFromDate(date);
} else {
setToDate(date);
}
setOpenPicker(false);
};
const handleApply = () => {
onApply({ fromDate, toDate, status });
onClose();
};
return (
<>
<Modal
visible={visible}
animationType="slide"
transparent
statusBarTranslucent
onRequestClose={onClose}
>
{/* Overlay */}
<Pressable style={styles.overlay} onPress={onClose} />
{/* Content */}
<View style={styles.container}>
<Text style={styles.title}>Filter Auctions</Text>
{/* ===== DATE RANGE ===== */}
<Text style={styles.sectionTitle}>Date Range</Text>
<View style={styles.dateRow}>
<TouchableOpacity
style={styles.dateBox}
onPress={() => openDatePicker('FROM')}
>
<Text style={styles.dateLabel}>From</Text>
<Text style={styles.dateValue}>
{fromDate
? moment(fromDate).format('DD MMM YYYY')
: 'Select date'}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.dateBox}
onPress={() => openDatePicker('TO')}
>
<Text style={styles.dateLabel}>To</Text>
<Text style={styles.dateValue}>
{toDate
? moment(toDate).format('DD MMM YYYY')
: 'Select date'}
</Text>
</TouchableOpacity>
</View>
{/* ===== STATUS ===== */}
<Text style={styles.sectionTitle}>Auction Status</Text>
<View style={styles.statusRow}>
{[
{ label: 'Running', value: 'RUNNING' },
{ label: 'Ended', value: 'ENDED' },
{ label: 'Not Started', value: 'NOT_STARTED' },
].map(item => {
const active = status === item.value;
return (
<TouchableOpacity
key={item.value}
onPress={() => setStatus(item.value as AuctionStatus)}
style={[
styles.statusPill,
active && styles.statusActive,
]}
>
<Text
style={[
styles.statusText,
active && styles.statusActiveText,
]}
>
{item.label}
</Text>
</TouchableOpacity>
);
})}
</View>
{/* ===== FOOTER ===== */}
<View style={styles.footer}>
{/* <TouchableOpacity style={styles.cancelBtn} onPress={onClose}>
<Text style={styles.cancelText}>Cancel</Text>
</TouchableOpacity> */}
<TouchableOpacity style={styles.applyBtn} onPress={handleApply}>
<Text style={styles.applyText}>Apply</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
{/* ===== DATE PICKER ===== */}
<DatePicker
modal
open={openPicker}
date={
pickerType === 'FROM'
? fromDate || new Date()
: toDate || new Date()
}
mode="date"
onConfirm={handleConfirm}
onCancel={() => setOpenPicker(false)}
/>
</>
);
};
export default FilterModal;
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.4)',
},
container: {
position: 'absolute',
bottom: 0,
width: '100%',
backgroundColor: '#fff',
borderTopLeftRadius: 22,
borderTopRightRadius: 22,
padding: 20,
},
title: {
fontSize: 18,
fontWeight: '700',
marginBottom: 20,
},
sectionTitle: {
fontSize: 14,
fontWeight: '600',
marginBottom: 10,
color: '#475569',
},
dateRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 20,
},
dateBox: {
width: '48%',
padding: 14,
borderRadius: 12,
backgroundColor: '#F8FAFC',
borderWidth: 1,
borderColor: '#E2E8F0',
},
dateLabel: {
fontSize: 11,
color: '#64748B',
marginBottom: 4,
},
dateValue: {
fontSize: 14,
fontWeight: '600',
color: '#0F172A',
},
statusRow: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 10,
},
statusPill: {
paddingVertical: 10,
paddingHorizontal: 14,
borderRadius: 20,
borderWidth: 1,
borderColor: '#CBD5E1',
backgroundColor: '#F8FAFC',
},
statusActive: {
backgroundColor: colors.primary,
borderColor: colors.primary,
},
statusText: {
fontSize: 13,
color: '#334155',
fontFamily:FontsFamily.poppinsSemiBold
},
statusActiveText: {
color: '#fff',
},
footer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 24,
},
cancelBtn: {
padding: 12,
},
applyBtn: {
backgroundColor: colors.primary,
paddingHorizontal: 20,
paddingVertical: 12,
borderRadius: 12,
width: '100%'
},
applyText: {
color: '#fff',
fontSize: 16,
textAlign: 'center',
fontFamily: FontsFamily.poppinsSemiBold
},
});
+75
View File
@@ -0,0 +1,75 @@
import React, { useEffect, useState, useCallback, memo } from 'react';
import { View, Text, StyleSheet, FlatList, RefreshControl } from 'react-native';
import LiveAuctionTile from './LiveAuctionTile';
import { useNavigation } from '@react-navigation/native';
import moment from 'moment';
import { useFetchPurchaseAuction } from '../../Services/BBPS/Hooks';
import PurchaseAuctionTile from './PurchaseAuctionTile';
import { useDispatch } from 'react-redux';
import { setAuction } from '../../Redux/Slices/SelectedAuction';
import { SearchInput } from '../../Component/Serach/SeacrhInput';
import { Skelton } from '../../Component/Index';
import { allBooleanProps } from './types/types';
import FilterModal from './Modal/FilterModal';
const PurchaseAuction = () => {
const navigation = useNavigation() as any;
const Dispatch = useDispatch()
const { data = {}, isPending } = useFetchPurchaseAuction();
const [allBoolean, setAllBoolean] = useState<allBooleanProps>({ isShowFilter: false })
const format = 'DD-MM-YYYY HH:mm:ss';
function isAuctionActive(item: any): boolean {
const now = moment();
const start = moment(item?.keyDates?.auctionBidding?.startDateTime, format);
const end = moment(item?.keyDates?.auctionBidding?.endDateTime, format);
if (!start.isValid() || !end.isValid()) return false;
return now.isBetween(start, end, undefined, '[]');
}
function isAuctionEnd(item: any): boolean {
const now = moment();
const end = moment(item?.keyDates?.auctionBidding?.endDateTime, format);
if (!end.isValid()) return false;
return now.isAfter(end);
}
console.log(allBoolean);
return (
<View style={{ flex: 1 }}>
<SearchInput onChange={() => { }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} />
{isPending ? <Skelton /> : <FlatList
data={data?.data.slice(0, 10) || []}
keyExtractor={(_, index) => index.toString()}
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: 200, paddingHorizontal: 10 }}
renderItem={({ item, index }) => (
<PurchaseAuctionTile
item={item}
onPressDetails={() => {
navigation.navigate('AuctionDetail', { item, onAddToPurchase: () => { }, routeFrom: 'PurchasedAuction' });
}}
onStartBidding={() => {
Dispatch(setAuction(item))
// navigation.navigate(NavigationString, { data: item })
}}
buttonEnable={isAuctionActive(item)}
isAuctionEnd={isAuctionEnd(item)}
/>
)}
/>}
{allBoolean.isShowFilter && <View style={{ flex: 1 }}>
<FilterModal visible={allBoolean.isShowFilter} onClose={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: false })) }} onApply={() => { }} />
</View>}
</View>
);
};
export default PurchaseAuction;
+277
View File
@@ -0,0 +1,277 @@
import React, { useState } from 'react';
import { View, Text, FlatList, StyleSheet, TouchableOpacity, Alert } from 'react-native';
import { InsideHeader, Loader } from '../../Component/Index';
import colors from '../../Constant/Color';
import { CreditCard, Calendar, User, DollarSign, ArrowRight } from 'lucide-react-native';
import { useSelector } from 'react-redux';
import { useOfflineAuctionPurchase } from '../../Services/BBPS/Hooks';
const dummyAuctionList = [
{
auctionId: 1,
auctionNumber: 'AUC-2025-001',
auctionPattern: 'Single Stage',
auctionDispatchDate: '25 Feb 2025',
auctionDispatcherNumber: 'DISP-001',
auctionFees: 2500,
gst: 18,
gstAmount: 450,
portalCharge: 100,
payableAmount: 3050,
},
{
auctionId: 2,
auctionNumber: 'AUC-2025-002',
auctionPattern: 'Two Stage',
auctionDispatchDate: '28 Feb 2025',
auctionDispatcherNumber: 'DISP-002',
auctionFees: 1800,
gst: 18,
gstAmount: 324,
portalCharge: 80,
payableAmount: 2204,
},
{
auctionId: 3,
auctionNumber: 'AUC-2025-003',
auctionPattern: 'Single Stage',
auctionDispatchDate: '01 Mar 2025',
auctionDispatcherNumber: 'DISP-003',
auctionFees: 3200,
gst: 18,
gstAmount: 576,
portalCharge: 120,
payableAmount: 3896,
},
];
const PurchaseAuctionList = () => {
const [loading, setLoading] = useState(false);
const [list, setList] = useState(dummyAuctionList);
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
console.log(SelectedPurchaseList,'iiiiiiiiiiiiiiiiiiiiiiii');
const { mutate, isPending } = useOfflineAuctionPurchase()
const calculateTotalAmount = (item: any) => {
const gstAmount =
(Number(item.auctionFees) + Number(item.portalCharge)) *
(Number(item.gst) / 100);
const payableAmount =
Number(item.auctionFees) + Number(item.portalCharge) + gstAmount;
return payableAmount
}
const handleOfflinePurchase = async () => {
const body = {
auctionDetails: SelectedPurchaseList.map((b: any) => ({
auctionDispatchDate: b?.auctionDispatchDate,
auctionDispatcherNumber: b?.auctionDispatcherNumber,
auctionFees: b?.auctionFees,
auctionId: b?.auctionId,
auctionNumber: b?.auctionNumber,
auctionPattern: b?.auctionPattern,
gst: b?.gst,
gstAmount: b?.gstAmount,
payableAmount: calculateTotalAmount(b),
portalCharge: b?.portalCharge
})),
orderNo: ''
};
try {
// console.log(body);
mutate(body, {
onSuccess: (res) => {
console.log(res, '---------------------purchsase response');
},
onError: (error) => {
console.log(error, "================purchse Error");
}
})
} catch (error) {
console.log("purchase offline error", error);
}
}
const handlePurchase = (type: 'online' | 'offline') => {
setLoading(true);
setTimeout(() => {
setLoading(false);
type === 'online' ? Alert.alert('Online Purchase Successful!') : handleOfflinePurchase();
}, 1200);
};
const renderItem = ({ item }: { item: typeof dummyAuctionList[0] }) => (
<View style={styles.card}>
<View style={styles.cardHeader}>
<Text style={styles.title}>{item.auctionNumber}</Text>
<Text style={styles.pattern}>{item.auctionPattern}</Text>
</View>
<View style={styles.cardRow}>
<Calendar size={18} color={colors.primary} />
<Text style={styles.text}>Dispatch Date: {item.auctionDispatchDate}</Text>
</View>
<View style={styles.cardRow}>
<User size={18} color={colors.primary} />
<Text style={styles.text}>Dispatcher No: {item.auctionDispatcherNumber}</Text>
</View>
<View style={styles.cardRow}>
<DollarSign size={18} color={colors.primary} />
<Text style={styles.text}>Auction Fees: ₹{item.auctionFees}</Text>
</View>
<View style={styles.cardRow}>
<DollarSign size={18} color={colors.primary} />
<Text style={styles.text}>GST ({item.gst}%): ₹{item.gstAmount}</Text>
</View>
<View style={styles.cardRow}>
<DollarSign size={18} color={colors.primary} />
<Text style={styles.text}>Portal Charge: ₹{item.portalCharge}</Text>
</View>
<View style={styles.totalRow}>
<CreditCard size={20} color={colors.white} />
<Text style={styles.total}>Total: ₹{item.payableAmount}</Text>
</View>
</View>
);
return (
<View style={{ flex: 1, backgroundColor: colors.white }}>
<InsideHeader title="Purchase List" showArrow />
<View style={styles.screen}>
<FlatList
data={SelectedPurchaseList}
renderItem={renderItem}
keyExtractor={(item) => item.auctionId.toString()}
contentContainerStyle={styles.container}
/>
</View>
<View style={styles.bottomButtonContainer}>
<TouchableOpacity
style={[styles.button, { marginRight: 8 }]}
onPress={() => handlePurchase('online')}
>
<Text style={styles.buttonText}>Purchase Online</Text>
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, { marginLeft: 8 }]}
onPress={() => handlePurchase('offline')}
>
<Text style={styles.buttonText}>Purchase Offline</Text>
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
</TouchableOpacity>
</View>
{loading && (
<View style={styles.loadingOverlay}>
<Loader />
</View>
)}
</View>
);
};
export default PurchaseAuctionList;
const styles = StyleSheet.create({
screen: {
flex: 1,
},
container: {
padding: 16,
paddingBottom: 120,
},
card: {
backgroundColor: '#fff',
borderRadius: 16,
padding: 16,
marginBottom: 16,
elevation: 5,
shadowColor: '#000',
shadowOpacity: 0.12,
shadowOffset: { width: 0, height: 3 },
shadowRadius: 8,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 12,
},
title: {
fontSize: 16,
fontWeight: '700',
color: '#0F172A',
},
pattern: {
fontSize: 14,
color: colors.primary,
fontWeight: '600',
},
cardRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
marginBottom: 4,
},
text: {
fontSize: 14,
color: '#555',
},
totalRow: {
marginTop: 10,
backgroundColor: colors.primary,
borderRadius: 12,
paddingVertical: 10,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
gap: 8,
},
total: {
fontSize: 15,
fontWeight: '700',
color: '#fff',
},
bottomButtonContainer: {
position: 'absolute',
bottom: 0,
left: 0,
flexDirection: 'row',
backgroundColor: colors.white,
width: '100%',
paddingVertical: 20,
paddingHorizontal: 10,
elevation: 5
},
button: {
flex: 1,
backgroundColor: colors.primary,
paddingVertical: 14,
borderRadius: 12,
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'center',
},
buttonText: {
color: '#fff',
fontWeight: '700',
fontSize: 14,
},
loadingOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.2)',
justifyContent: 'center',
alignItems: 'center',
zIndex: 10,
},
});
+274
View File
@@ -0,0 +1,274 @@
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Platform,
} from 'react-native';
import Animated, {
FadeInUp,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import moment from 'moment';
import CountdownTimer from '../../Component/Counter/CountdownTimer';
import colors from '../../Constant/Color';
const PurchaseAuctionTile = ({
item,
onPressDetails,
onStartBidding,
buttonEnable,
isAuctionEnd,
}: any) => {
const scale = useSharedValue(1);
const elevation = useSharedValue(8);
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ scale: scale.value }],
elevation: elevation.value, // Android-safe
};
}, []);
const handlePressIn = () => {
scale.value = withTiming(0.97, { duration: 120 });
elevation.value = withTiming(3, { duration: 120 });
};
const handlePressOut = () => {
scale.value = withTiming(1, { duration: 160 });
elevation.value = withTiming(8, { duration: 160 });
};
const statusColor = isAuctionEnd
? '#EF4444'
: buttonEnable
? colors.primary
: '#94A3B8';
const lightStatusBg = isAuctionEnd
? '#FEF2F2'
: buttonEnable
? '#EFF6FF'
: '#F8FAFC';
return (
<Animated.View
entering={FadeInUp.duration(400)}
style={[styles.shadowContainer, animatedStyle]}
>
<TouchableOpacity
activeOpacity={1}
onPress={onPressDetails}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
style={styles.cardInternal}
>
<View style={[styles.statusAccent, { backgroundColor: statusColor }]} />
<View style={styles.mainInfo}>
{/* Header */}
<View style={styles.headerRow}>
<View>
<View style={[styles.statusBadge, { backgroundColor: lightStatusBg }]}>
<View style={[styles.statusDot, { backgroundColor: statusColor }]} />
<Text style={[styles.statusText, { color: statusColor }]}>
{isAuctionEnd ? 'ENDED' : buttonEnable ? 'LIVE' : 'PENDING'}
</Text>
</View>
<Text style={styles.title}>{item.auctionNumber}</Text>
</View>
<View style={styles.amountContainer}>
<Text style={styles.amount}>
₹{item.auctionFees?.toLocaleString()}
</Text>
<Text style={styles.feeLabel}>Auction Fee</Text>
</View>
</View>
{/* Date */}
<View style={styles.dateRow}>
<Text style={styles.dateText}>
<Text style={styles.dateLabel}>Bidding Ends: </Text>
{moment(item?.keyDates?.auctionBidding?.endDateTime).format(
'DD MMM, hh:mm A'
)}
</Text>
</View>
{/* Footer */}
<View style={styles.footer}>
<View style={styles.footerTop}>
<Text style={styles.footerLabel}>Remaining Time</Text>
{buttonEnable && !isAuctionEnd ? (
<CountdownTimer
endDate={item.keyDates.auctionBidding.endDateTime}
/>
) : (
<Text style={styles.endedText}>--:--:--</Text>
)}
</View>
<TouchableOpacity
disabled={!buttonEnable}
onPress={onStartBidding}
activeOpacity={0.85}
style={[
styles.actionButton,
{ backgroundColor: statusColor },
!buttonEnable && styles.disabledButton,
]}
>
<Text style={styles.addText}>
{isAuctionEnd
? 'Bidding Closed'
: !buttonEnable
? 'Payment Required'
: 'Proceed to Start Bidding'}
</Text>
</TouchableOpacity>
</View>
</View>
</TouchableOpacity>
</Animated.View>
);
};
export default PurchaseAuctionTile;
const styles = StyleSheet.create({
shadowContainer: {
marginHorizontal: 5,
marginTop: 14,
marginBottom: 6,
borderRadius: 16,
backgroundColor: '#fff',
elevation: 8,
},
cardInternal: {
borderRadius: 16,
overflow: 'hidden',
flexDirection: 'row',
backgroundColor: '#fff',
},
statusAccent: {
width: 5,
height: '100%',
},
mainInfo: {
flex: 1,
padding: 16,
},
headerRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: 12,
},
statusBadge: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 3,
borderRadius: 6,
marginBottom: 6,
alignSelf: 'flex-start',
},
statusDot: {
width: 6,
height: 6,
borderRadius: 3,
marginRight: 5,
},
statusText: {
fontSize: 10,
fontWeight: '800',
letterSpacing: 0.5,
},
title: {
fontWeight: '800',
fontSize: 16,
color: '#1E293B',
},
amountContainer: {
alignItems: 'flex-end',
},
amount: {
fontWeight: '900',
fontSize: 18,
color: '#0F172A',
},
feeLabel: {
fontSize: 10,
color: '#64748B',
fontWeight: '500',
},
dateRow: {
backgroundColor: '#F8FAFC',
paddingHorizontal: 10,
paddingVertical: 8,
borderRadius: 10,
marginBottom: 16,
borderWidth: 1,
borderColor: '#F1F5F9',
},
dateText: {
fontSize: 12,
color: '#334155',
fontWeight: '600',
},
dateLabel: {
fontWeight: '400',
color: '#64748B',
},
footer: {
borderTopWidth: 1,
borderTopColor: '#F1F5F9',
paddingTop: 12,
},
footerTop: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
footerLabel: {
fontSize: 11,
fontWeight: '600',
color: '#94A3B8',
textTransform: 'uppercase',
},
endedText: {
fontSize: 13,
fontWeight: '700',
color: '#CBD5E1',
},
actionButton: {
width: '100%',
height: 48,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
}),
},
disabledButton: {
backgroundColor: '#E2E8F0',
},
addText: {
color: '#fff',
fontSize: 14,
fontWeight: '800',
letterSpacing: 0.2,
},
});
+220
View File
@@ -0,0 +1,220 @@
import React, { useState } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import Animated, { FadeIn, FadeOut, Layout, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
import { ChevronDown, Trash2, Info } from 'lucide-react-native';
import { InsideHeader } from '../../Component/Index';
import colors from '../../Constant/Color';
import { useNavigation } from '@react-navigation/native';
import NavigationString from '../../Constant/NavigationString';
import { useDispatch, useSelector } from 'react-redux';
import { togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
const PurchaseList = () => {
const Navigation: any = useNavigation()
const Dispatch = useDispatch()
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
const toggleExpand = (index: number) => {
setExpandedIndex(prev => (prev === index ? null : index));
};
return (
<View style={styles.screen}>
<InsideHeader title="Purchase" showArrow />
<ScrollView contentContainerStyle={styles.container}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.heading}>Purchase List</Text>
<Info size={20} color="#0F172A" />
</View>
{/* Cards */}
{SelectedPurchaseList.map((item: any, index: any) => {
const expanded = expandedIndex === index;
return (
<Animated.View
key={item.auctionId}
layout={Layout.springify()}
style={styles.card}
>
<TouchableOpacity
activeOpacity={0.9}
onPress={() => toggleExpand(index)}
>
<CardHeader
title={item.auctionOfWork}
expanded={expanded}
/>
<Text style={styles.subTitle}>
Auction No: {item.auctionNumber}
</Text>
<Text style={styles.subTitle}>
Status:{' '}
<Text
style={{
color: item.status ? '#16A34A' : '#DC2626',
fontWeight: '700',
}}
>
{item.status ? 'Open' : 'Closed'}
</Text>
</Text>
</TouchableOpacity>
{/* Expanded Content */}
{expanded && (
<Animated.View
entering={FadeIn.duration(220)}
exiting={FadeOut.duration(180)}
layout={Layout.springify()}
style={styles.details}
>
{renderRow('Auction Mode', item.auctionMode)}
{renderRow('Pattern', item.auctionPattern)}
{renderRow('Fees', `₹ ${item.auctionFees}`)}
{renderRow('Department', item.deptName)}
{renderRow('Purchase End', item.purchaseEnd)}
<TouchableOpacity style={styles.removeButton} onPress={() => { Dispatch(togglePurchaseAuction(item)) }}>
<Trash2 size={18} color="#fff" />
<Text style={styles.removeText}>Remove</Text>
</TouchableOpacity>
</Animated.View>
)}
</Animated.View>
);
})}
</ScrollView>
{/* Footer */}
<TouchableOpacity disabled={Boolean(!SelectedPurchaseList.length)} style={[styles.proceedButton, { backgroundColor: Boolean(!SelectedPurchaseList.length) ? colors.lightText : colors.primary }]} onPress={() => { Navigation.navigate(NavigationString.PurchaseAuctionList) }}>
<Text style={styles.proceedText}>Proceed to Purchase</Text>
</TouchableOpacity>
</View>
);
};
export default PurchaseList;
const CardHeader = ({ title, expanded }: {
title: string;
expanded: boolean;
}) => {
const rotation = useSharedValue(expanded ? 1 : 0);
rotation.value = withTiming(expanded ? 1 : 0, { duration: 220 });
const arrowStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${rotation.value * 180}deg` }],
}));
return (
<View style={styles.cardHeader}>
<Text style={styles.title}>{title}</Text>
<Animated.View style={arrowStyle}>
<ChevronDown size={22} color={colors.black} />
</Animated.View>
</View>
);
};
const renderRow = (label: string, value: string) => (
<View style={styles.row}>
<Text style={styles.label}>{label}</Text>
<Text style={styles.value}>{value}</Text>
</View>
);
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: '#F8FAFC',
},
container: {
padding: 16,
paddingBottom: 110,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 14,
},
heading: {
fontSize: 22,
fontWeight: '800',
color: colors.black,
},
card: {
backgroundColor: '#fff',
borderRadius: 18,
padding: 16,
marginBottom: 14,
borderWidth: 1,
borderColor: '#E5E7EB',
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
title: {
fontSize: 16,
fontWeight: '700',
color: '#0F172A',
flex: 1,
marginRight: 12,
},
subTitle: {
fontSize: 14,
color: '#64748B',
marginTop: 6,
},
details: {
marginTop: 16,
paddingTop: 14,
borderTopWidth: 1,
borderTopColor: '#E5E7EB',
},
row: {
marginVertical: 6,
},
label: {
fontSize: 13,
color: '#64748B',
},
value: {
fontSize: 15,
fontWeight: '600',
color: '#0F172A',
},
removeButton: {
marginTop: 18,
backgroundColor: colors.primary,
borderRadius: 14,
paddingVertical: 12,
flexDirection: 'row',
justifyContent: 'center',
gap: 8,
},
removeText: {
color: '#fff',
fontWeight: '700',
},
proceedButton: {
position: 'absolute',
bottom: 16,
left: 16,
right: 16,
backgroundColor: colors.primary,
paddingVertical: 16,
borderRadius: 18,
alignItems: 'center',
},
proceedText: {
color: '#fff',
fontSize: 16,
fontWeight: '800',
},
});
+18
View File
@@ -0,0 +1,18 @@
export interface LiveAuctionTileProps {
data: any
onPurchasePress?: () => void;
onDetailsPress?: () => void;
index: number;
auctionDispatchDate?: string;
auctionOfWork?: string;
purchaseEnd?: string;
auctionFees?: string | number;
isPurchased?: boolean;
auctionId?: any;
auctionNumber?: any;
}
export interface allBooleanProps {
showDetail?: boolean
isShowFilter?: boolean
}
@@ -0,0 +1,65 @@
import React, { useEffect, useState } from 'react';
import { Text, View } from 'react-native';
import moment from 'moment';
import LottieView from 'lottie-react-native';
import colors from '../../Constant/Color';
const AuctionTimer = ({ SelectedAuction, setAllBoolean }: any) => {
const [timeLeft, setTimeLeft] = useState('00:00:00');
useEffect(() => {
if (!SelectedAuction?.keyDates?.auctionBidding?.endDateTime) return;
const format = 'DD-MM-YYYY HH:mm:ss';
const endTime = moment(
SelectedAuction.keyDates.auctionBidding.endDateTime,
format
);
const timer = setInterval(() => {
const now = moment();
const diff = moment.duration(endTime.diff(now));
if (diff.asSeconds() <= 0) {
setTimeLeft('00:00:00');
clearInterval(timer);
setAllBoolean((prev: any) => ({
...prev,
timeEnd: true,
}));
return;
}
const hours = String(Math.floor(diff.asHours())).padStart(2, '0');
const minutes = String(diff.minutes()).padStart(2, '0');
const seconds = String(diff.seconds()).padStart(2, '0');
setTimeLeft(`${hours}:${minutes}:${seconds}`);
}, 1000);
return () => clearInterval(timer);
}, [SelectedAuction]);
return (
<View
style={{
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
gap: 5,
}}
>
<LottieView
source={require('../../lottie/clock.json')}
autoPlay
loop
style={{ width: 20, height: 20 }}
/>
<Text style={{ fontSize: 18, color: colors.black }}>{timeLeft}</Text>
</View>
);
};
export default AuctionTimer;
@@ -0,0 +1,105 @@
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import LottieView from 'lottie-react-native';
import Animated, { FadeInUp } from 'react-native-reanimated';
import colors from '../../../Constant/Color';
const EmdComplete = ({ onNext }: { onNext?: () => void }) => {
return (
<View style={styles.container}>
<LottieView
source={require('../../../lottie/confetti.json')}
autoPlay
loop={true}
style={styles.confetti}
/>
<LottieView
source={require('../../../lottie/Success.json')}
autoPlay
loop={true}
style={styles.success}
/>
<Animated.Text entering={FadeInUp.delay(400).duration(700)} style={styles.title}>
EMD Complete
</Animated.Text>
<Animated.Text entering={FadeInUp.delay(700).duration(700)} style={styles.subText} >
Your submission was successful
</Animated.Text>
<Animated.View entering={FadeInUp.delay(900).duration(700)} style={styles.nextWrapper} >
<TouchableOpacity activeOpacity={0.85} style={styles.nextBtn} onPress={onNext} >
<Text style={styles.nextText}>Next</Text>
</TouchableOpacity>
</Animated.View>
</View>
);
};
export default EmdComplete;
/* ---------------- Styles ---------------- */
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F8FAFC',
alignItems: 'center',
justifyContent: 'center',
},
confetti: {
position: 'absolute',
top: 0,
width: '100%',
height: '100%',
},
success: {
width: 220,
height: 220,
marginTop: -100
},
title: {
marginTop: 16,
fontSize: 24,
fontWeight: '900',
color: '#0F172A',
letterSpacing: 0.5,
},
subText: {
marginTop: 8,
fontSize: 14,
color: '#475569',
},
/* ---------- Floating Next Button ---------- */
nextWrapper: {
position: 'absolute',
right: 20,
bottom: 30,
},
nextBtn: {
backgroundColor: colors.primary,
paddingHorizontal: 26,
paddingVertical: 14,
borderRadius: 28,
shadowColor: colors.primary,
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.35,
shadowRadius: 14,
elevation: 6,
},
nextText: {
color: '#FFFFFF',
fontSize: 15,
fontWeight: '800',
letterSpacing: 0.4,
},
});
+351
View File
@@ -0,0 +1,351 @@
import React, { 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 { InsideHeader, Skelton } from '../../Component/Index';
import colors from '../../Constant/Color';
import EmdComplete from './Component/EmdComplete';
import NavigationString from '../../Constant/NavigationString';
import { useNavigation } from '@react-navigation/native';
const DUMMY_AUCTION_ITEMS = [
{ id: 1, productName: 'Iron Scrap Lot A', emdAmount: 50000, itemDocumentName: null },
{ id: 2, productName: 'Steel Rods Lot B', emdAmount: 75000, itemDocumentName: 'old_emd.pdf' },
{ id: 3, productName: 'Copper Wire Lot C', emdAmount: 60000, itemDocumentName: null },
];
type SelectedItem = {
auctionItemId: number;
productImageName: string;
file: {
name: string;
uri: string;
type: string;
} | null;
};
const Emd = () => {
const Navigation: any = useNavigation()
const [selectedItems, setSelectedItems] = useState<SelectedItem[]>([]);
const [acceptedPolicy, setAcceptedPolicy] = useState(false);
const [isComplete, setIsComplete] = useState(false);
/* ---------------- Helpers ---------------- */
const isSelected = (id: number) => selectedItems.some(i => i.auctionItemId === id);
const toggleSelect = (item: any, checked: boolean) => {
if (!checked) {
setSelectedItems(prev =>
prev.filter(i => i.auctionItemId !== item.id),
);
} else {
setSelectedItems(prev => [
...prev,
{ auctionItemId: item.id, productImageName: '', file: null },
]);
}
};
const updateSelectedItem = (id: number, data: Partial<SelectedItem>) => {
setSelectedItems(prev =>
prev.map(i => (i.auctionItemId === id ? { ...i, ...data } : i)),
);
};
const pickFile = async (id: number) => {
try {
const res = await pick({ type: ['application/pdf'] });
const file = res[0];
updateSelectedItem(id, {
file: {
name: file.name ?? 'document.pdf',
uri: file.uri,
type: 'application/pdf',
},
});
} catch {
console.log('Pick cancelled');
}
};
const handleSubmit = () => {
if (!acceptedPolicy) {
Alert.alert('Policy Required', 'Please accept terms');
return;
}
if (selectedItems.length === 0) {
Alert.alert('Select Item', 'Please select at least one item');
return;
}
for (const item of selectedItems) {
if (!item.productImageName || !item.file) {
Alert.alert('Incomplete', 'Upload documents for all selected items');
return;
}
}
console.log('✅ SUBMITTED:', selectedItems);
setIsComplete(true);
};
/* ---------------- Render Item ---------------- */
const renderItem = ({ item }: any) => {
const selected = isSelected(item.id);
const selectedData = selectedItems.find(
i => i.auctionItemId === item.id,
);
return (
<Animated.View
layout={Layout.springify()}
entering={FadeInDown}
style={[styles.card, selected && styles.cardSelected]}
>
<View style={styles.cardHeader}>
<View>
<Text style={styles.title}>{item.productName}</Text>
<Text style={styles.subText}>EMD ₹ {item.emdAmount}</Text>
</View>
<BouncyCheckbox
size={20}
fillColor={colors.primary}
iconStyle={{ borderRadius: 5, borderWidth: 1.5, borderColor: colors.primary }}
innerIconStyle={{ borderRadius: 6 }}
isChecked={selected}
onPress={(checked) => toggleSelect(item, checked)}
/>
</View>
<Text style={styles.meta}>
Existing Doc:{' '}
<Text style={styles.bold}>
{item.itemDocumentName ?? 'Not Uploaded'}
</Text>
</Text>
{selected && (
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
style={styles.form}
>
<TextInput
placeholder="Document name"
value={selectedData?.productImageName}
onChangeText={text =>
updateSelectedItem(item.id, { productImageName: text })
}
style={styles.input}
/>
<TouchableOpacity
style={styles.uploadBtn}
onPress={() => pickFile(item.id)}
>
<Text style={styles.uploadText}>
{selectedData?.file ? 'Change PDF' : 'Upload PDF'}
</Text>
</TouchableOpacity>
{selectedData?.file && (
<Text style={styles.fileName}>
{selectedData.file.name}
</Text>
)}
</Animated.View>
)}
</Animated.View>
);
};
return (
<View style={{ flex: 1, backgroundColor: colors.white }}>
<InsideHeader title="EMD Submission" showArrow />
<View style={styles.container}>
{false ? (
<Skelton />
) : true ? (
<EmdComplete onNext={() => { Navigation.navigate(NavigationString.StartBidding) }} />
) : <>
<FlatList
data={DUMMY_AUCTION_ITEMS}
keyExtractor={item => item.id.toString()}
renderItem={renderItem}
contentContainerStyle={{ paddingBottom: 170 }}
/>
<View style={styles.bottom}>
<BouncyCheckbox
size={18}
fillColor={colors.primary}
iconStyle={{ borderRadius: 5, borderWidth: 1.5, borderColor: colors.primary }}
innerIconStyle={{ borderRadius: 5 }}
isChecked={acceptedPolicy}
onPress={() => setAcceptedPolicy(p => !p)}
text="I agree to Terms & Privacy Policy"
textStyle={styles.policyText}
/>
<TouchableOpacity
style={[
styles.submitBtn,
!acceptedPolicy && styles.disabled,
]}
onPress={handleSubmit}
disabled={!acceptedPolicy}
>
<Text style={styles.submitText}>Submit EMD</Text>
</TouchableOpacity>
</View>
</>
}
</View>
</View>
);
};
export default Emd;
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 10
},
card: {
backgroundColor: '#FFFFFF',
marginHorizontal: 5,
marginTop: 16,
padding: 18,
borderRadius: 8,
elevation: 5,
overflow: 'hidden'
},
cardSelected: {
backgroundColor: colors.primaryHighlight,
borderWidth: 1.5,
borderColor: colors.primary,
},
cardHeader: {
width: '100%',
flexDirection: 'row',
justifyContent: 'space-between',
},
title: {
fontSize: 17,
fontWeight: '800',
color: '#0F172A',
},
subText: {
fontSize: 13,
color: '#64748B',
marginTop: 6,
},
meta: {
marginTop: 10,
fontSize: 13,
color: '#475569',
},
bold: {
fontWeight: '700',
color: '#020617',
},
form: {
marginTop: 16,
paddingTop: 16,
borderTopWidth: 1,
borderColor: '#E2E8F0',
},
input: {
borderWidth: 1,
borderColor: '#CBD5E1',
borderRadius: 14,
padding: 12,
backgroundColor: '#FFFFFF',
fontSize: 14,
},
uploadBtn: {
marginTop: 12,
backgroundColor: colors.primary,
paddingVertical: 13,
borderRadius: 14,
alignItems: 'center',
shadowColor: colors.primaryHighlight,
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.3,
shadowRadius: 12,
elevation: 4,
},
uploadText: {
color: '#FFFFFF',
fontWeight: '800',
fontSize: 14,
},
fileName: {
marginTop: 8,
fontSize: 12,
color: '#475569',
},
bottom: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
padding: 16,
backgroundColor: '#FFFFFF',
borderTopWidth: 1,
borderColor: '#E2E8F0',
shadowColor: '#000',
shadowOffset: { width: 0, height: -6 },
shadowOpacity: 0.08,
shadowRadius: 14,
elevation: 12,
},
policyText: {
fontSize: 13,
color: '#334155',
},
submitBtn: {
marginTop: 14,
backgroundColor: colors.primary,
paddingVertical: 15,
borderRadius: 16,
alignItems: 'center',
shadowColor: colors.primaryHighlight,
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.32,
shadowRadius: 16,
elevation: 6,
},
disabled: {
backgroundColor: colors.lightText
},
submitText: {
color: '#FFFFFF',
fontWeight: '800',
fontSize: 15,
},
});
+250
View File
@@ -0,0 +1,250 @@
import React, { useEffect, useState, useCallback } from 'react';
import {
View,
Text,
FlatList,
TextInput,
TouchableOpacity,
StyleSheet,
Platform,
Alert,
} from 'react-native';
import LottieView from 'lottie-react-native';
import Animated, {
FadeInUp,
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
Layout,
interpolate,
} from 'react-native-reanimated';
import { InsideHeader } from '../../Component/Index';
import colors from '../../Constant/Color';
/* ---------------- Types ---------------- */
interface AuctionItem {
id: number;
productName: string;
auctionStartValueFigure: number;
bidVariationValue: number;
bidAmount: number;
bidAvilable: number;
me: boolean;
}
/* ---------------- Dummy Data ---------------- */
const DUMMY_AUCTION = { auctionNumber: 'AUC-2025-001' };
const DUMMY_ITEMS: AuctionItem[] = [
{ id: 1, productName: 'Iron Scrap Lot', auctionStartValueFigure: 20000, bidVariationValue: 500, bidAmount: 20000, bidAvilable: 20000, me: false },
{ id: 2, productName: 'Steel Rod Bundle', auctionStartValueFigure: 35000, bidVariationValue: 1000, bidAmount: 35000, bidAvilable: 35000, me: true },
{ id: 3, productName: 'Copper Wire Roll', auctionStartValueFigure: 15000, bidVariationValue: 250, bidAmount: 15000, bidAvilable: 0, me: false },
];
/* ---------------- Bidding Card ---------------- */
const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid }: any) => {
const [expanded, setExpanded] = useState(false);
const isMin = bidValue <= item.bidAmount;
const animation = useSharedValue(0);
const toggleExpand = () => {
setExpanded(!expanded);
animation.value = withSpring(expanded ? 0 : 1, { damping: 15 });
};
const bodyStyle = useAnimatedStyle(() => {
return {
height: interpolate(animation.value, [0, 1], [0, 190]),
opacity: interpolate(animation.value, [0, 0.5, 1], [0, 0, 1]),
marginTop: interpolate(animation.value, [0, 1], [0, 15]),
};
});
const arrowStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${interpolate(animation.value, [0, 1], [0, 180])}deg` }],
}));
return (
<Animated.View
layout={Layout.springify()}
entering={FadeInUp.delay(item.id * 100)}
style={[styles.card, item.me && styles.leadingCard]}
>
{/* Header - Always Visible */}
<TouchableOpacity
activeOpacity={0.7}
onPress={toggleExpand}
style={styles.cardHeader}
>
<View style={{ flex: 1 }}>
<Text style={styles.productName}>{item.productName}</Text>
<Text style={styles.itemIdText}>{DUMMY_AUCTION.auctionNumber}</Text>
</View>
<View style={styles.headerRight}>
<View style={[styles.badge, item.me ? styles.meBadge : styles.liveBadge]}>
<Text style={[styles.badgeText, item.me ? styles.meText : styles.liveText]}>
{item.me ? 'LEADING' : 'LIVE'}
</Text>
</View>
<Animated.Text style={[styles.arrow, arrowStyle]}>▼</Animated.Text>
</View>
</TouchableOpacity>
{/* Main Price Info - Always Visible */}
<View style={styles.priceRow}>
<View>
<Text style={styles.label}>Current Bid</Text>
<Text style={styles.mainPrice}>₹{item.bidAmount.toLocaleString()}</Text>
</View>
<View style={{ alignItems: 'flex-end' }}>
<Text style={styles.label}>Increment</Text>
<Text style={styles.incrementText}>+₹{item.bidVariationValue}</Text>
</View>
</View>
{/* Collapsible Section */}
<Animated.View style={[styles.collapsibleContainer, bodyStyle]}>
<View style={styles.divider} />
<Text style={styles.bidLabel}>Set Your Bid</Text>
<View style={styles.stepperContainer}>
<TouchableOpacity
onPress={onDecrease}
disabled={isMin}
style={[styles.stepBtn, isMin && styles.disabledBtn]}
>
<Text style={styles.stepText}>−</Text>
</TouchableOpacity>
<View style={styles.inputBox}>
<Text style={styles.currency}>₹</Text>
<Text style={styles.bidValueText}>{bidValue.toLocaleString()}</Text>
</View>
<TouchableOpacity onPress={onIncrease} style={styles.stepBtn}>
<Text style={styles.stepText}>+</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
style={styles.submitBtn}
onPress={onPlaceBid}
activeOpacity={0.8}
>
<Text style={styles.submitBtnText}>Confirm Bid</Text>
</TouchableOpacity>
</Animated.View>
</Animated.View>
);
};
/* ---------------- Main Screen ---------------- */
const StartBidding = () => {
const [items, setItems] = useState<AuctionItem[]>(DUMMY_ITEMS);
const [bidValues, setBidValues] = useState<Record<number, number>>({});
const [showSuccess, setShowSuccess] = useState(false);
useEffect(() => {
const initial: Record<number, number> = {};
items.forEach(item => {
initial[item.id] = item.bidAmount + item.bidVariationValue;
});
setBidValues(initial);
}, []);
const handleUpdate = (id: number, val: number) => {
setBidValues(prev => ({ ...prev, [id]: prev[id] + val }));
};
const handlePlaceBid = (item: AuctionItem) => {
Alert.alert('Confirm', `Place bid for ₹${bidValues[item.id]}?`, [
{ text: 'No' },
{
text: 'Yes, Bid',
onPress: () => {
setItems(prev => prev.map(i => i.id === item.id ? { ...i, bidAmount: bidValues[item.id], me: true } : i));
setShowSuccess(true);
setTimeout(() => setShowSuccess(false), 2000);
}
}
]);
};
return (
<View style={styles.container}>
<InsideHeader title="Bidding Panel" showArrow />
<FlatList
data={items}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<BiddingCard
item={item}
bidValue={bidValues[item.id] || 0}
onIncrease={() => handleUpdate(item.id, item.bidVariationValue)}
onDecrease={() => handleUpdate(item.id, -item.bidVariationValue)}
onPlaceBid={() => handlePlaceBid(item)}
/>
)}
contentContainerStyle={{ padding: 16 }}
/>
{showSuccess && (
<View style={styles.overlay}>
<LottieView
source={require('../../lottie/Success.json')}
autoPlay
loop={false}
style={{ width: 200, height: 200 }}
/>
</View>
)}
</View>
);
};
export default StartBidding;
/* ---------------- Modern Styles ---------------- */
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.white },
card: {
backgroundColor: '#FFF',
borderRadius: 24,
padding: 20,
marginBottom: 16,
borderWidth: 1,
borderColor: '#E2E8F0',
overflow: 'hidden',
...Platform.select({ ios: { shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 10 }, android: { elevation: 2 } }),
},
leadingCard: { borderColor: '#22C55E', borderWidth: 2, backgroundColor: '#F0FDF4' },
cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start' },
headerRight: { flexDirection: 'row', alignItems: 'center', gap: 8 },
productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' },
itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 },
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
liveBadge: { backgroundColor: '#FEE2E2' },
meBadge: { backgroundColor: '#BBF7D0' },
badgeText: { fontSize: 10, fontWeight: '900' },
liveText: { color: '#EF4444' },
meText: { color: '#15803D' },
arrow: { fontSize: 10, color: '#94A3B8' },
priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
label: { fontSize: 11, color: '#94A3B8', textTransform: 'uppercase', fontWeight: '600' },
mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' },
incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' },
collapsibleContainer: { overflow: 'hidden' },
divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 },
bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 },
stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
stepBtn: { width: 45, height: 45, backgroundColor: colors.primary, borderRadius: 12, justifyContent: 'center', alignItems: 'center' },
disabledBtn: { backgroundColor: '#CBD5E1' },
stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' },
inputBox: { flex: 1, height: 45, backgroundColor: '#F1F5F9', borderRadius: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 },
bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' },
submitBtn: { height: 50, backgroundColor: colors.primary, borderRadius: 15, justifyContent: 'center', alignItems: 'center' },
submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' },
overlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(255,255,255,0.7)', justifyContent: 'center', alignItems: 'center', zIndex: 99 },
});
+18
View File
@@ -0,0 +1,18 @@
export type AuctionItem = {
id: number;
productName: string;
auctionStartValueFigure: number;
bidVariationValue: number;
bidAmount: any
me: boolean
bidAvilable: number
inventoryId: number
};
export type allBooleanType = {
ItemsBasedBids: any
showBidModal: boolean
isActiveItem: boolean
activeItemId: number | null
acitveItemData: any
timeEnd: boolean
}
+252
View File
@@ -0,0 +1,252 @@
import React, { useEffect } from 'react';
import {
View,
Text,
StyleSheet,
Dimensions,
FlatList,
ActivityIndicator,
} from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withDelay,
withTiming,
} from 'react-native-reanimated';
import {
Layers,
Clock,
ShoppingCart,
X,
FileText,
} from 'lucide-react-native';
import { Header } from '../../Component/Index';
import colors from '../../Constant/Color';
import { useWebSocketService } from '../../socket/Socket';
import Variables from '../../Constant/Variable';
import { useFocusEffect } from '@react-navigation/native';
import { useDashboard } from '../../Services/BBPS/Hooks';
const { width } = Dimensions.get('window');
const CARD_WIDTH = (width - 48) / 2;
const StatCard = ({ item, index, isLoading }: any) => {
const translateY = useSharedValue(30);
const opacity = useSharedValue(0);
useEffect(() => {
translateY.value = withDelay(index * 80, withTiming(0, { duration: 400 }));
opacity.value = withDelay(index * 80, withTiming(1, { duration: 400 }));
}, []);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateY: translateY.value }],
opacity: opacity.value,
}));
const Icon = item.icon;
return (
<Animated.View style={[styles.card, animatedStyle]}>
<View style={styles.header}>
<Text style={styles.title} numberOfLines={2}>
{item.title}
</Text>
<View style={[styles.badge, { borderColor: item.color }]}>
<Icon size={14} color={item.color} />
<Text style={[styles.badgeText, { color: item.color }]}>
{item.value}
</Text>
</View>
</View>
{/* VALUE / LOADER */}
{isLoading ? (
<ActivityIndicator
size="small"
color={item.color}
style={{ marginTop: 12 }}
/>
) : (
<Text style={styles.value}>{item.value}</Text>
)}
<Text style={styles.desc}>{item.desc}</Text>
</Animated.View>
);
};
const Home = () => {
const { data, isLoading } = useDashboard();
const dashboard = data?.data || {};
const DASHBOARD_CARDS = [
{
title: 'Total Auctions',
value: dashboard.myAuctionCount ?? 0,
desc: 'Auctions purchased by you',
icon: Layers,
color: '#2563EB',
},
{
title: 'Live Auctions',
value: dashboard.liveAuctionCount ?? 0,
desc: 'Currently live',
icon: Clock,
color: '#0284C7',
},
{
title: 'Participated Auctions',
value: dashboard.currentBiddingCount ?? 0,
desc: 'Participated in auction',
icon: ShoppingCart,
color: '#7C3AED',
},
{
title: 'Cancelled Auctions',
value: dashboard.cancelledAuctionCount ?? 0,
desc: 'Cancelled by department',
icon: X,
color: '#DC2626',
},
{
title: 'Total Tenders',
value: 0,
desc: 'Tenders assigned',
icon: FileText,
color: '#16A34A',
},
{
title: 'Live Tenders',
value: 0,
desc: 'Currently live',
icon: Clock,
color: '#059669',
},
{
title: 'Participated Tenders',
value: 0,
desc: 'Participated in tender',
icon: ShoppingCart,
color: '#0EA5E9',
},
{
title: 'Cancelled Tenders',
value: 0,
desc: 'Cancelled by authority',
icon: X,
color: '#EF4444',
},
];
/* ---------------- WebSocket ---------------- */
const { connect } = useWebSocketService(
Variables.webSocketUrl,
() => console.log('WebSocket Connected'),
error => console.log('WebSocket Error:', error),
);
useFocusEffect(() => {
connect();
});
return (
<View style={styles.screen}>
<Header title="Dashboard" />
<FlatList
data={DASHBOARD_CARDS}
keyExtractor={(_, index) => index.toString()}
numColumns={2}
columnWrapperStyle={styles.row}
contentContainerStyle={styles.list}
renderItem={({ item, index }) => (
<StatCard
item={item}
index={index}
isLoading={isLoading}
/>
)}
showsVerticalScrollIndicator={false}
/>
</View>
);
};
export default Home;
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: colors.white,
},
list: {
padding: 16,
paddingBottom: 24,
},
row: {
justifyContent: 'space-between',
marginBottom: 16,
},
card: {
width: CARD_WIDTH,
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 16,
borderWidth: 1,
borderColor: '#E5E7EB',
shadowColor: '#000',
shadowOpacity: 0.05,
shadowRadius: 8,
elevation: 3,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
},
title: {
fontSize: 13,
fontWeight: '600',
color: '#374151',
flex: 1,
paddingRight: 8,
},
badge: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
borderWidth: 1,
borderRadius: 20,
paddingHorizontal: 8,
paddingVertical: 4,
},
badgeText: {
fontSize: 12,
fontWeight: '700',
},
value: {
fontSize: 28,
fontWeight: '800',
color: '#111827',
marginTop: 5,
},
desc: {
fontSize: 12,
color: '#6B7280',
marginTop: 6,
},
});
+9
View File
@@ -0,0 +1,9 @@
export interface DashboardCard {
id: number;
title: 'Tenders' | 'Auctions';
leftLabel: string;
leftValue: number;
rightLabel: string;
rightValue: number;
buttonText: string;
}
+306
View File
@@ -0,0 +1,306 @@
import React, { useState } from 'react';
import { View, Text, Dimensions, TextInput, TouchableOpacity, StyleSheet, StatusBar, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { Formik } from 'formik';
import * as Yup from 'yup';
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 { 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 { Loader } from '../../Component/Index';
const hapticOptions = {
enableVibrateFallback: true,
ignoreAndroidSystemSettings: false
};
const { height, width } = Dimensions.get('window');
const LoginSchema = Yup.object().shape({
email: Yup.string().email('Invalid email address').required('Email is required'),
password: Yup.string().min(6, 'Minimum 6 characters').required('Password is required'),
});
const Login = () => {
const Navigation: any = useNavigation()
const Dispatch = useDispatch()
const [passwordVisible, setPasswordVisible] = React.useState(false);
const [loading, setLoading] = useState(false)
const { mutate, isPending } = useLogin()
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 },
],
})
);
}
setLoading(false)
},
onError: (err) => {
showErrorAlert(err.message)
setLoading(false)
}
})
}
return (
<View style={styles.container}>
<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}
>
<Animated.Image
entering={FadeInDown.delay(200).duration(800)}
source={ImagePath.Icon.Logo}
resizeMode="contain"
style={styles.logo}
/>
</LinearGradient>
</View>
<Animated.View
entering={FadeInUp.springify().damping(15)}
style={styles.formContainer}
>
<View style={styles.sheetHandle} />
<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)
}}
>
{({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => (
<View style={{}}>
<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} />
<TextInput
placeholder="name@example.com"
placeholderTextColor="#A0A0A0"
style={styles.input}
keyboardType="email-address"
autoCapitalize="none"
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
/>
</View>
{errors.email && touched.email && <Text style={styles.errorText}>{errors.email}</Text>}
<Text style={[styles.label, { marginTop: 16 }]}>Password</Text>
<View style={[styles.inputWrapper, touched.password && errors.password && styles.inputError]}>
<Lock size={20} color={touched.password && !errors.password ? colors.primary : colors.grayText} />
<TextInput
placeholder="••••••••"
placeholderTextColor="#A0A0A0"
style={styles.input}
secureTextEntry={!passwordVisible}
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
value={values.password}
/>
<TouchableOpacity onPress={() => setPasswordVisible(!passwordVisible)}>
{passwordVisible ? <EyeOff size={20} color={colors.grayText} /> : <Eye size={20} color={colors.grayText} />}
</TouchableOpacity>
</View>
{errors.password && touched.password && <Text style={styles.errorText}>{errors.password}</Text>}
<TouchableOpacity style={styles.forgotPass}>
<Text style={styles.forgotPassText}>Forgot Password?</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.8}
onPress={() => {
ReactNativeHapticFeedback.trigger("impactLight", hapticOptions);
handleSubmit();
}}
style={styles.buttonShadow}
>
<LinearGradient
colors={[colors.primary, colors.primaryLight || '#4c669f']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={styles.button}
>
{
loading ? <Loader color={colors.white} size='small'/> : <>
<Text style={styles.buttonText}>Sign In</Text>
<ArrowRight size={20} color={colors.white} style={{ marginLeft: 8, marginTop: 5 }} /></>
}
</LinearGradient>
</TouchableOpacity>
</View>
)}
</Formik>
</Animated.View>
</ScrollView>
</KeyboardAvoidingView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.white,
},
header: {
height: height * 0.35,
},
gradient: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
borderBottomLeftRadius: 0,
},
logo: {
height: 200,
width: 200,
},
formContainer: {
flex: 1,
backgroundColor: colors.white,
marginTop: -40,
borderTopLeftRadius: 35,
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: {
width: 40,
height: 5,
backgroundColor: '#E0E0E0',
borderRadius: 10,
alignSelf: 'center',
marginBottom: 20,
},
title: {
fontSize: 28,
fontWeight: '800',
color: '#1A1A1A',
letterSpacing: -0.5,
},
subtitle: {
fontSize: 15,
color: '#7C7C7C',
marginTop: 6,
marginBottom: 30,
},
label: {
fontSize: 14,
fontWeight: '600',
color: '#444',
marginBottom: 8,
marginLeft: 4,
},
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#F8F9FB',
borderRadius: 16,
borderWidth: 1.5,
borderColor: '#F0F0F0',
paddingHorizontal: 16,
height: 58,
},
inputError: {
borderColor: '#FF5252',
backgroundColor: '#FFF8F8',
},
input: {
flex: 1,
marginLeft: 12,
fontSize: 16,
color: '#1A1A1A',
fontWeight: '500',
},
errorText: {
color: '#FF5252',
fontSize: 12,
marginTop: 5,
marginLeft: 12,
fontWeight: '500',
},
forgotPass: {
alignSelf: 'flex-end',
marginTop: 12,
marginBottom: 24,
},
forgotPassText: {
color: colors.primary,
fontWeight: '600',
fontSize: 14,
},
buttonShadow: {
shadowColor: colors.primary,
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.3,
shadowRadius: 12,
elevation: 8,
},
button: {
height: 58,
borderRadius: 18,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
buttonText: {
color: colors.white,
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;
+151
View File
@@ -0,0 +1,151 @@
import React from 'react';
import { View, Text, StyleSheet, Dimensions, TouchableOpacity } from 'react-native';
import AppIntroSlider from 'react-native-app-intro-slider';
import ImagePath from '../../Constant/ImagePath';
import NavigationString from '../../Constant/NavigationString';
import colors from '../../Constant/Color';
import FontsFamily from '../../Constant/FontsFamily';
import { useNavigation } from '@react-navigation/native';
import AsyncStorage from '@react-native-async-storage/async-storage';
const { width } = Dimensions.get('window');
const slides = [
{
id: 1,
title: 'Welcome',
description: 'Discover a smarter way to manage your work and stay productive every day.',
Image: ImagePath.OnBoard1,
},
{
id: 2,
title: 'Stay Organized',
description: 'Keep all your tasks, updates, and important information in one place.',
Image: ImagePath.OnBoard2,
},
{
id: 3,
title: 'Real-Time Updates',
description: 'Get instant updates and notifications so you never miss anything important.',
Image: ImagePath.OnBoard3,
},
{
id: 4,
title: 'Get Started',
description: 'Let’s begin your journey and explore all the powerful features of the app.',
Image: ImagePath.OnBoard3,
},
];
const Onboarding = () => {
const Navigation: any = useNavigation()
const onFinish = async () => {
await AsyncStorage.setItem('onBoarding', 'true');
Navigation.navigate(NavigationString.Login)
};
const renderItem = ({ item }: any) => (
<View style={styles.slide}>
<item.Image />
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.description}>{item.description}</Text>
</View>
);
const renderNextButton = () => (
<View style={styles.btnCircle}>
<Text style={styles.btnText}>Next</Text>
</View>
);
const renderPrevButton = () => (
<View style={styles.btnCircle}>
<Text style={styles.btnText}>Prev</Text>
</View>
);
// ✅ Done Button
const renderDoneButton = () => (
<TouchableOpacity style={styles.btnCircle} onPress={onFinish}>
<Text style={styles.btnText}>Start</Text>
</TouchableOpacity>
);
return (
<AppIntroSlider
data={slides}
renderItem={renderItem}
showSkipButton
showPrevButton
renderNextButton={renderNextButton}
renderPrevButton={renderPrevButton}
renderDoneButton={renderDoneButton}
onSkip={onFinish}
onDone={onFinish}
activeDotStyle={styles.activeDot}
dotStyle={styles.dot}
contentContainerStyle={{ paddingBottom: 100 }}
/>
);
};
export default Onboarding;
const styles = StyleSheet.create({
slide: {
flex: 1,
backgroundColor: colors.white,
justifyContent: 'center',
paddingHorizontal: 24,
},
image: {
width: width * 0.75,
height: width * 0.75,
resizeMode: 'contain',
marginBottom: 40,
},
title: {
fontSize: 24,
fontFamily: FontsFamily.poppinsBold,
color: colors.primary,
textAlign: 'center',
marginBottom: 12,
},
description: {
fontSize: 16,
fontFamily: FontsFamily.poppinsRegular,
color: colors.grayText,
textAlign: 'center',
lineHeight: 22,
},
dot: {
backgroundColor: colors.border,
},
activeDot: {
backgroundColor: colors.primary,
width: 22,
},
btnCircle: {
paddingHorizontal: 16,
paddingVertical: 8,
backgroundColor: colors.primary,
borderRadius: 20,
width: 80
},
btnText: {
color: colors.white,
fontFamily: FontsFamily.poppinsMedium,
textAlign: 'center'
},
doneBtn: {
paddingHorizontal: 24,
paddingVertical: 10,
backgroundColor: colors.primary,
borderRadius: 24,
},
doneText: {
color: colors.white,
fontFamily: FontsFamily.poppinsMedium,
textAlign: 'center'
},
});
+40
View File
@@ -0,0 +1,40 @@
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 AsyncStorage from '@react-native-async-storage/async-storage';
import { useSelector } from 'react-redux';
const Splash = () => {
const navigation: any = useNavigation();
const Token = useSelector((state: any) => state.token.token)
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);
}
};
useEffect(() => {
handleSplashNavigation()
}, []);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontFamily: FontsFamily.poppinsBlack }}>
Splash
</Text>
</View>
);
};
export default Splash;
+12
View File
@@ -0,0 +1,12 @@
import { View, Text } from 'react-native'
import React from 'react'
const Tender = () => {
return (
<View>
<Text>Tender</Text>
</View>
)
}
export default Tender
+25
View File
@@ -0,0 +1,25 @@
import Auction from "./Auction/Auction";
import PurchaseAuctionList from "./Auction/PurchaseAuctionList";
import PurchaseList from "./Auction/PurchaseList";
import Emd from "./Bidding/Emd";
import StartBidding from "./Bidding/StartBidding";
import Home from "./Home/Home";
import Login from "./Login/Login";
import Onboarding from "./Onboarding/Onboarding";
import Splash from "./Splash/Splash";
import Tender from "./Tender/Tender";
const Screens = {
Splash,
Login,
Onboarding,
Home,
Auction,
Tender,
PurchaseList,
Emd,
StartBidding,
PurchaseAuctionList
}
export default Screens
+123
View File
@@ -0,0 +1,123 @@
const BASE_URL = `https://procurelinc.in/EProcurementSB/`
// const BASE_URL_Auction = `https://procurelinc.in/AuctionPostgre/api/auction/`
export const BASE_URL_Auction = `https://procurelinc.in/Auction/`
// const BASE_URL = `https://192.168.29.226:8090/EProcurementSB/`
const GET_DESIGNATION = `${BASE_URL}contractor/getAllDesignationContractor`
const SAVE_CONTRACTOR_STAGE1 = `${BASE_URL}contractor/registerContractorStage1`
const GET_CONTRACTOR_CLASS = `${BASE_URL}contractor/allContractorClassSecond`
const VERIFY_ORGPAN = `${BASE_URL}ekycdocverify/companyPanVerification`
const SEND_ADHAROTP = `${BASE_URL}ekycdocverify/aadhaarVerification`
const VERIFY_ADHAR = `${BASE_URL}ekycdocverify/aadhaarOtpVerification`
const VERIFY_GST = `${BASE_URL}ekycdocverify/gstVerification`
const ALLORG = `${BASE_URL}contractor/allOrganization`
const ALLSOCIALCAT = `${BASE_URL}contractor/allSocialCategories`
const ALLMSME = `${BASE_URL}contractor/allMsMeOrg`
const ALLCONTRACTOR = `${BASE_URL}contractor/getAllStatesContractor`
const GETCONTRACTORBYSTATE = `${BASE_URL}contractor/getCitiesContractorByStateId`
const REJ_STAGE_TWO = `${BASE_URL}contractor/indianContractorStage2`
const STAGETHREE = `${BASE_URL}contractor/contractorstage3`
const PARTNER_API_ENDPOINT = `${BASE_URL}contractor/contstage3_Partnership`
const ALL_DESIGNATION = `${BASE_URL}contractor/getAllDesignationContractor`
const ALL_MSME = `${BASE_URL}contractor/allMsMeOrg`
const CONTRACTOR_PROFILE = `${BASE_URL}contractor/getPreviousContractorDetails?regisId=`
const CONTRACTOR_BASIC_PROFILE = `${BASE_URL}contractor/getContractorById?contId=`
const CONTRACTOR_PROFILE_UPDATE = `${BASE_URL}contractor/updateContBasicDetails?id=`
const GET_REGISTRATION_STATE = `${BASE_URL}contractor/getContractorOrgTypeandStatus`
const GET_MASTER_DATA = `${BASE_URL}contractor/masterdata/all`
const LOG_IN_URL = `${BASE_URL_Auction}auction/loginWithoutDecryption`
const LIVE_TENDER = `${BASE_URL}liveTender/getAllLiveTenders`
const LIVE_AUCTION = `${BASE_URL_Auction}api/auction/getLiveAuctionForMobile`
const PROCEED_PURCHASE_AUCTION = `${BASE_URL_Auction}api/auction/proceedToPurchase`
const PURCHASE_AUCTION = `${BASE_URL_Auction}api/auction/purchaseAuction`
const PURCHASE_OFFLINE_AUCTION = `${BASE_URL_Auction}api/auction/purchaseAuctionOffline`
const PURCHASE_TENDER = `https://procurelinc.in/EProcurementSB/tenderPayment/purchaseTender`
const AUCTION_PURCHASE_STATUS = `https://api.godemo.in/Auction/api/auction/transactionStatus?orderNo=`
const GET_TENDER_DETAILS = `${BASE_URL}eTendering/getTenderById?tenderId=`
const GET_DOCID = `${BASE_URL}eTendering/download/docId?docId=`
// https://procurelinc.in/EProcurementSB/eTendering/download/docId?docId=158
// const DOWNLOAD_DOC = `${BASE_URL}eTende ring/download/docId?docId=`
const DOWNLOAD_DOC = `${BASE_URL}eTendering/download/docId?docId=`
const TWO_FACTOR_AUTH = `${BASE_URL}contractor/two-factor-auth?status=`
const AUCTION_LOGIN = `https://api.godemo.in/Auction/auction/loginWithoutDecryption`
const GET_AUCTION_DETAILS = `${BASE_URL_Auction}api/auction/getAuction/id?id=`
const TANDER_PURCHASE_STATUS = `https://procurelinc.in/EProcurementSB/tenderPayment/transactionStatus?orderNo=`
const GET_MY_AUCTION = `${BASE_URL_Auction}api/auction/getMyAuction`
const GET_MY_TENDER = "https://procurelinc.in/EProcurementSB/tenderBidding/getAllPurchasedTendersByConId"
const GET_DOCID_AUCTION = `${BASE_URL_Auction}api/auction/viewDocumentsForAuction?auctionId=`
const DOWNLOAD_DOC_AUCTION = "https://api.godemo.in/Auction/api/auction/downloadDocumentForAuction?documentId="
const FORARD_AUCTION_ITEM = `${BASE_URL_Auction}api/auction/getForwardAuctionItems/auctionNumber`
const AUCTION_ITEM_SUBMIT = `${BASE_URL_Auction}api/auction/addEMDDocumentsItemWise`
const EMD_CHECK = `${BASE_URL_Auction}api/auction/getEMDDocumentsForAuction`
const REVERSE_AUCTION_ITEM = `${BASE_URL_Auction}api/auction/getReverseAuctionItems/auctionNumber`
const FORWARD_AUCTION_ITEM = `${BASE_URL_Auction}api/auction/getForwardAuctionItems/auctionNumber`
const SELECTED_AUCTION_ITEM = `${BASE_URL_Auction}api/auction/getEMDDocumentsForAuction`
const GET_TECHNICAL_DOCUMENTAION = `${BASE_URL_Auction}api/auction/getAllTechnicalDocumentTemplates`
const CHECK_TECHNICAL_DOCUMENTAION = `${BASE_URL_Auction}api/auction/checkContractorAuctionMatch`
const TECHNICAL_VALUE_SUBMIT = `${BASE_URL_Auction}api/auction/saveTechnicalDocumentTemplateValue`
const TECHNICAL_DOCUMENT_SUBMIT = `${BASE_URL_Auction}api/auction/TechnicalDocumentUpload`
const ITEM_DETAIL = `${BASE_URL_Auction}api/auction/home/viewDocuments/inventoryId`
const ITEM_DOCUMENT_DOWNLOAD = `${BASE_URL_Auction}api/auction/home/downloadDocument/documentId`
// Rohit
const DASHBOARD_COUNT = `${BASE_URL_Auction}api/auction/home/dashboard/counts`
export {
// Rohit
DASHBOARD_COUNT,
ITEM_DOCUMENT_DOWNLOAD,
ITEM_DETAIL,
TECHNICAL_DOCUMENT_SUBMIT,
TECHNICAL_VALUE_SUBMIT,
CHECK_TECHNICAL_DOCUMENTAION,
GET_TECHNICAL_DOCUMENTAION,
SELECTED_AUCTION_ITEM,
REVERSE_AUCTION_ITEM,
FORWARD_AUCTION_ITEM,
EMD_CHECK,
AUCTION_ITEM_SUBMIT,
FORARD_AUCTION_ITEM,
DOWNLOAD_DOC_AUCTION,
GET_DOCID_AUCTION,
GET_MY_TENDER,
GET_MY_AUCTION,
TANDER_PURCHASE_STATUS,
GET_TENDER_DETAILS,
GET_DESIGNATION,
SAVE_CONTRACTOR_STAGE1,
LOG_IN_URL,
GET_CONTRACTOR_CLASS,
VERIFY_ORGPAN,
SEND_ADHAROTP,
VERIFY_ADHAR,
VERIFY_GST,
GET_REGISTRATION_STATE,
ALLMSME,
ALLCONTRACTOR,
ALLORG,
ALLSOCIALCAT,
ALL_MSME,
GETCONTRACTORBYSTATE,
REJ_STAGE_TWO,
PARTNER_API_ENDPOINT,
STAGETHREE,
ALL_DESIGNATION,
BASE_URL,
CONTRACTOR_PROFILE,
CONTRACTOR_PROFILE_UPDATE,
LIVE_TENDER,
LIVE_AUCTION,
PROCEED_PURCHASE_AUCTION,
PURCHASE_AUCTION,
AUCTION_PURCHASE_STATUS,
CONTRACTOR_BASIC_PROFILE,
PURCHASE_TENDER,
GET_DOCID,
DOWNLOAD_DOC,
GET_AUCTION_DETAILS,
AUCTION_LOGIN,
TWO_FACTOR_AUTH,
GET_MASTER_DATA,
PURCHASE_OFFLINE_AUCTION
};
+77
View File
@@ -0,0 +1,77 @@
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
import { fetchDashboard, fetchLiveAuction, fetchLiveTender, fetchPurchaseAuction, fetchPurchaseTender, offlineAuctionPurchase, userLogin } from './apis';
// User
export const useLogin = () => {
return useMutation({
mutationFn: (payload: { emailAdd: string, password: string }) => userLogin(payload),
});
};
// Dashboard
export const useDashboard = () => {
return useQuery({
queryKey: ['fetchDashboard'],
queryFn: () => fetchDashboard()
});
};
// Auction & Tender
export const useFetchPurchaseTender = () => {
return useQuery({
queryKey: [],
queryFn: () => fetchPurchaseTender()
});
};
export const useFetchLiveTender = () => {
return useQuery({
queryKey: [],
queryFn: () => fetchLiveTender()
});
};
export const useFetchPurchaseAuction = () => {
return useQuery({
queryKey: ['fetchPurchaseAuction',],
queryFn: () => fetchPurchaseAuction()
});
};
export const useFetchLiveAuction = () => {
return useInfiniteQuery<any>({
queryKey: ['fetchLiveAuction',],
queryFn: ({ pageParam = 1 }) =>
fetchLiveAuction({
page: pageParam,
size: 10,
}),
initialPageParam: 1,
getNextPageParam: (lastPage) => {
const { page, size, totalCount } = lastPage;
const totalPages = Math.ceil(totalCount / size);
const nextPage = page + 1;
return nextPage < totalPages ? nextPage : undefined;
},
select: (data) => {
const mergedResults = data.pages.flatMap((page: any) => page.data ?? []);
return {
...data,
result: mergedResults,
};
},
});
};
// Auction Purchase offline
export const useOfflineAuctionPurchase = () => {
return useMutation({
mutationFn: (payload: any) => offlineAuctionPurchase(payload),
});
};
+47
View File
@@ -0,0 +1,47 @@
import axios from "axios";
import { DASHBOARD_COUNT, GET_MY_AUCTION, GET_MY_TENDER, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION } from "./ApiUrls";
import { apiCall } from "../../Axios/Axios";
// User
export const userLogin = async (payload: { emailAdd: string, password: string }): Promise<any> => {
try {
const response = await axios.post(LOG_IN_URL, { ...payload, divice: "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
}
export const fetchPurchaseTender = async (): Promise<any> => {
const data = await apiCall<any>('get', `${GET_MY_TENDER}`);
return data
}
export const fetchLiveTender = async (): Promise<any> => {
const data = await apiCall<any>('get', `${LIVE_TENDER}`);
return data
}
export const fetchPurchaseAuction = async (): Promise<any> => {
const data = await apiCall<any>('get', `${GET_MY_AUCTION}`);
return data
}
export const fetchLiveAuction = async (payload: any): Promise<any> => {
const data = await apiCall<any>('get', `${LIVE_AUCTION}`, {}, { payload });
return data
}
// Auction Purchase offline
export const offlineAuctionPurchase = async (payload: any): Promise<any> => {
try {
const response = await axios.post(PROCEED_PURCHASE_AUCTION, payload);
return response.data;
} catch (error: any) {
throw error.response?.data || error.message;
}
};
+248
View File
@@ -0,0 +1,248 @@
export interface DeviceDetailItem {
name: string;
value: string;
}
export interface DeviceDetail {
detail: DeviceDetailItem[];
}
export interface CategoryPaylaodType {
mid: string;
secretKey: string;
reqId?: string;
// txnId: string;
clientUserId: string
deviceDetial: DeviceDetail;
udf1: string
udf2: string,
udf3: string,
udf4: string,
udf5: string,
udf6: string,
udf7: string,
udf8: string,
udf9: string,
udf10: string
}
export interface DeviceDetailItem {
name: string;
value: string;
}
export interface DeviceDetail {
detail: DeviceDetailItem[];
}
export interface ServicePayloadType {
mid: string;
secretKey: string;
reqId: string;
deviceDetial: DeviceDetail;
udf1: string;
udf2: string;
udf3: string;
udf4: string;
udf5: string;
udf6: string;
udf7: string;
udf8: string;
udf9: string;
udf10: string;
billerName?: string;
category?: string;
billerId?: string
pageNo?: number;
pageSize?: number;
}
export interface ProviderType {
mid: string;
secretKey: string;
deviceDetial: {
detail: DeviceDetail[];
};
udf1: string;
udf2: string;
udf3: string;
udf4: string;
udf5: string;
udf6: string;
udf7: string;
udf8: string;
udf9: string;
udf10: string;
billerId: string;
billerName?: string; // optional if needed later
}
export interface DeviceDetailItem {
name: string;
value: string;
}
export interface DeviceDetail {
detail: DeviceDetailItem[];
}
export interface DeviceBlockTag {
name: string;
value: string;
}
export interface CustomerParamsRequest {
tags: any[]; // you can replace any[] with your exact param type
}
export interface RemitterIdentity {
name: string;
value: string;
}
export interface BillFetchPayload {
mid: string;
secretKey: string;
reqId?: string; // generated by uniqueIdGenerator
// txnId?: string; // optional if needed
deviceDetial: DeviceDetail;
billerId: string;
billerName: string;
billerCategory: string;
clientReqId: string;
macAdress: string;
customerMobNo: string;
remitterName: string;
paymentChannel: string;
deviceBlockTags: DeviceBlockTag[];
customerParamsRequest: CustomerParamsRequest;
remitterIdentity: RemitterIdentity[];
}
export interface PlansPayload {
mid: string;
secretKey: string;
reqId: string;
deviceDetial: DeviceDetail; // Note: deviceDetial name retained as per JSON
udf1: string;
udf2: string;
udf3: string;
udf4: string;
udf5: string;
udf6: string;
udf7: string;
udf8: string;
udf9: string;
udf10: string;
billerId: string;
category: string;
amountInRupees: string;
circle: string;
page: number;
pagesize: number;
}
export interface BillValidationPayload {
mid: string;
secretKey: string;
deviceDetial: DeviceDetail;
udf1: string;
udf2: string;
udf3: string;
udf4: string;
udf5: string;
udf6: string;
udf7: string;
udf8: string;
udf9: string;
udf10: string;
billerId?: string | number;
billerName?: string;
billerCategory?: string;
customerMobNo: string;
macAddress: string;
customerParamsRequest: CustomerParamsRequest;
}
export interface PaymentRequest {
mid: string | number;
secretKey: string;
clientUserId: string;
deviceDetial: {
detail: {
name: string;
value: string;
}[];
};
udf1?: string;
udf2?: string;
udf3?: string;
udf4?: string;
udf5?: string;
udf6?: string;
udf7?: string;
udf8?: string;
udf9?: string;
udf10?: string;
udf11?: string;
udf12?: string;
udf13?: string;
udf14?: string;
amount: number;
currency: string;
emailId?: string;
customerVpa?: string;
mobileNo: string;
respUrl: string;
txnReqType: 'PAY' | 'REFUND' | 'STATUS';
name: string;
}
export interface PaymentPullbackType {
mid: string | number;
secretKey: string;
clientUserId: string;
deviceDetial: {
detail: {
name: string;
value: string;
}[];
};
udf1?: string;
udf2?: string;
udf3?: string;
udf4?: string;
udf5?: string;
udf6?: string;
udf7?: string;
udf8?: string;
udf9?: string;
udf10?: string;
udf11?: string;
udf12?: string;
udf13?: string;
udf14?: string;
orderId: string,
}
+39
View File
@@ -0,0 +1,39 @@
import {StyleSheet} from 'react-native';
import colors from '../Constant/Color';
let MainStyle = StyleSheet.create({
container:{
backgroundColor:colors.white,
flex:1,
},
containerWithPadding:{
flex:1,
paddingHorizontal:15
},
flexBetween:{
alignItems:"center",
flexDirection:"row",
justifyContent:"space-between"
},
flexCloumn:{
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'center',
},
flexLeft:{
alignItems:"center",
flexDirection:"row",
justifyContent:"flex-start"
},
flexRight:{
alignItems:"center",
flexDirection:"row",
justifyContent:"flex-end"
},
flexRow: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'center',
}
});
export default MainStyle;
+218
View File
@@ -0,0 +1,218 @@
import { StyleSheet } from 'react-native'
import Colors from './Color';
const Styles = StyleSheet.create({
logo: {
marginTop: 20,
alignItems: "center"
},
heading: {
fontWeight: 800,
fontSize: 18,
lineHeight: 29,
textAlign: "center",
color: "#1B4B66"
},
button: {
backgroundColor: Colors.appColourDark,
width: "100%",
paddingVertical: 15,
paddingHorizontal: 28,
borderRadius: 50,
// marginBottom: 87
},
next: {
fontSize: 15,
color: "white",
fontWeight: 700,
textAlign: "center",
letterSpacing: 3
},
input: {
backgroundColor: Colors.black20,
padding: 15,
borderRadius: 10,
marginTop: 10,
fontSize: 15,
color: Colors.appColourDark,
elevation: 2
},
flexDesign: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
gap: 10
},
paddding: {
paddingHorizontal: 5
},
description: {
fontSize: 17,
fontWeight: 500,
width: 290,
fontFamily: "Inter-Bold",
color: Colors.appColourDark,
marginTop: 5
},
title: {
textAlign: "center",
fontSize: 12,
fontWeight: 800,
fontFamily: "quicksand-SemiBold",
color: Colors.appColourDark,
},
Button_2: {
width: "100%",
backgroundColor: Colors.appColourDark,
paddingVertical: 10,
borderRadius: 10
},
Button_2_Text: {
fontFamily: "Inter-Bold",
textAlign: "center",
fontSize: 15,
fontWeight: 800,
lineHeight: 24
},
Button_3: {
backgroundColor: Colors.appColourDark,
width: "70%",
alignItems: "center",
flexDirection: "row",
justifyContent: "center",
paddingVertical: 15,
borderRadius: 8,
gap: 5
},
Button_3_Text: {
fontSize: 14,
fontWeight: 800,
lineHeight: 24,
color: "white"
},
productName: {
fontFamily: "Inter-Bold",
fontSize: 14,
fontWeight: 800,
lineHeight: 16,
color: Colors.black
},
productDiscription: {
fontSize: 13,
fontFamily: "Inter-Bold",
fontWeight: 800,
lineHeight: 16,
color: Colors.black50,
marginTop: 2,
},
date: {
fontFamily: "Inter-Bold",
fontWeight: 800,
fontSize: 13,
lineHeight: 16,
color: Colors.black
},
cost: {
fontFamily: "Inter-Bold",
fontSize: 14,
fontWeight: 700,
lineHeight: 18,
color: Colors.black
},
emptyCartHeading: {
fontFamily: "Inter-Bold",
fontSize: 28,
fontWeight: 800,
lineHeight: 26,
color: Colors.black,
marginTop: 30
},
emptyCartDiscription: {
fontFamily: "Inter-Bold",
fontSize: 15,
fontWeight: 700,
lineHeight: 18,
color: Colors.black,
marginTop: 16,
width: "60%",
textAlign: "center"
},
phone: {
fontSize: 19,
fontWeight: 700,
lineHeight: 24,
color: Colors.appColourDark,
marginTop: 9
},
itemCount: {
fontFamily: "Inter-Bold",
fontSize: 17,
fontWeight: 800,
lineHeight: 19,
color: Colors.black50,
marginBottom: 15
},
qty: {
fontSize: 12,
fontFamily: "Inter-Bold",
fontWeight: 600,
lineHeight: 18,
color: Colors.black50
},
heading_2: {
color: Colors.black,
fontSize: 14,
fontWeight: 800,
lineHeight: 18,
marginBottom: 10
},
text: {
fontWeight: 600,
fontSize: 14,
lineHeight: 25,
color: "#333333",
textAlign: "justify"
},
heading_3: {
color: Colors.black50,
fontSize: 16,
fontWeight: 800,
lineHeight: 18,
marginBottom: 8
},
address: {
fontFamily: "Inter-Bold",
fontSize: 14,
fontWeight: 700,
lineHeight: 18,
color: Colors.black50
},
buttonSize: {
width: 30,
height: 30,
alignItems: 'center'
},
deletePrice: {
fontFamily: "Inter-Bold",
fontSize: 13,
fontWeight: 700,
color: "#626262",
textDecorationLine: 'line-through'
},
off: {
fontFamily: "Inter-Bold",
fontSize: 12,
fontWeight: 700,
color: "#E21D1D",
},
currentPrice: {
fontFamily: "Inter-Bold",
fontSize: 17,
fontWeight: 800,
color: "#171520",
},
})
export default Styles;
+61
View File
@@ -0,0 +1,61 @@
import NavigationString from "../Constant/NavigationString";
export interface RootStackParamList {
[NavigationString.Home]: undefined;
};
export interface CommonColors {
appColourDark: string;
appColourDarkDim: string;
appColourLight: string;
appColourLightDim: string;
appRed: string;
appYellow: string;
appGreen: string;
appGreenDark: string;
appRedDark: string;
white: string;
black: string;
black70: string;
black90: string;
black20: string;
black50: string;
black10: string;
white90: string;
white80: string;
}
export interface Theme extends CommonColors {
theme: 'light' | 'dark';
background: string;
text: string;
subText: string;
inputBg: string;
border: string;
placeholder: string;
buttonBg: string;
buttonText: string;
merchenthomeBg: string;
primaryColor: string;
iconColor: string;
catagoryBg: string;
gray: string;
}
export interface ThemeState {
mode: 'light' | 'dark' | 'system';
theme: Theme;
}
export interface CategoryItemType {
buttonName: string;
categoryDomain: string;
categoryIcon: string; // base64 or URL
categoryId: string;
categoryName: string;
faqDetailsList: any[]; // you can type this later
textArea: string;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More