@burdenoff/microfe-movethewheels 2026.626.2 → 2026.703.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/{AIAssistantPage-AvsMbunM.js → AIAssistantPage-C3cPsWt-.js} +100 -73
  2. package/dist/AnalyticsPage-Dz6g2MvK.js +215 -0
  3. package/dist/{CreateOrderPage-CjlnEtk3.js → CreateOrderPage-C5iwnjp4.js} +36 -67
  4. package/dist/CustomerDetailsPage-B6nwFyLO.js +406 -0
  5. package/dist/{CustomersPage-CGGfD4ki.js → CustomersPage-2Y05LVyh.js} +26 -28
  6. package/dist/{DashboardPage-D2HRqwg9.js → DashboardPage-BVjQi0EB.js} +85 -83
  7. package/dist/{DataTable-CD9ZhpLB.js → DataTable-DmciIKKM.js} +3 -3
  8. package/dist/DriverDetailsPage-CN3WQ0B9.js +507 -0
  9. package/dist/{DriversPage-DKiynISH.js → DriversPage-B0Zpx8OQ.js} +25 -27
  10. package/dist/{FinancePage-Vg8WPs38.js → FinancePage-DEDykSOP.js} +9 -9
  11. package/dist/FleetPage-DqkFi3s3.js +365 -0
  12. package/dist/FormError-BlOjnXOq.js +15 -0
  13. package/dist/ImportExportPage-DKJRCezl.js +290 -0
  14. package/dist/InventoryPage-DwfgCUQq.js +253 -0
  15. package/dist/{LiveTrackingPage-DQ1q4Id3.js → LiveTrackingPage-DCX-K-LR.js} +60 -56
  16. package/dist/MarketplacePage-CIQ97_zp.js +247 -0
  17. package/dist/{MetricCard-B7yMyvMX.js → MetricCard-BY0QrMD4.js} +1 -1
  18. package/dist/{OrderDetailsPage-CFASgsYX.js → OrderDetailsPage-DYiZ-9UK.js} +83 -85
  19. package/dist/{OrdersListPage-BHeT8S5o.js → OrdersListPage-BjDlCKJd.js} +31 -33
  20. package/dist/{PageLayout-D3N8MCTm.js → PageLayout-D4LDHJJq.js} +483 -422
  21. package/dist/ProductDetailsPage-C9uOiYLp.js +350 -0
  22. package/dist/{ProductsPage--xEpbQ8A.js → ProductsPage-C1G9h7Xd.js} +22 -24
  23. package/dist/ReportsPage-Zp53q3_8.js +284 -0
  24. package/dist/RouteDetailsPage-DUStXSf9.js +531 -0
  25. package/dist/RoutesPage-DMfoicM1.js +144 -0
  26. package/dist/SettingsPage-Bn-v0mbQ.js +421 -0
  27. package/dist/StatusBadge-BZurYHMy.js +152 -0
  28. package/dist/{TrackingPage-CcRc_dva.js → TrackingPage-W-jtdM3Y.js} +59 -61
  29. package/dist/VehicleDetailsPage-Bceo5oz3.js +410 -0
  30. package/dist/{VehiclesPage-UlxPWP9K.js → VehiclesPage-VBtOep91.js} +20 -22
  31. package/dist/WarehouseDetailsPage-L_6sWigM.js +493 -0
  32. package/dist/{WarehousesPage-CuTO-OPp.js → WarehousesPage-DCbXXXWE.js} +16 -18
  33. package/dist/{arrow-left-CBpZKkpq.js → arrow-left-n48Y-Wis.js} +1 -1
  34. package/dist/{box-CAaiElsb.js → box-D9Pal4HM.js} +1 -1
  35. package/dist/{chart-column-D1Ym15-S.js → chart-column-BFIJysDR.js} +1 -1
  36. package/dist/{chevron-right-BIFGi0UA.js → chevron-right-EMIIyyJ1.js} +1 -1
  37. package/dist/{circle-alert-DjmNo5_L.js → circle-alert-iY24lSlp.js} +1 -1
  38. package/dist/{circle-check-big-CjuPVo3_.js → circle-check-big-w0TrLKMF.js} +1 -1
  39. package/dist/{clock-BjkdnddD.js → clock-DJZtdl8u.js} +1 -1
  40. package/dist/{dollar-sign-IB4O0wuQ.js → dollar-sign-CgKQRz_e.js} +1 -1
  41. package/dist/{download-Dpa9vqoG.js → download-DRXxoiQR.js} +1 -1
  42. package/dist/{file-text-C8Uioy11.js → file-text-DWWY6_f5.js} +1 -1
  43. package/dist/{filter-Da5OZaHI.js → filter-DloLC2eE.js} +1 -1
  44. package/dist/index.js +186 -162
  45. package/dist/{map-_Per4sT9.js → map-CkJaULb2.js} +1 -1
  46. package/dist/{map-pin-BSaf5IDL.js → map-pin-BGiCvyCs.js} +1 -1
  47. package/dist/{navigation-DaTbrhw2.js → navigation-D36prnAn.js} +1 -1
  48. package/dist/{package-BE7U0wQE.js → package-BChUzpJS.js} +1 -1
  49. package/dist/{phone-DV4StZiA.js → phone-Ac9s3mo_.js} +1 -1
  50. package/dist/{plus-Dl1rLeJR.js → plus-Z76q-FZm.js} +1 -1
  51. package/dist/{refresh-cw-CWfERAit.js → refresh-cw-DKt6f5T6.js} +1 -1
  52. package/dist/{save-XH8vydOn.js → save-CojywNhG.js} +1 -1
  53. package/dist/{search-OxoIiL_C.js → search-CDGefqb3.js} +1 -1
  54. package/dist/{square-pen-hLIbFQFV.js → square-pen-CopE9GmB.js} +1 -1
  55. package/dist/{star-YsVV3MNQ.js → star-nHlLjDKR.js} +1 -1
  56. package/dist/{store-RCBl7HB-.js → store-BR9Voi5E.js} +1 -1
  57. package/dist/trash-2-BKhar6Hs.js +32 -0
  58. package/dist/{trending-up-C-85AwGT.js → trending-up-Dohgd4t_.js} +1 -1
  59. package/dist/{triangle-alert-BOhfGgmh.js → triangle-alert-i-XQL9Rd.js} +1 -1
  60. package/dist/{truck-CtDjNFbi.js → truck-C0v_hbLR.js} +1 -1
  61. package/dist/{useAnalytics-ph7eTIK6.js → useAnalytics-DS8n0xgg.js} +40 -40
  62. package/dist/useAssistant-D4v3oxD3.js +84 -0
  63. package/dist/{useCustomers-bS3a4ytk.js → useCustomers-BmTNGxiI.js} +24 -24
  64. package/dist/{useFleet-BdETplNE.js → useFleet-DKqXZ16m.js} +133 -109
  65. package/dist/useImportExport-DHr-SWSw.js +55 -0
  66. package/dist/{useInventory-Dwn18FPz.js → useInventory-DkyiVY_A.js} +95 -75
  67. package/dist/{useOrders-D_3_hGMp.js → useOrders-Cfk1GLiq.js} +42 -42
  68. package/dist/useReports-CTqZr-AB.js +78 -0
  69. package/dist/{useRoutes-v4aBaS-E.js → useRoutes-BQWO607N.js} +31 -31
  70. package/dist/useSettings-CkDH_-gM.js +81 -0
  71. package/dist/{useTracking-De2KIUNu.js → useTracking-B-va1kO3.js} +22 -22
  72. package/dist/{user-Dz5TJZFs.js → user-jyFU3lXk.js} +1 -1
  73. package/dist/{users-g2N0eL-Z.js → users-DZ-ilS6B.js} +1 -1
  74. package/dist/{warehouse-D3WWDeTX.js → warehouse-6m5kXMLu.js} +1 -1
  75. package/dist/{wrench-CmxY1n4I.js → wrench-CANXWxF-.js} +1 -1
  76. package/package.json +1 -1
  77. package/src/movethewheels/MoveTheWheelsRoutes.tsx +31 -3
  78. package/src/movethewheels/components/FormError.tsx +25 -0
  79. package/src/movethewheels/components/StatusBadge.tsx +18 -37
  80. package/src/movethewheels/constants/index.ts +67 -26
  81. package/src/movethewheels/hooks/index.ts +12 -0
  82. package/src/movethewheels/hooks/useAnalytics.ts +20 -6
  83. package/src/movethewheels/hooks/useAssistant.ts +74 -0
  84. package/src/movethewheels/hooks/useFleet.ts +170 -59
  85. package/src/movethewheels/hooks/useImportExport.ts +101 -0
  86. package/src/movethewheels/hooks/useInventory.ts +107 -0
  87. package/src/movethewheels/hooks/useReports.ts +158 -0
  88. package/src/movethewheels/hooks/useSettings.ts +120 -0
  89. package/src/movethewheels/pages/AIAssistantPage.tsx +53 -15
  90. package/src/movethewheels/pages/AnalyticsPage.tsx +67 -64
  91. package/src/movethewheels/pages/CustomerDetailsPage.tsx +154 -21
  92. package/src/movethewheels/pages/CustomersPage.tsx +1 -1
  93. package/src/movethewheels/pages/DashboardPage.tsx +42 -22
  94. package/src/movethewheels/pages/DriverDetailsPage.tsx +160 -3
  95. package/src/movethewheels/pages/DriversPage.tsx +6 -2
  96. package/src/movethewheels/pages/FleetPage.tsx +96 -38
  97. package/src/movethewheels/pages/ImportExportPage.tsx +174 -68
  98. package/src/movethewheels/pages/InventoryPage.tsx +66 -28
  99. package/src/movethewheels/pages/LiveTrackingPage.tsx +12 -3
  100. package/src/movethewheels/pages/MarketplacePage.tsx +87 -45
  101. package/src/movethewheels/pages/OrderDetailsPage.tsx +2 -2
  102. package/src/movethewheels/pages/ProductDetailsPage.tsx +146 -3
  103. package/src/movethewheels/pages/ProductsPage.tsx +5 -1
  104. package/src/movethewheels/pages/ReportsPage.tsx +194 -90
  105. package/src/movethewheels/pages/RouteDetailsPage.tsx +290 -36
  106. package/src/movethewheels/pages/RoutesPage.tsx +4 -9
  107. package/src/movethewheels/pages/SettingsPage.tsx +234 -56
  108. package/src/movethewheels/pages/TrackingPage.tsx +6 -6
  109. package/src/movethewheels/pages/VehicleDetailsPage.tsx +158 -2
  110. package/src/movethewheels/pages/VehiclesPage.tsx +5 -1
  111. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +224 -2
  112. package/src/movethewheels/pages/WarehousesPage.tsx +5 -1
  113. package/src/movethewheels/providers/MoveTheWheelsProvider.tsx +7 -6
  114. package/src/movethewheels/store/movethewheelsStore.ts +1 -1
  115. package/dist/AnalyticsPage-CCLfHgQZ.js +0 -212
  116. package/dist/CustomerDetailsPage-I6cdDHUi.js +0 -265
  117. package/dist/DriverDetailsPage-rg_pGNuP.js +0 -330
  118. package/dist/FleetPage-DddXt8r8.js +0 -332
  119. package/dist/ImportExportPage-BW6qO3kc.js +0 -248
  120. package/dist/InventoryPage-rMGuaJN-.js +0 -222
  121. package/dist/MarketplacePage-DKrXAv62.js +0 -219
  122. package/dist/ProductDetailsPage-CFGcbaHo.js +0 -195
  123. package/dist/ReportsPage-Bb7_90rK.js +0 -235
  124. package/dist/RouteDetailsPage-CVtDVb1N.js +0 -282
  125. package/dist/RoutesPage-s57PtgcQ.js +0 -163
  126. package/dist/SettingsPage-BYNCBOpN.js +0 -298
  127. package/dist/StatusBadge-CijYQXtb.js +0 -209
  128. package/dist/VehicleDetailsPage-3YDMPsPs.js +0 -220
  129. package/dist/WarehouseDetailsPage-Db7KeN-d.js +0 -238
  130. package/dist/graphqlClient-CdJyR_ed.js +0 -55
  131. package/dist/navigation-BgnOfsVd.js +0 -6
  132. package/dist/route-Dp9JiN_E.js +0 -51
  133. package/dist/settings-3Rw5NwFF.js +0 -28
  134. package/src/movethewheels/constants/mockData.ts +0 -1342
  135. /package/dist/{formatters-_vJlC-47.js → formatters-CF2S2l1m.js} +0 -0
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Settings Page
3
+ *
4
+ * Loads persisted module settings (Query.moduleSettings.preferences) and saves
5
+ * changes via updateModuleSettings. Integrations read "Not configured" until a
6
+ * real connection is recorded — no fabricated "Connected" statuses.
3
7
  */
