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
+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