updated code with dsc and device binding
This commit is contained in:
@@ -1,8 +1,24 @@
|
||||
import React, { use, useEffect, 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 React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
FlatList,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import Animated, { FadeInDown, FadeIn } from 'react-native-reanimated';
|
||||
import {
|
||||
Calendar,
|
||||
Hash,
|
||||
Wifi,
|
||||
WifiOff,
|
||||
Receipt,
|
||||
CheckCircle2,
|
||||
Gavel,
|
||||
Building2,
|
||||
BadgeIndianRupee,
|
||||
} from 'lucide-react-native';
|
||||
import { InsideHeader } from '../../Component/Index';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useOfflineAuctionPurchase, usePurchaseAuctionList } from '../../Services/ApiCall/Hooks';
|
||||
import { showSuccessAlert } from '../../Constant/ShowDailog';
|
||||
@@ -10,284 +26,466 @@ import { clearPurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
||||
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||
import NavigationString from '../../Constant/NavigationString';
|
||||
import Skeleton from '../../Component/Skelton/Skelton';
|
||||
|
||||
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,
|
||||
},
|
||||
];
|
||||
import { useTheme } from '../../Hooks/useTheme';
|
||||
import AppText from '../../components/AppText';
|
||||
|
||||
const PurchaseAuctionList = () => {
|
||||
const Navigation = useNavigation()
|
||||
const Dispatch = useDispatch()
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [list, setList] = useState([]);
|
||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
||||
|
||||
const LoginUser = useSelector((state: any) => state.token.token);
|
||||
const { mutate, isPending } = useOfflineAuctionPurchase()
|
||||
const { mutate: purchaseListMutation, isPending: purchaseListPending } = usePurchaseAuctionList();
|
||||
const Navigation: any = useNavigation();
|
||||
const Dispatch = useDispatch();
|
||||
const { colors, spacing, shadows } = useTheme();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [list, setList] = useState<any[]>([]);
|
||||
|
||||
const calculateTotalAmount = (item: any) => {
|
||||
const gstAmount =
|
||||
(Number(item.auctionFees) + Number(item.portalCharge)) *
|
||||
(Number(item.gst) / 100);
|
||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
||||
const LoginUser = useSelector((state: any) => state.token.token);
|
||||
|
||||
const payableAmount =
|
||||
Number(item.auctionFees) + Number(item.portalCharge) + gstAmount;
|
||||
return payableAmount
|
||||
}
|
||||
const { mutate, isPending } = useOfflineAuctionPurchase();
|
||||
const { mutate: purchaseListMutation, isPending: purchaseListPending } = usePurchaseAuctionList();
|
||||
|
||||
const handleOfflinePurchase = async () => {
|
||||
const body = {
|
||||
auctionDetails: list.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: ''
|
||||
};
|
||||
const calculateTotalAmount = (item: any) => {
|
||||
const gstAmount =
|
||||
(Number(item.auctionFees) + Number(item.portalCharge)) *
|
||||
(Number(item.gst) / 100);
|
||||
return Number(item.auctionFees) + Number(item.portalCharge) + gstAmount;
|
||||
};
|
||||
|
||||
try {
|
||||
mutate(body, {
|
||||
onSuccess: (res) => {
|
||||
if (res.statusCode == 200) {
|
||||
showSuccessAlert(res.message)
|
||||
Dispatch(clearPurchaseAuction())
|
||||
Navigation.dispatch(
|
||||
CommonActions.reset({
|
||||
index: 0,
|
||||
routes: [{ name: NavigationString.Home }],
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(error, "purchse Error");
|
||||
}
|
||||
})
|
||||
const grandTotal = list.reduce(
|
||||
(sum: number, item: any) => sum + calculateTotalAmount(item),
|
||||
0,
|
||||
);
|
||||
|
||||
} 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 handleOfflinePurchase = async () => {
|
||||
const body = {
|
||||
auctionDetails: list.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: '',
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
|
||||
const getPurchaseList = () => {
|
||||
const auctionNumbers: string[] = SelectedPurchaseList.map(
|
||||
(item: { auctionNumber: string }) => item.auctionNumber
|
||||
);
|
||||
purchaseListMutation({ auctionNumbers, contractorId: LoginUser.contractorId }, {
|
||||
onSuccess: (res) => {
|
||||
console.log("purchaseListMutation",res)
|
||||
if (res.statusCode == 200) {
|
||||
|
||||
setList(res.data)
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(error, "purchase list error");
|
||||
}
|
||||
})
|
||||
try {
|
||||
mutate(body, {
|
||||
onSuccess: res => {
|
||||
if (res.statusCode === 200) {
|
||||
showSuccessAlert(res.message);
|
||||
Dispatch(clearPurchaseAuction());
|
||||
Navigation.dispatch(
|
||||
CommonActions.reset({ index: 0, routes: [{ name: NavigationString.Home }] }),
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: error => console.log(error, 'purchase Error'),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('purchase offline error', error);
|
||||
}
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
getPurchaseList()
|
||||
}, [SelectedPurchaseList])
|
||||
};
|
||||
|
||||
const handlePurchase = (type: 'online' | 'offline') => {
|
||||
setLoading(true);
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
type === 'online' ? undefined : handleOfflinePurchase();
|
||||
}, 1200);
|
||||
};
|
||||
|
||||
const getPurchaseList = () => {
|
||||
const auctionNumbers: string[] = SelectedPurchaseList.map(
|
||||
(item: { auctionNumber: string }) => item.auctionNumber,
|
||||
);
|
||||
purchaseListMutation(
|
||||
{ auctionNumbers, contractorId: LoginUser.contractorId },
|
||||
{
|
||||
onSuccess: res => {
|
||||
if (res.statusCode === 200) setList(res.data);
|
||||
},
|
||||
onError: error => console.log(error, 'purchase list error'),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
getPurchaseList();
|
||||
}, [SelectedPurchaseList]),
|
||||
);
|
||||
|
||||
const styles = makeStyles(colors, spacing, shadows);
|
||||
|
||||
/* ── Render Item ── */
|
||||
const renderItem = ({ item, index }: { item: any; index: number }) => {
|
||||
const total = calculateTotalAmount(item);
|
||||
const gstAmount =
|
||||
(Number(item.auctionFees) + Number(item.portalCharge)) *
|
||||
(Number(item.gst) / 100);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: colors.white }}>
|
||||
<InsideHeader title="Purchase List" showArrow />
|
||||
<View style={styles.screen}>
|
||||
{purchaseListPending ? <Skeleton /> : <FlatList
|
||||
data={list}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item) => item.auctionId.toString()}
|
||||
contentContainerStyle={styles.container}
|
||||
/>}
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(index * 90).springify()}
|
||||
style={styles.card}
|
||||
>
|
||||
{/* Top accent stripe */}
|
||||
<View style={styles.cardAccent} />
|
||||
|
||||
<View style={styles.cardBody}>
|
||||
{/* Header */}
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={[styles.indexBadge, { backgroundColor: colors.primary }]}>
|
||||
<AppText variant="caption" color={colors.buttonText} style={{ fontWeight: '800' } as any}>
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</AppText>
|
||||
</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')}
|
||||
>
|
||||
{
|
||||
isPending ? <Loader size='small' color={colors.white} /> : <>
|
||||
<Text style={styles.buttonText}>Purchase Offline</Text>
|
||||
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
|
||||
</>
|
||||
}
|
||||
|
||||
</TouchableOpacity>
|
||||
<View style={{ flex: 1, marginLeft: spacing.s }}>
|
||||
<AppText variant="h4" color={colors.text} numberOfLines={2}>
|
||||
{item.auctionNumber}
|
||||
</AppText>
|
||||
{item.auctionPattern ? (
|
||||
<View style={[styles.patternBadge, { backgroundColor: `${colors.primary}15` }]}>
|
||||
<Gavel size={11} color={colors.primary} strokeWidth={2} />
|
||||
<AppText variant="caption" color={colors.primary} style={{ fontWeight: '600' } as any}>
|
||||
{item.auctionPattern}
|
||||
</AppText>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.divider} />
|
||||
|
||||
{/* Info Rows */}
|
||||
<InfoRow
|
||||
icon={<Hash size={15} color={colors.primary} />}
|
||||
label="Dispatcher No"
|
||||
value={item.auctionDispatcherNumber ?? '—'}
|
||||
colors={colors}
|
||||
/>
|
||||
<InfoRow
|
||||
icon={<Calendar size={15} color={colors.primary} />}
|
||||
label="Dispatch Date"
|
||||
value={item.auctionDispatchDate ?? '—'}
|
||||
colors={colors}
|
||||
/>
|
||||
|
||||
<View style={[styles.divider, { marginVertical: spacing.s }]} />
|
||||
|
||||
{/* Fee Breakdown */}
|
||||
<View style={[styles.feeSection, { backgroundColor: colors.overlay }]}>
|
||||
<AppText variant="caption" color={colors.textSecondary} style={styles.feeHeader as any}>
|
||||
FEE BREAKDOWN
|
||||
</AppText>
|
||||
<FeeRow label="Auction Fees" value={`₹ ${Number(item.auctionFees).toFixed(2)}`} colors={colors} />
|
||||
<FeeRow label={`GST (${item.gst}%)`} value={`₹ ${gstAmount.toFixed(2)}`} colors={colors} />
|
||||
<FeeRow label="Portal Charge" value={`₹ ${Number(item.portalCharge).toFixed(2)}`} colors={colors} />
|
||||
</View>
|
||||
|
||||
{/* Total Banner */}
|
||||
<View style={[styles.totalBanner, { backgroundColor: colors.primary }]}>
|
||||
<View style={styles.totalLeft}>
|
||||
<Receipt size={18} color={colors.buttonText} />
|
||||
<AppText variant="bodySmall" color={colors.buttonText} style={{ fontWeight: '600' } as any}>
|
||||
Total Payable
|
||||
</AppText>
|
||||
</View>
|
||||
<AppText variant="h4" color={colors.buttonText}>
|
||||
₹ {total.toFixed(2)}
|
||||
</AppText>
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.screen}>
|
||||
<InsideHeader title="Auction Purchase" showArrow />
|
||||
|
||||
{/* Grand Total Strip */}
|
||||
<Animated.View entering={FadeInDown.springify()} style={styles.grandTotalStrip}>
|
||||
<View>
|
||||
<AppText variant="caption" color={colors.textSecondary}>
|
||||
{list.length} auction{list.length !== 1 ? 's' : ''}
|
||||
</AppText>
|
||||
<AppText variant="h3" color={colors.text}>
|
||||
₹ {grandTotal.toFixed(2)}
|
||||
</AppText>
|
||||
</View>
|
||||
<View style={[styles.grandTotalRight, { backgroundColor: `${colors.success}15` }]}>
|
||||
<CheckCircle2 size={18} color={colors.success} />
|
||||
<AppText variant="caption" color={colors.success} style={{ fontWeight: '600' } as any}>
|
||||
Ready to pay
|
||||
</AppText>
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
{/* List */}
|
||||
{purchaseListPending ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
<FlatList
|
||||
data={list}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={item => item.auctionId?.toString()}
|
||||
contentContainerStyle={styles.container}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Bottom Buttons */}
|
||||
<View style={styles.footer}>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.onlineButton, { borderColor: colors.primary }]}
|
||||
activeOpacity={0.85}
|
||||
onPress={() => handlePurchase('online')}
|
||||
>
|
||||
<Wifi size={18} color={colors.primary} />
|
||||
<AppText variant="button" color={colors.primary} style={styles.btnText as any}>
|
||||
Online
|
||||
</AppText>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.offlineButton, { backgroundColor: colors.buttonPrimary }]}
|
||||
activeOpacity={0.85}
|
||||
onPress={() => handlePurchase('offline')}
|
||||
>
|
||||
{isPending ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
) : (
|
||||
<>
|
||||
<WifiOff size={18} color={colors.buttonText} />
|
||||
<AppText variant="button" color={colors.buttonText} style={styles.btnText as any}>
|
||||
Offline
|
||||
</AppText>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Loading Overlay */}
|
||||
{(loading || isPending) && (
|
||||
<Animated.View entering={FadeIn.duration(200)} style={styles.loadingOverlay}>
|
||||
<View style={[styles.loadingCard, { backgroundColor: colors.surface }]}>
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
<AppText variant="bodySmall" color={colors.textSecondary} style={{ marginTop: spacing.s } as any}>
|
||||
Processing purchase…
|
||||
</AppText>
|
||||
</View>
|
||||
</Animated.View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default PurchaseAuctionList;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
/* ─── Sub-components ─── */
|
||||
|
||||
const InfoRow = ({
|
||||
icon,
|
||||
label,
|
||||
value,
|
||||
colors,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
value: string;
|
||||
colors: any;
|
||||
}) => (
|
||||
<View style={infoRowStyles.row}>
|
||||
<View style={infoRowStyles.iconWrap}>{icon}</View>
|
||||
<AppText variant="caption" color={colors.textSecondary} style={infoRowStyles.label as any}>
|
||||
{label}
|
||||
</AppText>
|
||||
<AppText variant="bodySmall" color={colors.text} style={infoRowStyles.value as any}>
|
||||
{value}
|
||||
</AppText>
|
||||
</View>
|
||||
);
|
||||
|
||||
const infoRowStyles = StyleSheet.create({
|
||||
row: { flexDirection: 'row', alignItems: 'center', paddingVertical: 5, gap: 6 },
|
||||
iconWrap: { width: 22, alignItems: 'center' },
|
||||
label: { flex: 1, fontWeight: '500' } as any,
|
||||
value: { fontWeight: '600' } as any,
|
||||
});
|
||||
|
||||
const FeeRow = ({
|
||||
label,
|
||||
value,
|
||||
colors,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
colors: any;
|
||||
}) => (
|
||||
<View style={feeRowStyles.row}>
|
||||
<AppText variant="caption" color={colors.textSecondary}>{label}</AppText>
|
||||
<AppText variant="caption" color={colors.text} style={{ fontWeight: '600' } as any}>{value}</AppText>
|
||||
</View>
|
||||
);
|
||||
|
||||
const feeRowStyles = StyleSheet.create({
|
||||
row: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 3 },
|
||||
});
|
||||
|
||||
/* ─── Styles factory ─── */
|
||||
const makeStyles = (colors: any, spacing: any, shadows: any) =>
|
||||
StyleSheet.create({
|
||||
screen: {
|
||||
flex: 1,
|
||||
flex: 1,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
/* Grand total strip */
|
||||
grandTotalStrip: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: colors.surface,
|
||||
marginHorizontal: spacing.m,
|
||||
marginTop: spacing.m,
|
||||
marginBottom: spacing.s,
|
||||
paddingHorizontal: spacing.m,
|
||||
paddingVertical: spacing.m,
|
||||
borderRadius: 18,
|
||||
...shadows.medium,
|
||||
},
|
||||
grandTotalRight: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 5,
|
||||
paddingHorizontal: spacing.s + 2,
|
||||
paddingVertical: spacing.xs + 2,
|
||||
borderRadius: 20,
|
||||
},
|
||||
|
||||
container: {
|
||||
padding: 16,
|
||||
paddingBottom: 120,
|
||||
paddingHorizontal: spacing.m,
|
||||
paddingTop: spacing.s,
|
||||
paddingBottom: 120,
|
||||
},
|
||||
|
||||
/* Card */
|
||||
card: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
marginBottom: 16,
|
||||
elevation: 5,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.12,
|
||||
shadowOffset: { width: 0, height: 3 },
|
||||
shadowRadius: 8,
|
||||
backgroundColor: colors.surface,
|
||||
borderRadius: 20,
|
||||
marginBottom: spacing.m,
|
||||
overflow: 'hidden',
|
||||
...shadows.medium,
|
||||
},
|
||||
cardAccent: {
|
||||
height: 4,
|
||||
backgroundColor: colors.primary,
|
||||
},
|
||||
cardBody: {
|
||||
padding: spacing.m,
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 12,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: spacing.s,
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#0F172A',
|
||||
indexBadge: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 10,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: 2,
|
||||
},
|
||||
pattern: {
|
||||
fontSize: 14,
|
||||
color: colors.primary,
|
||||
fontWeight: '600',
|
||||
patternBadge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
alignSelf: 'flex-start',
|
||||
paddingHorizontal: spacing.s,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 8,
|
||||
marginTop: 4,
|
||||
},
|
||||
cardRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
marginBottom: 4,
|
||||
divider: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: colors.border,
|
||||
marginVertical: spacing.s,
|
||||
},
|
||||
text: {
|
||||
fontSize: 14,
|
||||
color: '#555',
|
||||
feeSection: {
|
||||
borderRadius: 12,
|
||||
padding: spacing.s + 2,
|
||||
marginBottom: spacing.s,
|
||||
},
|
||||
totalRow: {
|
||||
marginTop: 10,
|
||||
backgroundColor: colors.primary,
|
||||
borderRadius: 12,
|
||||
paddingVertical: 10,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
feeHeader: {
|
||||
letterSpacing: 1.2,
|
||||
marginBottom: spacing.xs + 2,
|
||||
fontWeight: '700',
|
||||
} as any,
|
||||
totalBanner: {
|
||||
borderRadius: 14,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: spacing.m,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
total: {
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
color: '#fff',
|
||||
totalLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: spacing.s,
|
||||
},
|
||||
bottomButtonContainer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: colors.white,
|
||||
width: '100%',
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 10,
|
||||
elevation: 5
|
||||
|
||||
/* Footer */
|
||||
footer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: spacing.m,
|
||||
paddingTop: spacing.s + 4,
|
||||
paddingBottom: spacing.m + 4,
|
||||
backgroundColor: colors.surface,
|
||||
gap: spacing.s,
|
||||
...shadows.strong,
|
||||
},
|
||||
button: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.primary,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 14,
|
||||
borderRadius: 16,
|
||||
gap: spacing.s,
|
||||
},
|
||||
buttonText: {
|
||||
color: '#fff',
|
||||
fontWeight: '700',
|
||||
fontSize: 14,
|
||||
onlineButton: {
|
||||
backgroundColor: colors.surface,
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
offlineButton: {
|
||||
...shadows.medium,
|
||||
},
|
||||
btnText: {
|
||||
letterSpacing: 1.2,
|
||||
} as any,
|
||||
|
||||
/* Loading overlay */
|
||||
loadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0,0,0,0.2)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 10,
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0,0,0,0.3)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 10,
|
||||
},
|
||||
});
|
||||
loadingCard: {
|
||||
borderRadius: 20,
|
||||
padding: spacing.l,
|
||||
alignItems: 'center',
|
||||
...shadows.strong,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user