Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Navigate, Outlet, Route, Routes } from 'react-router-dom'
import LoginRoute from './routes/Login'
import OrdersRoute from './routes/Orders/Index'
import ProfileRoute from './routes/Profile'
import { ProtectedRoute } from './components/ProtectedRoute'
import { ProtectedRoute } from './components/ProtectedRoute.tsx'
import { Header } from './components/Header'
import { BottomNav } from './components/BottomNav'
import { useAuth } from './hooks/useAuth'
Expand Down
6 changes: 6 additions & 0 deletions src/api/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export const mockOrders: Order[] = [
requiresIdCheck: true,
requiresPaymentCheck: true,
createdAt: minutesAgo(28),
acceptedAt: minutesAgo(32),
startedAt: minutesAgo(30),
customer: {
name: 'John Doe',
phone: '(555) 246-8135',
Expand All @@ -66,6 +68,10 @@ export const mockOrders: Order[] = [
requiresIdCheck: true,
requiresPaymentCheck: true,
createdAt: minutesAgo(125),
acceptedAt: minutesAgo(120),
startedAt: minutesAgo(118),
arrivedAt: minutesAgo(110),
completedAt: minutesAgo(105),
customer: {
name: 'Alice Lee',
phone: '(555) 678-9012',
Expand Down
25 changes: 24 additions & 1 deletion src/api/orders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ export async function acceptOrder(orderId: string): Promise<Order> {
if (!existing) {
throw new Error('Order not found')
}
existing.acceptedAt = new Date().toISOString()
existing.status = 'ASSIGNED'
return existing
},
)
}

export async function startOrder(orderId: string): Promise<Order> {
return safeRequest(
async () => {
const response = await apiClient.post<Order>(`/orders/${orderId}/start`)
return response.data
},
async () => {
const existing = mockOrders.find((order) => order.id === orderId)
if (!existing) {
throw new Error('Order not found')
}
existing.startedAt = new Date().toISOString()
existing.status = 'IN_PROGRESS'
return existing
},
Expand All @@ -40,6 +59,7 @@ export async function arriveOrder(orderId: string): Promise<Order> {
if (!existing) {
throw new Error('Order not found')
}
existing.arrivedAt = new Date().toISOString()
existing.status = 'ARRIVED'
return existing
},
Expand All @@ -49,14 +69,17 @@ export async function arriveOrder(orderId: string): Promise<Order> {
export async function completeOrder(orderId: string, signature?: string): Promise<Order> {
return safeRequest(
async () => {
const response = await apiClient.post<Order>(`/orders/${orderId}/complete`, { signature })
const response = await apiClient.post<Order>(`/orders/${orderId}/complete`, {
proof: signature ? { signatureUrl: signature } : undefined,
})
return response.data
},
async () => {
const existing = mockOrders.find((order) => order.id === orderId)
if (!existing) {
throw new Error('Order not found')
}
existing.completedAt = new Date().toISOString()
existing.status = 'COMPLETED'
return existing
},
Expand Down
62 changes: 41 additions & 21 deletions src/components/VerifyChecklist.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
interface VerifyChecklistProps {
idChecked: boolean
paymentChecked: boolean
requiresIdCheck: boolean
requiresPaymentCheck: boolean
onChange: (next: { idChecked: boolean; paymentChecked: boolean }) => void
}

export function VerifyChecklist({ idChecked, paymentChecked, onChange }: VerifyChecklistProps): JSX.Element {
export function VerifyChecklist({
idChecked,
paymentChecked,
requiresIdCheck,
requiresPaymentCheck,
onChange,
}: VerifyChecklistProps): JSX.Element {
return (
<div className="verify-section">
<div className="verify-header">
<h3>Required Verifications</h3>
</div>
<button
type="button"
className={`verify-item ${idChecked ? 'checked' : ''}`}
onClick={() => onChange({ idChecked: !idChecked, paymentChecked })}
>
<span>ID Verified (21+ years)</span>
<span className={`verify-checkbox ${idChecked ? 'checked' : ''}`} aria-hidden>
{idChecked ? '✓' : ''}
</span>
</button>
<button
type="button"
className={`verify-item ${paymentChecked ? 'checked' : ''}`}
onClick={() => onChange({ idChecked, paymentChecked: !paymentChecked })}
>
<span>Payment Verified</span>
<span className={`verify-checkbox ${paymentChecked ? 'checked' : ''}`} aria-hidden>
{paymentChecked ? '✓' : ''}
</span>
</button>
{requiresIdCheck ? (
<button
type="button"
className={`verify-item ${idChecked ? 'checked' : ''}`}
onClick={() => onChange({ idChecked: !idChecked, paymentChecked })}
>
<span>ID Verified (21+ years)</span>
<span className={`verify-checkbox ${idChecked ? 'checked' : ''}`} aria-hidden>
{idChecked ? '✓' : ''}
</span>
</button>
) : (
<div className="verify-item disabled">
<span>ID verification not required</span>
</div>
)}
{requiresPaymentCheck ? (
<button
type="button"
className={`verify-item ${paymentChecked ? 'checked' : ''}`}
onClick={() => onChange({ idChecked, paymentChecked: !paymentChecked })}
>
<span>Payment Verified</span>
<span className={`verify-checkbox ${paymentChecked ? 'checked' : ''}`} aria-hidden>
{paymentChecked ? '✓' : ''}
</span>
</button>
) : (
<div className="verify-item disabled">
<span>Payment verification not required</span>
</div>
)}
</div>
)
}
14 changes: 10 additions & 4 deletions src/hooks/useOrders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import {
useMemo,
useState,
} from 'react'
import { acceptOrder, completeOrder, getOrders } from '../api/orders'
import { arriveOrder } from '../api/orders'
import { acceptOrder, completeOrder, getOrders, startOrder, arriveOrder } from '../api/orders'
import { Order } from '../types'
import { useSocket } from './useSocket'
import { useToast } from './useToast'
Expand All @@ -18,6 +17,7 @@ interface OrdersContextValue {
orders: Order[]
refresh: () => Promise<void>
accept: (orderId: string) => Promise<void>
markStarted: (orderId: string) => Promise<void>
markArrived: (orderId: string) => Promise<void>
markComplete: (orderId: string, signature?: string) => Promise<void>
isFetching: boolean
Expand Down Expand Up @@ -78,6 +78,12 @@ export function OrdersProvider({ children }: PropsWithChildren): JSX.Element {
push({ title: 'Order accepted', description: order.number, variant: 'success' })
}, [mergeOrder, push])

const markStarted = useCallback(async (orderId: string) => {
const order = await startOrder(orderId)
mergeOrder(order)
push({ title: 'Delivery started', description: order.number, variant: 'info' })
}, [mergeOrder, push])

const markArrived = useCallback(async (orderId: string) => {
const order = await arriveOrder(orderId)
mergeOrder(order)
Expand All @@ -91,8 +97,8 @@ export function OrdersProvider({ children }: PropsWithChildren): JSX.Element {
}, [mergeOrder, push])

const value = useMemo<OrdersContextValue>(
() => ({ orders, refresh, accept, markArrived, markComplete, isFetching }),
[accept, isFetching, markArrived, markComplete, orders, refresh],
() => ({ orders, refresh, accept, markStarted, markArrived, markComplete, isFetching }),
[accept, isFetching, markArrived, markComplete, markStarted, orders, refresh],
)

return <OrdersContext.Provider value={value}>{children}</OrdersContext.Provider>
Expand Down
2 changes: 1 addition & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
import App from './App.tsx'
import './styles/globals.css'
import { AuthProvider } from './hooks/useAuth'
import { ToastProvider } from './hooks/useToast'
Expand Down
15 changes: 4 additions & 11 deletions src/routes/Orders/CompletedOrderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,11 @@ interface CompletedOrderCardProps {
order: Order
expanded: boolean
onToggle: (order: Order) => void
onArrive: (orderId: string) => Promise<void>
onComplete: (orderId: string, signature: string) => Promise<void>
}

export function CompletedOrderCard({
order,
expanded,
onToggle,
onArrive,
onComplete,
}: CompletedOrderCardProps): JSX.Element {
const deliveredTime = formatTimeOfDay(order.createdAt)
export function CompletedOrderCard({ order, expanded, onToggle }: CompletedOrderCardProps): JSX.Element {
const deliveredAt = order.completedAt ?? order.createdAt
const deliveredTime = formatTimeOfDay(deliveredAt)

return (
<article className={classNames('completed-order-card', expanded && 'expanded')}>
Expand Down Expand Up @@ -48,7 +41,7 @@ export function CompletedOrderCard({
</button>
{expanded ? (
<div className="completed-order-detail" id={`completed-order-${order.id}`}>
<OrderDetail order={order} onArrive={onArrive} onComplete={onComplete} />
<OrderDetail order={order} />
</div>
) : null}
</article>
Expand Down
Loading