sg-delivery-partner/implementation_plan_for_socket

234 lines
9.6 KiB
Plaintext

# Delivery Socket Integration — Implementation Plan
## Overview
This plan covers the end-to-end integration of the real-time delivery dispatch flow, as described in `mobile_delivery_app_integration.md`. The work involves:
1. **Interfaces** — Add typed contracts for the `delivery_offer` socket event and the REST respond endpoint.
2. **API Layer** — Implement `deliveryApi.ts` with `respondToOffer` and `getPendingOffer` calls.
3. **Redux (delivery slice)** — Wire thunks + reducer in `app/store/commonReducers/delivery/` for the respond flow.
4. **SocketService** — Implement real socket.io connection in `socketService.ts` (replacing the empty file), initialized globally.
5. **Global socket bootstrap** — Initialise/teardown the socket in `App.tsx` (or root navigator) so every screen benefits.
6. **NewJobRequestScreen** — Connect to Redux delivery state, replace current `job.newJobOffer` source with socket-driven data, and enhance UI.
7. **DashboardScreen** — Remove the mock 12-s timer and wire real socket online/offline control.
---
## User Review Required
> [!IMPORTANT]
> The integration guide specifies the socket event name as `delivery_offer` and the action values `"ACCEPT"` / `"REJECT"`. The existing `SocketEvents` enum uses `new_job`. The plan aligns with the integration guide naming while keeping the old mock service unchanged.
> [!WARNING]
> `socket.io-client` must already be installed. If not, run: `npm install socket.io-client`. Please confirm before executing.
> [!IMPORTANT]
> The base URL for the socket is the same as the REST API (`https://8415-202-8-116-13.ngrok-free.app`). Confirm this is correct for the socket server too.
---
## Open Questions
> [!NOTE]
> 1. **Timer duration** — The integration guide shows 15 s timer on the screen. The `newJobRequestScreen` already uses 15 s. Keep it or change to 30 s (constant `JOB_ACCEPT_TIMEOUT_SECONDS`)?
> 2. **Pending offer on app start** — Should `checkPendingOffers()` run every time the app launches (even before going online), or only after the auth/onboarding flow completes?
> 3. **NewJobRequestScreen UI** — User says "you can enhance it". Plan is to add an animated circular progress timer ring instead of plain text countdown, and a subtle pulsing accept button. Agree?
---
## Proposed Changes
### 1 — Interfaces
#### [MODIFY] [delivery.ts](file:///d:/SG-Delivery/sg-delivery-partner/app/interfaces/delivery.ts)
Add typed contracts that mirror the integration guide's socket offer payload and REST respond endpoint.
```typescript
// Socket offer payload (delivery_offer event)
export interface DeliveryOffer {
deliveryId: string;
orderId: string;
pickupAddress: { label: string; lat: number; lng: number };
dropAddress: { label: string; lat: number; lng: number };
totalAmount: number;
distanceKm: number;
}
// REST: POST /delivery-partners/deliveries/{id}/respond
export type DeliveryAction = 'ACCEPT' | 'REJECT';
export interface RespondToOfferPayload {
action: DeliveryAction;
}
export interface RespondToOfferResponse {
success: boolean;
message: string;
deliveryId?: string;
}
// REST: GET /delivery-partners/deliveries/pending-offer
export interface PendingOfferResponse {
offer: DeliveryOffer | null;
}
```
---
### 2 — API Layer
#### [MODIFY] [deliveryApi.ts](file:///d:/SG-Delivery/sg-delivery-partner/app/api/deliveryApi.ts)
Implement two API calls matching the integration guide:
- `respondToOffer(deliveryId, action)` → `POST /delivery-partners/deliveries/{id}/respond`
- `getPendingOffer()` → `GET /delivery-partners/deliveries/pending-offer`
---
### 3 — Redux Delivery Slice
#### [MODIFY] [thunk.ts](file:///d:/SG-Delivery/sg-delivery-partner/app/store/commonReducers/delivery/thunk.ts)
Two async thunks:
- `respondToOfferThunk(deliveryId, action)` — calls `deliveryApi.respondToOffer`
- `checkPendingOfferThunk()` — calls `deliveryApi.getPendingOffer`
#### [MODIFY] [reducer.ts](file:///d:/SG-Delivery/sg-delivery-partner/app/store/commonReducers/delivery/reducer.ts)
`DeliveryState`:
```typescript
interface DeliveryState {
currentOffer: DeliveryOffer | null; // live socket offer
respondLoading: boolean;
respondError: string | null;
pendingOfferLoading: boolean;
}
```
Actions:
- `setDeliveryOffer(offer)` — dispatched by socketService on `delivery_offer`
- `clearDeliveryOffer()` — dispatched after accept/reject
Thunk cases: pending/fulfilled/rejected for both thunks.
#### [MODIFY] [index.ts](file:///d:/SG-Delivery/sg-delivery-partner/app/store/commonReducers/delivery/index.ts)
Export reducer, thunks, and actions.
#### [MODIFY] [commonReducers/index.ts](file:///d:/SG-Delivery/sg-delivery-partner/app/store/commonReducers/index.ts)
Add `export * from './delivery'`.
#### [MODIFY] [rootReducer.ts](file:///d:/SG-Delivery/sg-delivery-partner/app/store/rootReducer.ts)
Import and register `deliveryReducer` under key `delivery`.
---
### 4 — SocketService
#### [MODIFY] [socketService.ts](file:///d:/SG-Delivery/sg-delivery-partner/app/services/socketService.ts)
Real `socket.io-client` implementation with the following contract:
```typescript
class SocketService {
connect(token: string): void // init socket with JWT auth
disconnect(): void
goOnline(): void // emit partner going online
goOffline(): void // emit partner going offline
updateLocation(coords): void // emit location update
on(event, callback): void
off(event, callback?): void
isConnected(): boolean
}
export const socketService = new SocketService();
```
Key behaviors:
- Connects to `BASE_URL` with `auth: { token }` — same URL as axiosInstance
- Listens for `delivery_offer` → dispatches `setDeliveryOffer` + navigates to `NewJobRequest` screen (store reference injected)
- Handles reconnection automatically (socket.io built-in)
- On `connect_error` logs the error
> [!NOTE]
> The socketService needs a reference to the Redux store and the navigation ref to dispatch and navigate globally. We will use a `bootstrapSocketService(store, navRef)` function called once in `App.tsx`.
---
### 5 — Global Bootstrap in App.tsx
#### [MODIFY] [App.tsx](file:///d:/SG-Delivery/sg-delivery-partner/app/App.tsx)
- Create a `navigationRef` using `createNavigationContainerRef`.
- After `PersistGate` hydrates, run `useEffect` that: reads `accessToken` from store → if present, calls `socketService.connect(token)`.
- Pass `navigationRef` to `NavigationContainer`.
- On token change (logout), call `socketService.disconnect()`.
- Also call `checkPendingOfferThunk()` on app start when token exists.
---
### 6 — NewJobRequestScreen Enhancement
#### [MODIFY] [newJobRequestScreen.tsx](file:///d:/SG-Delivery/sg-delivery-partner/app/features/screens/newJobRequestScreen/newJobRequestScreen.tsx)
**Data source change**: Read from `state.delivery.currentOffer` (the real socket offer) instead of `state.job.newJobOffer`. Map `DeliveryOffer` fields to UI fields.
**UI enhancements**:
- Animated circular countdown ring (using `Animated` API — no extra deps needed) instead of the plain text timer box.
- Pulsing green ring on the Accept button when < 5 s remain.
- Order amount shown prominently with a gradient badge.
- Accept/Reject dispatch `respondToOfferThunk` + `clearDeliveryOffer`.
#### [MODIFY] [newJobRequestScreen.styles.ts](file:///d:/SG-Delivery/sg-delivery-partner/app/features/screens/newJobRequestScreen/newJobRequestScreen.styles.ts)
Add styles for the circular timer, pulse ring, earnings badge.
---
### 7 — DashboardScreen Cleanup
#### [MODIFY] [dashboardScreen.tsx](file:///d:/SG-Delivery/sg-delivery-partner/app/features/screens/dashboardScreen/dashboardScreen.tsx)
- Remove the mock 12-s `setTimeout` that simulates a job offer.
- When going online → call `socketService.goOnline()`.
- When going offline → call `socketService.goOffline()`.
---
## File Change Summary
| File | Action | Purpose |
|---|---|---|
| `app/interfaces/delivery.ts` | MODIFY | DeliveryOffer, RespondToOfferPayload, PendingOfferResponse types |
| `app/api/deliveryApi.ts` | MODIFY | respondToOffer, getPendingOffer API calls |
| `app/store/commonReducers/delivery/thunk.ts` | MODIFY | respondToOfferThunk, checkPendingOfferThunk |
| `app/store/commonReducers/delivery/reducer.ts` | MODIFY | DeliveryState, setDeliveryOffer, clearDeliveryOffer |
| `app/store/commonReducers/delivery/index.ts` | MODIFY | barrel export |
| `app/store/commonReducers/index.ts` | MODIFY | add delivery export |
| `app/store/rootReducer.ts` | MODIFY | register deliveryReducer |
| `app/services/socketService.ts` | MODIFY | real socket.io-client implementation |
| `app/App.tsx` | MODIFY | global socket bootstrap + navigationRef |
| `app/features/screens/newJobRequestScreen/newJobRequestScreen.tsx` | MODIFY | socket-driven data + enhanced UI |
| `app/features/screens/newJobRequestScreen/newJobRequestScreen.styles.ts` | MODIFY | timer ring + pulse styles |
| `app/features/screens/dashboardScreen/dashboardScreen.tsx` | MODIFY | remove mock timer, add socket calls |
---
## Verification Plan
### Automated Checks
- TypeScript compilation: `npx tsc --noEmit` — must pass with 0 errors.
### Manual Verification
- App launches → socket connects (check logs: `[Socket] Connected`).
- Go online → `goOnline()` called.
- Backend dispatches `delivery_offer` → `NewJobRequestScreen` pops up from any screen.
- Accept → REST `POST /respond` with `ACCEPT` fires → screen navigates to `OrderAccepted`.
- Reject / timeout → REST `POST /respond` with `REJECT` fires → back to Dashboard.
- Kill app → relaunch → `checkPendingOfferThunk` fires → pending offer screen shown if offer exists.
- Go offline → socket `goOffline()` event sent.