301 lines
13 KiB
TypeScript
301 lines
13 KiB
TypeScript
import React, { memo, useCallback, useRef, useState } from 'react';
|
||
import { View, Text, TouchableOpacity, StyleSheet, Platform, TextInput, Alert } from 'react-native';
|
||
import Animated, { FadeInUp, Layout, interpolate, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
|
||
import colors_const from '../../../Constant/Color';
|
||
import AuctionItemDetail from '../../Auction/Modal/AuctionItemDetail';
|
||
import MainStyle from '../../../Styles/MainStyle';
|
||
import { useTheme } from '../../../Hooks/useTheme';
|
||
import AppText from '../../../components/AppText';
|
||
import { Loader } from '../../../Component/Index';
|
||
import { useSelector } from 'react-redux';
|
||
import axios from 'axios';
|
||
import Variables from '../../../Constant/Variable';
|
||
import { showErrorAlert, showSuccessAlert } from '../../../Constant/ShowDailog';
|
||
|
||
const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, SelectedAuction, allBoolean, setAllBoolean }: any) => {
|
||
const { colors } = useTheme();
|
||
const styles = React.useMemo(() => makeStyles(colors), [colors]);
|
||
const Token = useSelector((state: any) => state.token.token)
|
||
const [showDetail, setShowDetail] = useState(false);
|
||
const [value, setValue] = useState(`${item.bidAmount}`);
|
||
const [allState, setAllState] = useState({ isLoading: false })
|
||
const isMin = bidValue <= item.bidAmount;
|
||
|
||
// 🔒 Persist expand state safely
|
||
const expandedRef = useRef(false);
|
||
const animation = useSharedValue(0);
|
||
|
||
const toggleExpand = useCallback(() => {
|
||
expandedRef.current = !expandedRef.current;
|
||
animation.value = withSpring(expandedRef.current ? 1 : 0, {
|
||
damping: 15,
|
||
stiffness: 140,
|
||
});
|
||
}, []);
|
||
|
||
const bodyStyle = useAnimatedStyle(() => ({
|
||
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 bodyStyle2 = useAnimatedStyle(() => ({
|
||
height: interpolate(animation.value, [0, 1], [0, 100]),
|
||
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`,
|
||
},
|
||
],
|
||
}));
|
||
const MAX_VALUE = Number(item.bidAmount);
|
||
|
||
const handleChange = (text: string) => {
|
||
let cleaned = text.replace(/[^0-9.]/g, '');
|
||
if ((cleaned.match(/\./g) || []).length > 1) return;
|
||
setValue(cleaned);
|
||
};
|
||
|
||
|
||
const oneTimePlaceBid = async () => {
|
||
try {
|
||
if (Number(value) < MAX_VALUE) {
|
||
showErrorAlert("Enter the greather vlaue to current bid value")
|
||
return false
|
||
}
|
||
setAllState((prev: any) => ({ ...prev, isLoading: true }))
|
||
const payload = {
|
||
itemId: item.id,
|
||
bidAmount: Number(value),
|
||
auctionId: SelectedAuction.auctionId,
|
||
contractorId: Token.contractorId,
|
||
fullName: Token.fullName,
|
||
};
|
||
|
||
|
||
const res = await axios.post(
|
||
`${Variables.socketUrl}bidding/placeBidForAuction`,
|
||
[payload],
|
||
{ headers: { Authentication: `Bearer ${Token.token}` } }
|
||
);
|
||
|
||
if (res.data.statusCode === 200) {
|
||
showSuccessAlert(res.data.message);
|
||
}
|
||
setAllBoolean((prev: any) => ({ ...prev, reload: !prev.reload }))
|
||
} catch (error) {
|
||
if (axios.isAxiosError(error)) {
|
||
showErrorAlert(error.response?.data.message)
|
||
} else {
|
||
console.log('UNKNOWN ERROR:', error);
|
||
}
|
||
|
||
} finally {
|
||
setAllState((prev: any) => ({ ...prev, isLoading: false }))
|
||
}
|
||
}
|
||
|
||
return (
|
||
<View>
|
||
{
|
||
!showDetail ? <Animated.View
|
||
layout={Layout.springify()}
|
||
entering={FadeInUp.delay(100)}
|
||
style={[styles.card, item.me ? styles.leadingCard : undefined, { borderColor: item.me ? colors.success : colors.error, backgroundColor: item.me ? `${colors.success}10` : `${colors.error}10` }]}
|
||
>
|
||
{/* HEADER */}
|
||
<TouchableOpacity
|
||
activeOpacity={0.7}
|
||
onPress={() => { setShowDetail(true) }}
|
||
style={styles.cardHeader}
|
||
>
|
||
<View style={{ flex: 1 }}>
|
||
<AppText variant="h4" color={colors.text} bold style={styles.productName as any}>{item.productName}</AppText>
|
||
{/* auctionStartValueFigure */}
|
||
<AppText variant="caption" color={colors.textSecondary} style={styles.itemIdText as any}>
|
||
Initial Amt : {item?.auctionStartValueFigure}
|
||
</AppText>
|
||
</View>
|
||
|
||
<TouchableOpacity style={styles.headerRight} onPress={toggleExpand}>
|
||
<View style={[MainStyle.flexBetween, { gap: 10 }]}>
|
||
<View style={[styles.badge, item.me ? styles.meBadge : styles.liveBadge]}>
|
||
<AppText variant="caption" bold style={[styles.badgeText as any, item.me ? styles.meText : styles.liveText]}>
|
||
{item.me ? 'LEADING' : 'LIVE'}
|
||
</AppText>
|
||
</View>
|
||
|
||
<Animated.Text style={[styles.arrow, { color: colors.textSecondary }, arrowStyle]}>
|
||
▼
|
||
</Animated.Text>
|
||
</View>
|
||
<AppText variant="caption" color={colors.textSecondary} style={styles.itemIdText as any}>
|
||
{SelectedAuction?.auctionNumber}
|
||
</AppText>
|
||
</TouchableOpacity>
|
||
|
||
</TouchableOpacity>
|
||
|
||
{/* PRICE INFO */}
|
||
<View style={styles.priceRow}>
|
||
<View>
|
||
<AppText variant="caption" color={colors.textSecondary} semiBold style={styles.label as any}>Current Bid</AppText>
|
||
<AppText variant="h2" color={colors.text} bold style={styles.mainPrice as any}>₹{item?.bidAmount || item.auctionStartValueFigure}</AppText>
|
||
</View>
|
||
|
||
<View style={{ alignItems: 'flex-end' }}>
|
||
<AppText variant="caption" color={colors.textSecondary} semiBold style={styles.label as any}>Increment</AppText>
|
||
<AppText variant="body" color={colors.primary} bold style={styles.incrementText as any}>
|
||
+₹{item.bidVariationValue}
|
||
</AppText>
|
||
</View>
|
||
</View>
|
||
|
||
{/* COLLAPSIBLE BODY */}
|
||
<Animated.View style={[styles.collapsibleContainer, (!allBoolean.rebid && item.contractorId == undefined) ? bodyStyle : bodyStyle2]}>
|
||
<View style={styles.divider} />
|
||
|
||
<AppText variant="bodySmall" color={colors.textSecondary} semiBold style={styles.bidLabel as any}>Set Your Bid</AppText>
|
||
|
||
{!allBoolean.oneTimeBid && <View style={styles.stepperContainer}>
|
||
<TouchableOpacity
|
||
onPress={onDecrease}
|
||
disabled={isMin}
|
||
style={[styles.stepBtn, isMin && styles.disabledBtn]}
|
||
>
|
||
<AppText variant="h3" color="#FFF" bold style={styles.stepText as any}>−</AppText>
|
||
</TouchableOpacity>
|
||
|
||
<View style={styles.inputBox}>
|
||
<AppText variant="body" color={colors.textSecondary} bold style={styles.currency as any}>₹</AppText>
|
||
<AppText variant="h4" color={colors.text} bold style={styles.bidValueText as any}>{bidValue}</AppText>
|
||
</View>
|
||
|
||
<TouchableOpacity onPress={onIncrease} style={styles.stepBtn}>
|
||
<AppText variant="h3" color="#FFF" bold style={styles.stepText as any}>+</AppText>
|
||
</TouchableOpacity>
|
||
</View>}
|
||
{
|
||
(allBoolean.oneTimeBid || allBoolean.rebid) && <View style={styles.inputBox2}>
|
||
<AppText variant="body" color={colors.textSecondary} bold style={styles.currency2 as any}>₹</AppText>
|
||
|
||
<TextInput
|
||
value={value}
|
||
onChangeText={handleChange}
|
||
keyboardType="decimal-pad"
|
||
placeholder="0.00"
|
||
style={[styles.input, { color: colors.text }]}
|
||
placeholderTextColor={colors.textSecondary}
|
||
/>
|
||
</View>
|
||
}
|
||
|
||
{(!allBoolean.rebid && item.contractorId == undefined) ? <TouchableOpacity
|
||
style={[styles.submitBtn]}
|
||
onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid}
|
||
>
|
||
{
|
||
allState.isLoading ? <Loader size='small' color="#FFF" /> : <AppText variant="button" color="#FFF" bold style={styles.submitBtnText as any}>Confirm Bid</AppText>
|
||
}
|
||
</TouchableOpacity> : null}
|
||
</Animated.View>
|
||
</Animated.View>
|
||
:
|
||
<AuctionItemDetail data={item} onClose={() => { setShowDetail(false) }} visible={showDetail} />
|
||
}
|
||
|
||
</View>
|
||
)
|
||
};
|
||
|
||
export default memo(BiddingCard);
|
||
|
||
const makeStyles = (colors: any) => StyleSheet.create({
|
||
inputBox2: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
backgroundColor: colors.background,
|
||
borderRadius: 12,
|
||
height: 45,
|
||
paddingHorizontal: 12,
|
||
width: '100%',
|
||
marginBottom: 20
|
||
},
|
||
currency2: {
|
||
marginRight: 4,
|
||
} as any,
|
||
input: {
|
||
flex: 1,
|
||
fontSize: 18,
|
||
fontWeight: '700',
|
||
width: '100%'
|
||
},
|
||
card: {
|
||
borderRadius: 24,
|
||
padding: 20,
|
||
marginBottom: 16,
|
||
borderWidth: 1,
|
||
overflow: 'hidden',
|
||
},
|
||
leadingCard: {
|
||
borderWidth: 2,
|
||
},
|
||
cardHeader: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'flex-start',
|
||
},
|
||
headerRight: { flexDirection: 'column', alignItems: 'center', gap: 8 },
|
||
productName: {} as any,
|
||
itemIdText: { marginTop: 2 } as any,
|
||
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
|
||
liveBadge: { backgroundColor: `${colors.error}20` },
|
||
meBadge: { backgroundColor: `${colors.success}20` },
|
||
badgeText: { fontSize: 10 } as any,
|
||
liveText: { color: colors.error },
|
||
meText: { color: colors.success },
|
||
arrow: { fontSize: 10 },
|
||
priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
|
||
label: {
|
||
textTransform: 'uppercase',
|
||
} as any,
|
||
mainPrice: {} as any,
|
||
incrementText: {} as any,
|
||
collapsibleContainer: { overflow: 'hidden' },
|
||
divider: { height: 1, backgroundColor: colors.border, marginVertical: 10 },
|
||
bidLabel: { marginBottom: 10 } as any,
|
||
stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
|
||
stepBtn: {
|
||
width: 45,
|
||
height: 45,
|
||
backgroundColor: colors.primary,
|
||
borderRadius: 12,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
disabledBtn: { backgroundColor: colors.border },
|
||
stepText: {} as any,
|
||
inputBox: {
|
||
flex: 1,
|
||
height: 45,
|
||
backgroundColor: colors.background,
|
||
borderRadius: 12,
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
currency: { marginRight: 4 } as any,
|
||
bidValueText: {} as any,
|
||
submitBtn: {
|
||
height: 50,
|
||
backgroundColor: colors.primary,
|
||
borderRadius: 15,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
submitBtnText: {} as any,
|
||
});
|