import React, { useEffect, useState, useRef } from 'react' import { View, Text, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator, Alert, } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import { io, Socket } from 'socket.io-client' import AsyncStorage from '@react-native-async-storage/async-storage' import { api } from '../lib/api' interface AvailableOrder { id: string order_number: number restaurant: { name: string; address: string } delivery_address: string delivery_fee: number tip_amount: number distance_km: number duration_minutes: number item_count: number } export default function OrdersScreen() { const [available, setAvailable] = useState([]) const [activeOrder, setActiveOrder] = useState(null) const [connected, setConnected] = useState(false) const [accepting, setAccepting] = useState(null) const socketRef = useRef(null) useEffect(() => { connectSocket() return () => { socketRef.current?.disconnect() } }, []) const connectSocket = async () => { const token = await AsyncStorage.getItem('vibe_token') const socket = io(`${process.env.EXPO_PUBLIC_WS_URL}/tracking`, { auth: { token }, transports: ['websocket'], }) socketRef.current = socket socket.on('connect', () => { setConnected(true) socket.emit('join:zone', 'downtown-toronto') }) socket.on('disconnect', () => setConnected(false)) socket.on('order:available', (order: AvailableOrder) => { setAvailable((prev) => prev.some((o) => o.id === order.id) ? prev : [order, ...prev]) }) socket.on('order:taken', ({ orderId }: { orderId: string }) => { setAvailable((prev) => prev.filter((o) => o.id !== orderId)) }) socket.on('delivery:assigned', (order: any) => { setActiveOrder(order) setAvailable([]) }) } const acceptOrder = async (orderId: string) => { setAccepting(orderId) try { const { data } = await api.patch(`/orders/${orderId}/assign-driver`) setActiveOrder(data) setAvailable([]) } catch (err: any) { setAvailable((prev) => prev.filter((o) => o.id !== orderId)) Alert.alert('Order unavailable', err.response?.data?.message || 'This order was taken by another driver.') } finally { setAccepting(null) } } const markPickedUp = () => { Alert.alert('Confirm pickup', 'Have you picked up the order from the restaurant?', [ { text: 'Not yet', style: 'cancel' }, { text: 'Yes, picked up', onPress: async () => { await api.patch(`/orders/${activeOrder.id}/pickup`) setActiveOrder((o: any) => ({ ...o, status: 'picked_up' })) }, }, ]) } const markDelivered = () => { Alert.alert('Confirm delivery', 'Has the customer received their order?', [ { text: 'Not yet', style: 'cancel' }, { text: 'Yes, delivered!', onPress: async () => { await api.patch(`/orders/${activeOrder.id}/delivered`) setActiveOrder(null) }, }, ]) } if (activeOrder) { const earnings = Number(activeOrder.delivery_fee) + Number(activeOrder.tip_amount) return ( {activeOrder.status === 'driver_assigned' ? '🏪 Go to Restaurant' : '🏠 Deliver to Customer'} Order #{activeOrder.order_number} {activeOrder.status === 'driver_assigned' && ( Pickup from {activeOrder.restaurant?.name} {activeOrder.restaurant?.address} )} Deliver to {activeOrder.delivery_address} This delivery earns you ${earnings.toFixed(2)} {activeOrder.tip_amount > 0 && ( incl. ${Number(activeOrder.tip_amount).toFixed(2)} tip (100% yours) )} {activeOrder.status === 'driver_assigned' ? ( I've Picked It Up → ) : ( Mark as Delivered ✓ )} ) } return ( {connected ? 'Connected — watching for orders' : 'Connecting...'} item.id} contentContainerStyle={styles.list} ListEmptyComponent={ 📡 Waiting for orders... New orders in your zone appear here instantly } renderItem={({ item }) => ( ${(item.delivery_fee + item.tip_amount).toFixed(2)} {item.tip_amount > 0 && ( +${item.tip_amount.toFixed(2)} tip )} {item.distance_km?.toFixed(1)} km ~{item.duration_minutes} min 📍 Pickup {item.restaurant.name} {item.restaurant.address} 🏠 Deliver to {item.delivery_address} acceptOrder(item.id)} disabled={accepting === item.id} > {accepting === item.id ? ( ) : ( Accept → )} )} /> ) } const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: '#0F172A' }, header: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 16, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#1E293B', }, dot: { width: 8, height: 8, borderRadius: 4, backgroundColor: '#475569' }, dotGreen: { backgroundColor: '#22C55E' }, headerText: { color: '#94A3B8', fontSize: 13 }, list: { padding: 16, gap: 12 }, empty: { alignItems: 'center', paddingTop: 80 }, emptyIcon: { fontSize: 48, marginBottom: 12 }, emptyTitle: { color: '#fff', fontSize: 18, fontWeight: '700', marginBottom: 6 }, emptySub: { color: '#64748B', fontSize: 14, textAlign: 'center' }, orderCard: { backgroundColor: '#1E293B', borderRadius: 16, padding: 16 }, orderTop: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 12 }, orderEarnings: { color: '#22C55E', fontSize: 28, fontWeight: '800' }, orderTip: { color: '#22C55E', fontSize: 12, flex: 1 }, orderMeta: { alignItems: 'flex-end' }, orderMetaText: { color: '#64748B', fontSize: 12 }, route: { gap: 0, marginBottom: 14 }, routeRow: { flexDirection: 'row', gap: 10, alignItems: 'flex-start' }, routeIcon: { fontSize: 16 }, routeLabel: { color: '#64748B', fontSize: 11 }, routeMain: { color: '#fff', fontSize: 14, fontWeight: '600' }, routeSub: { color: '#94A3B8', fontSize: 12 }, acceptBtn: { backgroundColor: '#22C55E', borderRadius: 12, padding: 14, alignItems: 'center' }, acceptBtnDisabled: { opacity: 0.5 }, acceptBtnText: { color: '#fff', fontWeight: '700', fontSize: 15 }, // Active order activeOrder: { flex: 1, padding: 16, gap: 16 }, statusBadge: { backgroundColor: '#1E3A5F', borderRadius: 10, padding: 10, alignItems: 'center' }, statusBadgeAmber: { backgroundColor: '#451A03' }, statusText: { color: '#fff', fontWeight: '700', fontSize: 15 }, orderNum: { color: '#64748B', fontSize: 13 }, section: { backgroundColor: '#1E293B', borderRadius: 12, padding: 14 }, sectionLabel: { color: '#64748B', fontSize: 11, marginBottom: 4 }, sectionTitle: { color: '#fff', fontSize: 16, fontWeight: '600' }, sectionSub: { color: '#94A3B8', fontSize: 13, marginTop: 2 }, earningsBox: { backgroundColor: '#14532D', borderRadius: 12, padding: 16, alignItems: 'center' }, earningsLabel: { color: '#86EFAC', fontSize: 13 }, earningsAmount: { color: '#22C55E', fontSize: 40, fontWeight: '800', marginTop: 4 }, tipLine: { color: '#86EFAC', fontSize: 12, marginTop: 4 }, btnAmber: { backgroundColor: '#D97706', borderRadius: 14, padding: 16, alignItems: 'center' }, btnGreen: { backgroundColor: '#15803D', borderRadius: 14, padding: 16, alignItems: 'center' }, btnText: { color: '#fff', fontWeight: '700', fontSize: 16 }, })