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>(); // 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 ( {/* ── Header Row ─────────────────────────────────────────────────── */} New Delivery Request {/* Circular countdown timer (pure RN — no SVG needed) */} {secondsLeft}s {/* ── Earnings Badge ─────────────────────────────────────────────── */} Estimated Earnings {formatCurrency(currentOffer.totalAmount)} {/* ── Route Details ──────────────────────────────────────────────── */} {/* ── Distance & Order ID ────────────────────────────────────────── */} Distance {currentOffer.distanceKm} km Order #{currentOffer.orderId} {/* ── Action Buttons ─────────────────────────────────────────────── */} ); }; export default NewJobRequestScreen;