Fix form keyboard issues and sticky footers in agent screens
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
|
||||
import { useTheme } from '../../../theme';
|
||||
import type { CardPhysicalDelivery } from '../../../types/app';
|
||||
import { formatDate } from '../../../utils/formatters';
|
||||
import {
|
||||
getCardDeliveryProgressIndex,
|
||||
getCardDeliveryStatusLabel,
|
||||
} from '../utils/cardCopy';
|
||||
|
||||
interface CardDeliveryStatusProps {
|
||||
delivery: CardPhysicalDelivery;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
type DeliveryStep = {
|
||||
id: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const CardDeliveryStatusComponent = ({
|
||||
delivery,
|
||||
compact = false,
|
||||
}: CardDeliveryStatusProps) => {
|
||||
const theme = useTheme();
|
||||
const statusLabel = getCardDeliveryStatusLabel(delivery);
|
||||
const progressIndex = getCardDeliveryProgressIndex(delivery);
|
||||
|
||||
const steps = useMemo<DeliveryStep[]>(
|
||||
() =>
|
||||
delivery.mode === 'home'
|
||||
? [
|
||||
{ id: 'processing', label: 'Requested' },
|
||||
{ id: 'personalizing', label: 'Prepared' },
|
||||
{ id: 'shipped', label: 'In transit' },
|
||||
{ id: 'delivered', label: 'Delivered' },
|
||||
]
|
||||
: [
|
||||
{ id: 'processing', label: 'Requested' },
|
||||
{ id: 'personalizing', label: 'Prepared' },
|
||||
{ id: 'ready-for-pickup', label: 'At branch' },
|
||||
{ id: 'delivered', label: 'Collected' },
|
||||
],
|
||||
[delivery.mode],
|
||||
);
|
||||
|
||||
const accentColor =
|
||||
delivery.status === 'delivered'
|
||||
? theme.colors.accentStrong
|
||||
: delivery.status === 'ready-for-pickup'
|
||||
? theme.colors.warning
|
||||
: theme.colors.primary;
|
||||
const accentBackground =
|
||||
delivery.status === 'delivered'
|
||||
? `${theme.colors.accent}12`
|
||||
: delivery.status === 'ready-for-pickup'
|
||||
? `${theme.colors.warning}14`
|
||||
: `${theme.colors.primary}12`;
|
||||
const modeLabel = delivery.mode === 'home' ? 'Home delivery' : 'Branch pickup';
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.headerCopy}>
|
||||
<Text style={[styles.eyebrow, { color: theme.colors.textSecondary }]}>
|
||||
Physical card delivery
|
||||
</Text>
|
||||
<Text style={[styles.title, { color: theme.colors.textPrimary }]}>{statusLabel}</Text>
|
||||
<Text style={[styles.subtitle, { color: theme.colors.textSecondary }]}>
|
||||
{delivery.status === 'delivered'
|
||||
? `Completed on ${formatDate(delivery.lastUpdatedAt)}`
|
||||
: `Expected by ${formatDate(delivery.estimatedDate)}`}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.badge, { backgroundColor: accentBackground }]}>
|
||||
<Ionicons name="cube-outline" size={14} color={accentColor} />
|
||||
<Text style={[styles.badgeText, { color: accentColor }]}>{modeLabel}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.metricsRow}>
|
||||
<View style={[styles.metricTile, { backgroundColor: theme.colors.surfaceMuted }]}>
|
||||
<Text style={[styles.metricLabel, { color: theme.colors.textSecondary }]}>
|
||||
Tracking ID
|
||||
</Text>
|
||||
<Text style={[styles.metricValue, { color: theme.colors.textPrimary }]}>
|
||||
{delivery.trackingId}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.metricTile, { backgroundColor: theme.colors.surfaceMuted }]}>
|
||||
<Text style={[styles.metricLabel, { color: theme.colors.textSecondary }]}>
|
||||
Destination
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={compact ? 1 : 2}
|
||||
style={[styles.metricValue, { color: theme.colors.textPrimary }]}
|
||||
>
|
||||
{delivery.destinationLabel}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.timelineRow}>
|
||||
{steps.map((step, index) => {
|
||||
const isCompleted = index < progressIndex;
|
||||
const isCurrent = index === progressIndex;
|
||||
const activeColor = isCompleted || isCurrent ? accentColor : theme.colors.cardBorder;
|
||||
const timelineDotStyle = {
|
||||
backgroundColor: isCompleted || isCurrent ? activeColor : 'transparent',
|
||||
borderColor: activeColor,
|
||||
};
|
||||
const timelineLineStyle = {
|
||||
backgroundColor: index < progressIndex ? accentColor : theme.colors.cardBorder,
|
||||
};
|
||||
|
||||
return (
|
||||
<View key={step.id} style={styles.timelineItem}>
|
||||
<View style={styles.timelineTopRow}>
|
||||
<View style={[styles.timelineDot, timelineDotStyle]}>
|
||||
{isCompleted ? (
|
||||
<Ionicons name="checkmark" size={10} color={theme.colors.white} />
|
||||
) : null}
|
||||
</View>
|
||||
{index !== steps.length - 1 ? (
|
||||
<View style={[styles.timelineLine, timelineLineStyle]} />
|
||||
) : null}
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
styles.timelineLabel,
|
||||
{
|
||||
color:
|
||||
isCompleted || isCurrent
|
||||
? theme.colors.textPrimary
|
||||
: theme.colors.textMuted,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{step.label}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{!compact ? (
|
||||
<View style={[styles.footerCard, { backgroundColor: theme.colors.surfaceMuted }]}>
|
||||
<View style={styles.footerRow}>
|
||||
<Text style={[styles.footerLabel, { color: theme.colors.textSecondary }]}>
|
||||
Requested
|
||||
</Text>
|
||||
<Text style={[styles.footerValue, { color: theme.colors.textPrimary }]}>
|
||||
{formatDate(delivery.requestedAt)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.footerRow}>
|
||||
<Text style={[styles.footerLabel, { color: theme.colors.textSecondary }]}>
|
||||
Last updated
|
||||
</Text>
|
||||
<Text style={[styles.footerValue, { color: theme.colors.textPrimary }]}>
|
||||
{formatDate(delivery.lastUpdatedAt)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
gap: 14,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
gap: 12,
|
||||
},
|
||||
headerCopy: {
|
||||
flex: 1,
|
||||
gap: 4,
|
||||
},
|
||||
eyebrow: {
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.8,
|
||||
},
|
||||
title: {
|
||||
fontSize: 17,
|
||||
fontWeight: '800',
|
||||
letterSpacing: -0.2,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
},
|
||||
badge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
borderRadius: 999,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
badgeText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
},
|
||||
metricsRow: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
metricTile: {
|
||||
flex: 1,
|
||||
borderRadius: 18,
|
||||
padding: 14,
|
||||
gap: 6,
|
||||
},
|
||||
metricLabel: {
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.7,
|
||||
},
|
||||
metricValue: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
},
|
||||
timelineRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
timelineItem: {
|
||||
flex: 1,
|
||||
gap: 8,
|
||||
},
|
||||
timelineTopRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
timelineDot: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
timelineLine: {
|
||||
flex: 1,
|
||||
height: 2,
|
||||
marginHorizontal: 6,
|
||||
},
|
||||
timelineLabel: {
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
lineHeight: 15,
|
||||
},
|
||||
footerCard: {
|
||||
borderRadius: 18,
|
||||
padding: 14,
|
||||
gap: 10,
|
||||
},
|
||||
footerRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
gap: 12,
|
||||
},
|
||||
footerLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
footerValue: {
|
||||
fontSize: 12,
|
||||
fontWeight: '800',
|
||||
},
|
||||
});
|
||||
|
||||
export const CardDeliveryStatus = memo(CardDeliveryStatusComponent);
|
||||
Reference in New Issue
Block a user