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
@@ -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
}