4
8
 
5
- import { useState } from 'react';
9
+ import { useEffect, useMemo, useState } from 'react';
6
10
  import { Settings, User, Bell, Shield, Globe, Webhook, Save } from 'lucide-react';
7
11
  import { PageLayout } from '../components/PageLayout';
8
12
  import {
@@ -26,9 +30,116 @@ import {
26
30
  TabsContent,
27
31
  } from '../components/ui';
28
32
  import { EmphasisPanel, PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';
33
+ import { useModuleSettings, type ModulePreferences } from '../hooks/useSettings';
34
+
35
+ interface CompanyPrefs {
36
+ name: string;
37
+ email: string;
38
+ phone: string;
39
+ website: string;
40
+ }
41
+ interface LocalizationPrefs {
42
+ timezone: string;
43
+ currency: string;
44
+ language: string;
45
+ }
46
+ interface NotificationPrefs {
47
+ orderUpdates: boolean;
48
+ deliveryAlerts: boolean;
49
+ systemNotifications: boolean;
50
+ weeklyReports: boolean;
51
+ }
52
+ interface SettingsForm {
53
+ company: CompanyPrefs;
54
+ localization: LocalizationPrefs;
55
+ notifications: NotificationPrefs;
56
+ twoFactorEnabled: boolean;
57
+ integrations: Record<string, boolean>;
58
+ }
59
+
60
+ const INTEGRATIONS = [
61
+ { key: 'googleMaps', name: 'Google Maps' },
62
+ { key: 'stripe', name: 'Stripe' },
63
+ { key: 'twilio', name: 'Twilio' },
64
+ ];
65
+
66
+ const NOTIFICATION_ITEMS: { key: keyof NotificationPrefs; label: string; desc: string }[] = [
67
+ {
68
+ key: 'orderUpdates',
69
+ label: 'Order updates',
70
+ desc: 'Receive notifications for order status changes',
71
+ },
72
+ { key: 'deliveryAlerts', label: 'Delivery alerts', desc: 'Get notified about delivery issues' },
73
+ {
74
+ key: 'systemNotifications',
75
+ label: 'System notifications',
76
+ desc: 'Important system updates and maintenance',
77
+ },
78
+ { key: 'weeklyReports', label: 'Weekly reports', desc: 'Receive weekly performance summary' },
79
+ ];
80
+
81
+ function toForm(preferences: ModulePreferences): SettingsForm {
82
+ const company = (preferences.company as Partial<CompanyPrefs> | undefined) ?? {};
83
+ const localization = (preferences.localization as Partial<LocalizationPrefs> | undefined) ?? {};
84
+ const notifications = (preferences.notifications as Partial<NotificationPrefs> | undefined) ?? {};
85
+ const security = (preferences.security as { twoFactorEnabled?: boolean } | undefined) ?? {};
86
+ const integrations = (preferences.integrations as Record<string, boolean> | undefined) ?? {};
87
+ return {
88
+ company: {
89
+ name: company.name ?? '',
90
+ email: company.email ?? '',
91
+ phone: company.phone ?? '',
92
+ website: company.website ?? '',
93
+ },
94
+ localization: {
95
+ timezone: localization.timezone ?? 'utc',
96
+ currency: localization.currency ?? 'usd',
97
+ language: localization.language ?? 'en',
98
+ },
99
+ notifications: {
100
+ orderUpdates: notifications.orderUpdates ?? true,
101
+ deliveryAlerts: notifications.deliveryAlerts ?? true,
102
+ systemNotifications: notifications.systemNotifications ?? true,
103
+ weeklyReports: notifications.weeklyReports ?? false,
104
+ },
105
+ twoFactorEnabled: security.twoFactorEnabled ?? false,
106
+ integrations,
107
+ };
108
+ }
29
109
 
30
110
  export default function SettingsPage() {
111
+ const { preferences, isLoading, isSaving, error, savePreferences } = useModuleSettings();
31
112
  const [tab, setTab] = useState('general');
113
+ const [form, setForm] = useState<SettingsForm>(() => toForm({}));
114
+ const [saved, setSaved] = useState(false);
115
+
116
+ const loadedForm = useMemo(() => toForm(preferences), [preferences]);
117
+ useEffect(() => {
118
+ setForm(loadedForm);
119
+ }, [loadedForm]);
120
+
121
+ const patchCompany = (patch: Partial<CompanyPrefs>) =>
122
+ setForm((prev) => ({ ...prev, company: { ...prev.company, ...patch } }));
123
+ const patchLocalization = (patch: Partial<LocalizationPrefs>) =>
124
+ setForm((prev) => ({ ...prev, localization: { ...prev.localization, ...patch } }));
125
+ const toggleNotification = (key: keyof NotificationPrefs, value: boolean) =>
126
+ setForm((prev) => ({ ...prev, notifications: { ...prev.notifications, [key]: value } }));
127
+
128
+ const handleSave = async () => {
129
+ setSaved(false);
130
+ try {
131
+ await savePreferences({
132
+ company: form.company,
133
+ localization: form.localization,
134
+ notifications: form.notifications,
135
+ security: { twoFactorEnabled: form.twoFactorEnabled },
136
+ integrations: form.integrations,
137
+ });
138
+ setSaved(true);
139
+ } catch {
140
+ // error surfaced via the hook's `error`
141
+ }
142
+ };
32
143
 
33
144
  return (
34
145
  <PageLayout
@@ -43,6 +154,19 @@ export default function SettingsPage() {
43
154
  expect.
44
155
  </PagePurpose>
45
156
 
157
+ {(saved || error) && (
158
+ <div
159
+ role="status"
160
+ className={`rounded-lg p-3 text-sm ${
161
+ error
162
+ ? 'bg-status-error-bg-subtle text-status-error-text'
163
+ : 'bg-status-success-bg-subtle text-status-success-text'
164
+ }`}
165
+ >
166
+ {error ? error.message : 'Settings saved.'}
167
+ </div>
168
+ )}
169
+
46
170
  <NextSteps
47
171
  storageKey="movethewheels-settings"
48
172
  steps={[
@@ -101,19 +225,40 @@ export default function SettingsPage() {
101
225
  <div className="grid grid-cols-2 gap-4">
102
226
  <div>
103
227
  <Label>Company Name</Label>
104
- <Input defaultValue="QuickLogistics Pro" />
228
+ <Input
229
+ value={form.company.name}
230
+ placeholder="Your company name"
231
+ disabled={isLoading}
232
+ onChange={(e) => patchCompany({ name: e.target.value })}
233
+ />
105
234
  </div>
106
235
  <div>
107
236
  <Label>Contact Email</Label>
108
- <Input defaultValue="contact@quicklogistics.com" />
237
+ <Input
238
+ type="email"
239
+ value={form.company.email}
240
+ placeholder="contact@example.com"
241
+ disabled={isLoading}
242
+ onChange={(e) => patchCompany({ email: e.target.value })}
243
+ />
109
244
  </div>
110
245
  <div>
111
246
  <Label>Phone</Label>
112
- <Input defaultValue="+1-555-QUICK" />
247
+ <Input
248
+ value={form.company.phone}
249
+ placeholder="+1-555-0100"
250
+ disabled={isLoading}
251
+ onChange={(e) => patchCompany({ phone: e.target.value })}
252
+ />
113
253
  </div>
114
254
  <div>
115
255
  <Label>Website</Label>
116
- <Input defaultValue="https://quicklogistics.com" />
256
+ <Input
257
+ value={form.company.website}
258
+ placeholder="https://example.com"
259
+ disabled={isLoading}
260
+ onChange={(e) => patchCompany({ website: e.target.value })}
261
+ />
117
262
  </div>
118
263
  </div>
119
264
  </EmphasisPanel>
@@ -128,7 +273,10 @@ export default function SettingsPage() {
128
273
  <div className="grid grid-cols-3 gap-4">
129
274
  <div>
130
275
  <Label>Timezone</Label>
131
- <Select defaultValue="pst">
276
+ <Select
277
+ value={form.localization.timezone}
278
+ onValueChange={(value) => patchLocalization({ timezone: value })}
279
+ >
132
280
  <SelectTrigger>
133
281
  <SelectValue />
134
282
  </SelectTrigger>
@@ -141,7 +289,10 @@ export default function SettingsPage() {
141
289
  </div>
142
290
  <div>
143
291
  <Label>Currency</Label>
144
- <Select defaultValue="usd">
292
+ <Select
293
+ value={form.localization.currency}
294
+ onValueChange={(value) => patchLocalization({ currency: value })}
295
+ >
145
296
  <SelectTrigger>
146
297
  <SelectValue />
147
298
  </SelectTrigger>
@@ -154,7 +305,10 @@ export default function SettingsPage() {
154
305
  </div>
155
306
  <div>
156
307
  <Label>Language</Label>
157
- <Select defaultValue="en">
308
+ <Select
309
+ value={form.localization.language}
310
+ onValueChange={(value) => patchLocalization({ language: value })}
311
+ >
158
312
  <SelectTrigger>
159
313
  <SelectValue />
160
314
  </SelectTrigger>
@@ -176,18 +330,17 @@ export default function SettingsPage() {
176
330
  <CardTitle>Email Notifications</CardTitle>
177
331
  </CardHeader>
178
332
  <CardContent className="space-y-4">
179
- {[
180
- { label: 'Order updates', desc: 'Receive notifications for order status changes' },
181
- { label: 'Delivery alerts', desc: 'Get notified about delivery issues' },
182
- { label: 'System notifications', desc: 'Important system updates and maintenance' },
183
- { label: 'Weekly reports', desc: 'Receive weekly performance summary' },
184
- ].map((item) => (
185
- <div key={item.label} className="flex items-center justify-between">
333
+ {NOTIFICATION_ITEMS.map((item) => (
334
+ <div key={item.key} className="flex items-center justify-between">
186
335
  <div>
187
336
  <p className="font-medium">{item.label}</p>
188
337
  <p className="text-sm text-text-secondary">{item.desc}</p>
189
338
  </div>
190
- <Switch defaultChecked />
339
+ <Switch
340
+ checked={form.notifications[item.key]}
341
+ disabled={isLoading}
342
+ onCheckedChange={(value) => toggleNotification(item.key, value)}
343
+ />
191
344
  </div>
192
345
  ))}
193
346
  </CardContent>
@@ -198,23 +351,26 @@ export default function SettingsPage() {
198
351
  <Card>
199
352
  <CardHeader>
200
353
  <CardTitle>Password</CardTitle>
354
+ <CardDescription>
355
+ Passwords are managed by your workspace identity provider.
356
+ </CardDescription>
201
357
  </CardHeader>
202
358
  <CardContent className="space-y-4">
203
359
  <div>
204
360
  <Label>Current Password</Label>
205
- <Input type="password" />
361
+ <Input type="password" autoComplete="current-password" />
206
362
  </div>
207
363
  <div className="grid grid-cols-2 gap-4">
208
364
  <div>
209
365
  <Label>New Password</Label>
210
- <Input type="password" />
366
+ <Input type="password" autoComplete="new-password" />
211
367
  </div>
212
368
  <div>
213
369
  <Label>Confirm Password</Label>
214
- <Input type="password" />
370
+ <Input type="password" autoComplete="new-password" />
215
371
  </div>
216
372
  </div>
217
- <Button>Update Password</Button>
373
+ <Button variant="outline">Update Password</Button>
218
374
  </CardContent>
219
375
  </Card>
220
376
  <Card>
@@ -224,59 +380,81 @@ export default function SettingsPage() {
224
380
  </CardHeader>
225
381
  <CardContent>
226
382
  <div className="flex items-center justify-between">
227
- <div>
228
- <p>
229
- Status: <span className="text-red-500 font-medium">Disabled</span>
230
- </p>
231
- </div>
232
- <Button variant="outline">Enable 2FA</Button>
383
+ <p>
384
+ Status:{' '}
385
+ <span
386
+ className={`font-medium ${
387
+ form.twoFactorEnabled
388
+ ? 'text-status-success-text'
389
+ : 'text-status-warning-text'
390
+ }`}
391
+ >
392
+ {form.twoFactorEnabled ? 'Enabled' : 'Disabled'}
393
+ </span>
394
+ </p>
395
+ <Button
396
+ variant="outline"
397
+ disabled={isSaving}
398
+ onClick={() =>
399
+ setForm((prev) => ({ ...prev, twoFactorEnabled: !prev.twoFactorEnabled }))
400
+ }
401
+ >
402
+ {form.twoFactorEnabled ? 'Disable 2FA' : 'Enable 2FA'}
403
+ </Button>
233
404
  </div>
234
405
  </CardContent>
235
406
  </Card>
236
407
  </TabsContent>
237
408
 
238
409
  <TabsContent value="integrations" className="space-y-6">
239
- <Card>
240
- <CardHeader>
241
- <CardTitle>API Keys</CardTitle>
242
- <CardDescription>Manage your API access</CardDescription>
243
- </CardHeader>
244
- <CardContent className="space-y-4">
245
- <div className="p-4 bg-bg-sunken rounded-lg">
246
- <p className="font-medium">Production API Key</p>
247
- <code className="text-sm">pk_live_••••••••••••••••</code>
248
- </div>
249
- <Button variant="outline">Generate New Key</Button>
250
- </CardContent>
251
- </Card>
252
410
  <Card>
253
411
  <CardHeader>
254
412
  <CardTitle>Connected Services</CardTitle>
413
+ <CardDescription>
414
+ Connect the providers that power maps, payments and messaging.
415
+ </CardDescription>
255
416
  </CardHeader>
256
417
  <CardContent className="space-y-3">
257
- {[
258
- { name: 'Google Maps', status: 'Connected' },
259
- { name: 'Stripe', status: 'Connected' },
260
- { name: 'Twilio', status: 'Not Connected' },
261
- ].map((s) => (
262
- <div
263
- key={s.name}
264
- className="flex items-center justify-between p-3 border rounded-lg"
265
- >
266
- <span>{s.name}</span>
267
- <Button variant={s.status === 'Connected' ? 'outline' : 'default'} size="sm">
268
- {s.status === 'Connected' ? 'Manage' : 'Connect'}
269
- </Button>
270
- </div>
271
- ))}
418
+ {INTEGRATIONS.map((service) => {
419
+ const connected = form.integrations[service.key] === true;
420
+ return (
421
+ <div
422
+ key={service.key}
423
+ className="flex items-center justify-between p-3 border rounded-lg"
424
+ >
425
+ <div>
426
+ <span className="font-medium">{service.name}</span>
427
+ <p className="text-sm text-text-secondary">
428
+ {connected ? 'Connected' : 'Not configured'}
429
+ </p>
430
+ </div>
431
+ <Button
432
+ variant={connected ? 'outline' : 'default'}
433
+ size="sm"
434
+ disabled={isSaving}
435
+ onClick={() =>
436
+ setForm((prev) => ({
437
+ ...prev,
438
+ integrations: {
439
+ ...prev.integrations,
440
+ [service.key]: !connected,
441
+ },
442
+ }))
443
+ }
444
+ >
445
+ {connected ? 'Disconnect' : 'Connect'}
446
+ </Button>
447
+ </div>
448
+ );
449
+ })}
272
450
  </CardContent>
273
451
  </Card>
274
452
  </TabsContent>
275
453
  </Tabs>
276
454
 
277
455
  <div className="flex justify-end">
278
- <Button>
279
- <Save size={16} /> Save Changes
456
+ <Button onClick={() => void handleSave()} disabled={isSaving || isLoading}>
457
+ <Save size={16} /> {isSaving ? 'Saving…' : 'Save Changes'}
280
458
  </Button>
281
459
  </div>
282
460
  </PageLayout>
@@ -308,20 +308,20 @@ export default function TrackingPage() {
308
308
  key={alert.id}
309
309
  className={`flex items-start gap-3 p-3 rounded-lg ${
310
310
  alert.severity === 'critical'
311
- ? 'bg-red-50 dark:bg-red-900/20'
311
+ ? 'bg-status-error-bg-subtle'
312
312
  : alert.severity === 'high'
313
- ? 'bg-orange-50 dark:bg-orange-900/20'
314
- : 'bg-yellow-50 dark:bg-yellow-900/20'
313
+ ? 'bg-status-warning-bg-subtle'
314
+ : 'bg-status-warning-bg-subtle'
315
315
  }`}
316
316
  >
317
317
  <AlertCircle
318
318
  size={16}
319
319
  className={
320
320
  alert.severity === 'critical'
321
- ? 'text-red-600'
321
+ ? 'text-status-error-text'
322
322
  : alert.severity === 'high'
323
- ? 'text-orange-600'
324
- : 'text-yellow-600'
323
+ ? 'text-status-warning-text'
324
+ : 'text-status-warning-text'
325
325
  }
326
326
  />
327
327
  <div>
@@ -2,8 +2,9 @@
2
2
  * Vehicle Details Page
3
3
  */
4
4
 
5
+ import { useState } from 'react';
5
6
  import { useParams } from 'react-router-dom';
6
- import { Truck, ArrowLeft, MapPin, Fuel, Wrench, FileText, Edit } from 'lucide-react';
7
+ import { Truck, ArrowLeft, MapPin, Fuel, Wrench, FileText, Edit, Save } from 'lucide-react';
7
8
  import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
8
9
  import { StatusBadge, VehicleTypeBadge } from '../components/StatusBadge';
9
10
  import {
@@ -14,12 +15,166 @@ import {
14
15
  Button,
15
16
  Badge,
16
17
  Progress,
18
+ Input,
19
+ Label,
20
+ Select,
21
+ SelectContent,
22
+ SelectItem,
23
+ SelectTrigger,
24
+ SelectValue,
17
25
  } from '../components/ui';
18
26
  import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
19
- import { useVehicle } from '../hooks/useFleet';
27
+ import { useVehicle, useFleet } from '../hooks/useFleet';
20
28
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
21
29
  import { formatDate } from '../utils/formatters';
22
30
  import { joinPath } from '../utils/navigation';
31
+ import { FormError } from '../components/FormError';
32
+
33
+ const VEHICLE_TYPE_OPTIONS = [
34
+ { value: 'TRUCK', label: 'Truck' },
35
+ { value: 'VAN', label: 'Van' },
36
+ { value: 'MOTORCYCLE', label: 'Motorcycle' },
37
+ { value: 'BICYCLE', label: 'Bicycle' },
38
+ { value: 'FLATBED', label: 'Flatbed' },
39
+ { value: 'REFRIGERATED', label: 'Refrigerated' },
40
+ { value: 'SEMI_TRAILER', label: 'Semi-Trailer' },
41
+ { value: 'DRONE', label: 'Drone' },
42
+ ];
43
+
44
+ function NewVehicleForm() {
45
+ const { basePath, navigate } = useMoveTheWheels();
46
+ const { createVehicle } = useFleet();
47
+ const [form, setForm] = useState({
48
+ vehicleNumber: '',
49
+ type: 'VAN',
50
+ make: '',
51
+ model: '',
52
+ licensePlate: '',
53
+ });
54
+ const [submitting, setSubmitting] = useState(false);
55
+ const [error, setError] = useState<string | null>(null);
56
+
57
+ const goBack = () => navigate?.(joinPath(basePath, 'fleet/vehicles'));
58
+
59
+ const handleSubmit = async (e: React.FormEvent) => {
60
+ e.preventDefault();
61
+ if (!form.vehicleNumber.trim()) {
62
+ setError('Vehicle number is required.');
63
+ return;
64
+ }
65
+ setSubmitting(true);
66
+ setError(null);
67
+ try {
68
+ const created = await createVehicle({
69
+ vehicleNumber: form.vehicleNumber.trim(),
70
+ type: form.type,
71
+ make: form.make.trim() || undefined,
72
+ model: form.model.trim() || undefined,
73
+ licensePlate: form.licensePlate.trim() || undefined,
74
+ });
75
+ navigate?.(joinPath(basePath, `fleet/vehicles/${created.id}`));
76
+ } catch (err) {
77
+ console.error('Failed to create vehicle:', err);
78
+ setError(err instanceof Error ? err.message : 'Failed to create vehicle. Please try again.');
79
+ setSubmitting(false);
80
+ }
81
+ };
82
+
83
+ return (
84
+ <PageLayout
85
+ title="Add Vehicle"
86
+ subtitle="Register a new vehicle in your fleet"
87
+ icon={<Truck size={24} />}
88
+ headerActions={
89
+ <div className="flex gap-2">
90
+ <Button variant="outline" onClick={goBack} disabled={submitting}>
91
+ <ArrowLeft size={16} /> Cancel
92
+ </Button>
93
+ <Button onClick={handleSubmit} disabled={submitting}>
94
+ <Save size={16} /> {submitting ? 'Saving...' : 'Add Vehicle'}
95
+ </Button>
96
+ </div>
97
+ }
98
+ >
99
+ <PagePurpose icon={<Truck size={18} />}>
100
+ Add a vehicle so it can be assigned to deliveries and routes. Give it a fleet number and type
101
+ to start — make, model and plate help identify it on the road.
102
+ </PagePurpose>
103
+
104
+ <form onSubmit={handleSubmit} className="max-w-2xl space-y-6">
105
+ <FormError message={error} />
106
+ <EmphasisPanel>
107
+ <h2 className="text-base font-semibold text-text-primary mb-4">Vehicle Details</h2>
108
+ <div className="space-y-4">
109
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
110
+ <div className="space-y-2">
111
+ <Label htmlFor="vehicleNumber">
112
+ Vehicle Number <span className="text-status-error-text">*</span>
113
+ </Label>
114
+ <Input
115
+ id="vehicleNumber"
116
+ value={form.vehicleNumber}
117
+ onChange={(e) => setForm((prev) => ({ ...prev, vehicleNumber: e.target.value }))}
118
+ placeholder="MTW-001"
119
+ required
120
+ />
121
+ </div>
122
+ <div className="space-y-2">
123
+ <Label htmlFor="type">
124
+ Type <span className="text-status-error-text">*</span>
125
+ </Label>
126
+ <Select
127
+ value={form.type}
128
+ onValueChange={(value) => setForm((prev) => ({ ...prev, type: value }))}
129
+ >
130
+ <SelectTrigger id="type">
131
+ <SelectValue />
132
+ </SelectTrigger>
133
+ <SelectContent>
134
+ {VEHICLE_TYPE_OPTIONS.map((option) => (
135
+ <SelectItem key={option.value} value={option.value}>
136
+ {option.label}
137
+ </SelectItem>
138
+ ))}
139
+ </SelectContent>
140
+ </Select>
141
+ </div>
142
+ </div>
143
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
144
+ <div className="space-y-2">
145
+ <Label htmlFor="make">Make</Label>
146
+ <Input
147
+ id="make"
148
+ value={form.make}
149
+ onChange={(e) => setForm((prev) => ({ ...prev, make: e.target.value }))}
150
+ placeholder="Ford"
151
+ />
152
+ </div>
153
+ <div className="space-y-2">
154
+ <Label htmlFor="model">Model</Label>
155
+ <Input
156
+ id="model"
157
+ value={form.model}
158
+ onChange={(e) => setForm((prev) => ({ ...prev, model: e.target.value }))}
159
+ placeholder="Transit"
160
+ />
161
+ </div>
162
+ <div className="space-y-2">
163
+ <Label htmlFor="licensePlate">License Plate</Label>
164
+ <Input
165
+ id="licensePlate"
166
+ value={form.licensePlate}
167
+ onChange={(e) => setForm((prev) => ({ ...prev, licensePlate: e.target.value }))}
168
+ placeholder="ABC-1234"
169
+ />
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </EmphasisPanel>
174
+ </form>
175
+ </PageLayout>
176
+ );
177
+ }
23
178
 
24
179
  export default function VehicleDetailsPage() {
25
180
  const { vehicleId } = useParams<{ vehicleId: string }>();
@@ -29,6 +184,7 @@ export default function VehicleDetailsPage() {
29
184
  const goBack = () => navigate?.(joinPath(basePath, 'fleet/vehicles'));
30
185
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
31
186
 
187
+ if (!vehicleId) return <NewVehicleForm />;
32
188
  if (isLoading) return <LoadingState message="Loading vehicle details..." />;
33
189
  if (error || !vehicle) return <ErrorState title="Vehicle not found" onRetry={refetch} />;
34
190
 
@@ -95,7 +95,11 @@ export default function VehiclesPage() {
95
95
  icon={<Truck size={24} />}
96
96
  headerActions={
97
97
  <CTAOverflowMenu
98
- primary={{ label: 'Add Vehicle', icon: <Plus size={16} />, onSelect: () => {} }}
98
+ primary={{
99
+ label: 'Add Vehicle',
100
+ icon: <Plus size={16} />,
101
+ onSelect: () => goTo('fleet/vehicles/new'),
102
+ }}
99
103
  actions={[
100
104
  { label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() },
101
105
  { label: 'Export', icon: <Download size={16} />, onSelect: () => {} },