206 lines
7.1 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import { View, Text, Animated, Easing } from 'react-native';
import { useAppTheme } from '@theme';
import { PrimaryButton, LocationRow } from '@components';
import { useAppDispatch, useAppSelector } from '@store';
import {
respondToOfferThunk,
clearDeliveryOffer,
} from '@store/commonReducers/delivery';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
import { formatCurrency } from '@utils/formatters';
import { getStyles } from './newJobRequestScreen.styles';
const TOTAL_SECONDS = 15;
export const NewJobRequestScreen: React.FC = () => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation =
useNavigation<NativeStackNavigationProp<AppStackParamList>>();
// Read from the socket-driven delivery slice
const currentOffer = useAppSelector(state => state.delivery.currentOffer);
const respondLoading = useAppSelector(
state => state.delivery.respondLoading,
);
const [secondsLeft, setSecondsLeft] = useState(TOTAL_SECONDS);
// Animated values
const pulseAnim = useRef(new Animated.Value(1)).current;
// ── Countdown timer ──────────────────────────────────────────────────────
useEffect(() => {
if (secondsLeft <= 0) {
handleReject();
return;
}
const timer = setInterval(() => {
setSecondsLeft(prev => prev - 1);
}, 1000);
return () => clearInterval(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [secondsLeft]);
// ── Pulse animation on Accept button when < 5 s remain ──────────────────
useEffect(() => {
if (secondsLeft <= 5 && secondsLeft > 0) {
Animated.loop(
Animated.sequence([
Animated.timing(pulseAnim, {
toValue: 1.06,
duration: 400,
easing: Easing.inOut(Easing.ease),
useNativeDriver: true,
}),
Animated.timing(pulseAnim, {
toValue: 1,
duration: 400,
easing: Easing.inOut(Easing.ease),
useNativeDriver: true,
}),
]),
).start();
} else {
pulseAnim.setValue(1);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [secondsLeft <= 5]);
// ── Handlers ─────────────────────────────────────────────────────────────
const handleAccept = () => {
if (!currentOffer) return;
dispatch(
respondToOfferThunk({
deliveryId: currentOffer.deliveryId,
action: 'ACCEPT',
}),
)
.unwrap()
.then(() => {
navigation.navigate(RouteNames.OrderAccepted);
})
.catch(() => {
// stay on screen — respondError will be set in redux
});
};
const handleReject = () => {
if (currentOffer) {
dispatch(
respondToOfferThunk({
deliveryId: currentOffer.deliveryId,
action: 'REJECT',
}),
);
}
dispatch(clearDeliveryOffer());
navigation.navigate(RouteNames.BottomTabs, {
screen: RouteNames.Dashboard,
});
};
if (!currentOffer) return null;
// Timer text color: warning when < 5s
const timerColor = secondsLeft <= 5 ? colors.error : colors.primary;
const timerBorderColor =
secondsLeft <= 5 ? colors.error : colors.primary;
return (
<View style={styles.container}>
<View style={styles.modalContent}>
{/* ── Header Row ─────────────────────────────────────────────────── */}
<View style={styles.headerRow}>
<Text style={styles.modalTitle}>New Delivery Request</Text>
{/* Circular countdown timer (pure RN — no SVG needed) */}
<View
style={[
styles.timerWrapper,
{ borderColor: timerBorderColor },
]}
>
<Text style={[styles.timerText, { color: timerColor }]}>
{secondsLeft}s
</Text>
</View>
</View>
{/* ── Earnings Badge ─────────────────────────────────────────────── */}
<View
style={[
styles.earningsBadge,
{ backgroundColor: colors.primary },
]}
>
<Text style={styles.earningsBadgeLabel}>Estimated Earnings</Text>
<Text style={styles.earningsBadgeValue}>
{formatCurrency(currentOffer.totalAmount)}
</Text>
</View>
{/* ── Route Details ──────────────────────────────────────────────── */}
<View style={styles.routeContainer}>
<LocationRow
type="pickup"
name="Pickup Location"
address={currentOffer.pickupAddress.label}
/>
<View style={styles.spacer} />
<LocationRow
type="drop"
name="Drop Location"
address={currentOffer.dropAddress.label}
/>
</View>
{/* ── Distance & Order ID ────────────────────────────────────────── */}
<View style={styles.detailsRow}>
<View style={styles.detailBox}>
<Text style={styles.detailLabel}>Distance</Text>
<Text style={styles.detailValue}>
{currentOffer.distanceKm} km
</Text>
</View>
<View style={styles.divider} />
<View style={styles.detailBox}>
<Text style={styles.detailLabel}>Order</Text>
<Text style={styles.detailValue}>#{currentOffer.orderId}</Text>
</View>
</View>
{/* ── Action Buttons ─────────────────────────────────────────────── */}
<View style={styles.btnRow}>
<PrimaryButton
title="Reject"
onPress={handleReject}
style={styles.rejectBtn}
textStyle={{ color: colors.text }}
disabled={respondLoading}
/>
<Animated.View
style={[{ flex: 2, transform: [{ scale: pulseAnim }] }]}
>
<PrimaryButton
title={respondLoading ? 'Accepting...' : 'Accept'}
onPress={handleAccept}
style={[styles.acceptBtn, { flex: undefined }]}
disabled={respondLoading}
/>
</Animated.View>
</View>
</View>
</View>
);
};
export default NewJobRequestScreen;