@burdenoff/microfe-movethewheels 2026.828.1 → 2026.830.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.
- package/dist/MoveTheWheelsRoot.js +86 -83
- package/dist/MoveTheWheelsRoutes.js +107 -104
- package/dist/hooks/index.js +1 -0
- package/dist/index.js +38 -37
- package/dist/movethewheels/hooks/index.d.ts +1 -0
- package/dist/movethewheels/shared/hooks/useTr.d.ts +16 -0
- package/dist/pages/AIAssistantPage.js +45 -43
- package/dist/pages/DashboardPage.js +6 -6
- package/dist/pages/FleetPage.js +7 -7
- package/dist/pages/InventoryPage.js +6 -6
- package/dist/pages/OrdersListPage.js +5 -5
- package/dist/pages/SettingsPage.js +175 -154
- package/dist/pages/TrackingPage.js +5 -5
- package/dist/shared/hooks/useTr.js +12 -0
- package/package.json +1 -1
- package/src/movethewheels/MoveTheWheelsRoot.tsx +28 -24
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +7 -2
- package/src/movethewheels/hooks/index.ts +3 -0
- package/src/movethewheels/pages/AIAssistantPage.tsx +53 -32
- package/src/movethewheels/pages/SettingsPage.tsx +172 -70
- package/src/movethewheels/shared/hooks/useTr.ts +37 -0
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from '../components/ui';
|
|
32
32
|
import { EmphasisPanel, PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';
|
|
33
33
|
import { useModuleSettings, type ModulePreferences } from '../hooks/useSettings';
|
|
34
|
+
import { useTr } from '../hooks';
|
|
34
35
|
|
|
35
36
|
interface CompanyPrefs {
|
|
36
37
|
name: string;
|
|
@@ -57,27 +58,6 @@ interface SettingsForm {
|
|
|
57
58
|
integrations: Record<string, boolean>;
|
|
58
59
|
}
|
|
59
60
|
|
|
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
61
|
function toForm(preferences: ModulePreferences): SettingsForm {
|
|
82
62
|
const company = (preferences.company as Partial<CompanyPrefs> | undefined) ?? {};
|
|
83
63
|
const localization = (preferences.localization as Partial<LocalizationPrefs> | undefined) ?? {};
|
|
@@ -108,11 +88,66 @@ function toForm(preferences: ModulePreferences): SettingsForm {
|
|
|
108
88
|
}
|
|
109
89
|
|
|
110
90
|
export default function SettingsPage() {
|
|
91
|
+
const tr = useTr();
|
|
111
92
|
const { preferences, isLoading, isSaving, error, savePreferences } = useModuleSettings();
|
|
112
93
|
const [tab, setTab] = useState('general');
|
|
113
94
|
const [form, setForm] = useState<SettingsForm>(() => toForm({}));
|
|
114
95
|
const [saved, setSaved] = useState(false);
|
|
115
96
|
|
|
97
|
+
const integrations = useMemo(
|
|
98
|
+
() => [
|
|
99
|
+
{
|
|
100
|
+
key: 'googleMaps',
|
|
101
|
+
name: tr('movethewheels.settings.integrations.googleMaps', 'Google Maps'),
|
|
102
|
+
},
|
|
103
|
+
{ key: 'stripe', name: tr('movethewheels.settings.integrations.stripe', 'Stripe') },
|
|
104
|
+
{ key: 'twilio', name: tr('movethewheels.settings.integrations.twilio', 'Twilio') },
|
|
105
|
+
],
|
|
106
|
+
[tr]
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const notificationItems: { key: keyof NotificationPrefs; label: string; desc: string }[] =
|
|
110
|
+
useMemo(
|
|
111
|
+
() => [
|
|
112
|
+
{
|
|
113
|
+
key: 'orderUpdates',
|
|
114
|
+
label: tr('movethewheels.settings.notifications.orderUpdates.label', 'Order updates'),
|
|
115
|
+
desc: tr(
|
|
116
|
+
'movethewheels.settings.notifications.orderUpdates.desc',
|
|
117
|
+
'Receive notifications for order status changes'
|
|
118
|
+
),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
key: 'deliveryAlerts',
|
|
122
|
+
label: tr('movethewheels.settings.notifications.deliveryAlerts.label', 'Delivery alerts'),
|
|
123
|
+
desc: tr(
|
|
124
|
+
'movethewheels.settings.notifications.deliveryAlerts.desc',
|
|
125
|
+
'Get notified about delivery issues'
|
|
126
|
+
),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
key: 'systemNotifications',
|
|
130
|
+
label: tr(
|
|
131
|
+
'movethewheels.settings.notifications.systemNotifications.label',
|
|
132
|
+
'System notifications'
|
|
133
|
+
),
|
|
134
|
+
desc: tr(
|
|
135
|
+
'movethewheels.settings.notifications.systemNotifications.desc',
|
|
136
|
+
'Important system updates and maintenance'
|
|
137
|
+
),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
key: 'weeklyReports',
|
|
141
|
+
label: tr('movethewheels.settings.notifications.weeklyReports.label', 'Weekly reports'),
|
|
142
|
+
desc: tr(
|
|
143
|
+
'movethewheels.settings.notifications.weeklyReports.desc',
|
|
144
|
+
'Receive weekly performance summary'
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
[tr]
|
|
149
|
+
);
|
|
150
|
+
|
|
116
151
|
const loadedForm = useMemo(() => toForm(preferences), [preferences]);
|
|
117
152
|
useEffect(() => {
|
|
118
153
|
setForm(loadedForm);
|
|
@@ -143,15 +178,15 @@ export default function SettingsPage() {
|
|
|
143
178
|
|
|
144
179
|
return (
|
|
145
180
|
<PageLayout
|
|
146
|
-
title=
|
|
147
|
-
subtitle=
|
|
181
|
+
title={tr('movethewheels.settings.title', 'Settings')}
|
|
182
|
+
subtitle={tr('movethewheels.settings.subtitle', 'Manage your account and preferences')}
|
|
148
183
|
icon={<Settings size={24} />}
|
|
149
184
|
>
|
|
150
185
|
<PagePurpose icon={<Settings size={18} />}>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
186
|
+
{tr(
|
|
187
|
+
'movethewheels.settings.purpose',
|
|
188
|
+
'Configure how MoveTheWheels works for your business — company details and localization, which notifications you receive, account security, and the integrations (maps, payments, messaging) that power deliveries. Set these once so the rest of the app behaves the way you expect.'
|
|
189
|
+
)}
|
|
155
190
|
</PagePurpose>
|
|
156
191
|
|
|
157
192
|
{(saved || error) && (
|
|
@@ -163,7 +198,7 @@ export default function SettingsPage() {
|
|
|
163
198
|
: 'bg-status-success-bg-subtle text-status-success-text'
|
|
164
199
|
}`}
|
|
165
200
|
>
|
|
166
|
-
{error ? error.message : 'Settings saved.'}
|
|
201
|
+
{error ? error.message : tr('movethewheels.settings.saved', 'Settings saved.')}
|
|
167
202
|
</div>
|
|
168
203
|
)}
|
|
169
204
|
|
|
@@ -172,29 +207,47 @@ export default function SettingsPage() {
|
|
|
172
207
|
steps={[
|
|
173
208
|
{
|
|
174
209
|
id: 'company',
|
|
175
|
-
label:
|
|
176
|
-
|
|
210
|
+
label: tr(
|
|
211
|
+
'movethewheels.settings.nextSteps.company.label',
|
|
212
|
+
'Confirm company details & localization'
|
|
213
|
+
),
|
|
214
|
+
description: tr(
|
|
215
|
+
'movethewheels.settings.nextSteps.company.description',
|
|
216
|
+
'Set your name, contact info, timezone, currency and language.'
|
|
217
|
+
),
|
|
177
218
|
icon: <User size={16} />,
|
|
178
219
|
onClick: () => setTab('general'),
|
|
179
220
|
},
|
|
180
221
|
{
|
|
181
222
|
id: 'notifications',
|
|
182
|
-
label:
|
|
183
|
-
|
|
223
|
+
label: tr(
|
|
224
|
+
'movethewheels.settings.nextSteps.notifications.label',
|
|
225
|
+
'Choose your notifications'
|
|
226
|
+
),
|
|
227
|
+
description: tr(
|
|
228
|
+
'movethewheels.settings.nextSteps.notifications.description',
|
|
229
|
+
'Decide which order, delivery and report emails you receive.'
|
|
230
|
+
),
|
|
184
231
|
icon: <Bell size={16} />,
|
|
185
232
|
onClick: () => setTab('notifications'),
|
|
186
233
|
},
|
|
187
234
|
{
|
|
188
235
|
id: 'security',
|
|
189
|
-
label: 'Secure your account',
|
|
190
|
-
description:
|
|
236
|
+
label: tr('movethewheels.settings.nextSteps.security.label', 'Secure your account'),
|
|
237
|
+
description: tr(
|
|
238
|
+
'movethewheels.settings.nextSteps.security.description',
|
|
239
|
+
'Enable two-factor authentication for an extra layer of security.'
|
|
240
|
+
),
|
|
191
241
|
icon: <Shield size={16} />,
|
|
192
242
|
onClick: () => setTab('security'),
|
|
193
243
|
},
|
|
194
244
|
{
|
|
195
245
|
id: 'integrations',
|
|
196
|
-
label: 'Connect your tools',
|
|
197
|
-
description:
|
|
246
|
+
label: tr('movethewheels.settings.nextSteps.integrations.label', 'Connect your tools'),
|
|
247
|
+
description: tr(
|
|
248
|
+
'movethewheels.settings.nextSteps.integrations.description',
|
|
249
|
+
'Link maps, payments and messaging providers.'
|
|
250
|
+
),
|
|
198
251
|
icon: <Webhook size={16} />,
|
|
199
252
|
onClick: () => setTab('integrations'),
|
|
200
253
|
},
|
|
@@ -204,58 +257,74 @@ export default function SettingsPage() {
|
|
|
204
257
|
<Tabs value={tab} onValueChange={setTab}>
|
|
205
258
|
<TabsList className="mb-6">
|
|
206
259
|
<TabsTrigger value="general">
|
|
207
|
-
<User size={16} /> General
|
|
260
|
+
<User size={16} /> {tr('movethewheels.settings.tabs.general', 'General')}
|
|
208
261
|
</TabsTrigger>
|
|
209
262
|
<TabsTrigger value="notifications">
|
|
210
|
-
<Bell size={16} /> Notifications
|
|
263
|
+
<Bell size={16} /> {tr('movethewheels.settings.tabs.notifications', 'Notifications')}
|
|
211
264
|
</TabsTrigger>
|
|
212
265
|
<TabsTrigger value="security">
|
|
213
|
-
<Shield size={16} /> Security
|
|
266
|
+
<Shield size={16} /> {tr('movethewheels.settings.tabs.security', 'Security')}
|
|
214
267
|
</TabsTrigger>
|
|
215
268
|
<TabsTrigger value="integrations">
|
|
216
|
-
<Webhook size={16} /> Integrations
|
|
269
|
+
<Webhook size={16} /> {tr('movethewheels.settings.tabs.integrations', 'Integrations')}
|
|
217
270
|
</TabsTrigger>
|
|
218
271
|
</TabsList>
|
|
219
272
|
|
|
220
273
|
<TabsContent value="general" className="space-y-6">
|
|
221
274
|
{/* Company Information — the page's single emphasis zone */}
|
|
222
275
|
<EmphasisPanel>
|
|
223
|
-
<h2 className="text-base font-semibold text-text-primary">
|
|
224
|
-
|
|
276
|
+
<h2 className="text-base font-semibold text-text-primary">
|
|
277
|
+
{tr('movethewheels.settings.company.title', 'Company Information')}
|
|
278
|
+
</h2>
|
|
279
|
+
<p className="text-sm text-text-secondary mb-4">
|
|
280
|
+
{tr('movethewheels.settings.company.basicDetails', 'Basic company details')}
|
|
281
|
+
</p>
|
|
225
282
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
226
283
|
<div>
|
|
227
|
-
<Label>Company Name</Label>
|
|
284
|
+
<Label>{tr('movethewheels.settings.company.name', 'Company Name')}</Label>
|
|
228
285
|
<Input
|
|
229
286
|
value={form.company.name}
|
|
230
|
-
placeholder=
|
|
287
|
+
placeholder={tr(
|
|
288
|
+
'movethewheels.settings.company.placeholders.name',
|
|
289
|
+
'Your company name'
|
|
290
|
+
)}
|
|
231
291
|
disabled={isLoading}
|
|
232
292
|
onChange={(e) => patchCompany({ name: e.target.value })}
|
|
233
293
|
/>
|
|
234
294
|
</div>
|
|
235
295
|
<div>
|
|
236
|
-
<Label>Contact Email</Label>
|
|
296
|
+
<Label>{tr('movethewheels.settings.company.email', 'Contact Email')}</Label>
|
|
237
297
|
<Input
|
|
238
298
|
type="email"
|
|
239
299
|
value={form.company.email}
|
|
240
|
-
placeholder=
|
|
300
|
+
placeholder={tr(
|
|
301
|
+
'movethewheels.settings.company.placeholders.email',
|
|
302
|
+
'contact@example.com'
|
|
303
|
+
)}
|
|
241
304
|
disabled={isLoading}
|
|
242
305
|
onChange={(e) => patchCompany({ email: e.target.value })}
|
|
243
306
|
/>
|
|
244
307
|
</div>
|
|
245
308
|
<div>
|
|
246
|
-
<Label>Phone</Label>
|
|
309
|
+
<Label>{tr('movethewheels.settings.company.phone', 'Phone')}</Label>
|
|
247
310
|
<Input
|
|
248
311
|
value={form.company.phone}
|
|
249
|
-
placeholder=
|
|
312
|
+
placeholder={tr(
|
|
313
|
+
'movethewheels.settings.company.placeholders.phone',
|
|
314
|
+
'+1-555-0100'
|
|
315
|
+
)}
|
|
250
316
|
disabled={isLoading}
|
|
251
317
|
onChange={(e) => patchCompany({ phone: e.target.value })}
|
|
252
318
|
/>
|
|
253
319
|
</div>
|
|
254
320
|
<div>
|
|
255
|
-
<Label>Website</Label>
|
|
321
|
+
<Label>{tr('movethewheels.settings.company.website', 'Website')}</Label>
|
|
256
322
|
<Input
|
|
257
323
|
value={form.company.website}
|
|
258
|
-
placeholder=
|
|
324
|
+
placeholder={tr(
|
|
325
|
+
'movethewheels.settings.company.placeholders.website',
|
|
326
|
+
'https://example.com'
|
|
327
|
+
)}
|
|
259
328
|
disabled={isLoading}
|
|
260
329
|
onChange={(e) => patchCompany({ website: e.target.value })}
|
|
261
330
|
/>
|
|
@@ -266,13 +335,14 @@ export default function SettingsPage() {
|
|
|
266
335
|
<Card>
|
|
267
336
|
<CardHeader>
|
|
268
337
|
<CardTitle className="flex items-center gap-2">
|
|
269
|
-
<Globe size={18} />
|
|
338
|
+
<Globe size={18} />{' '}
|
|
339
|
+
{tr('movethewheels.settings.localization.title', 'Localization')}
|
|
270
340
|
</CardTitle>
|
|
271
341
|
</CardHeader>
|
|
272
342
|
<CardContent className="space-y-4">
|
|
273
343
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
274
344
|
<div>
|
|
275
|
-
<Label>Timezone</Label>
|
|
345
|
+
<Label>{tr('movethewheels.settings.localization.timezone', 'Timezone')}</Label>
|
|
276
346
|
<Select
|
|
277
347
|
value={form.localization.timezone}
|
|
278
348
|
onValueChange={(value) => patchLocalization({ timezone: value })}
|
|
@@ -288,7 +358,7 @@ export default function SettingsPage() {
|
|
|
288
358
|
</Select>
|
|
289
359
|
</div>
|
|
290
360
|
<div>
|
|
291
|
-
<Label>Currency</Label>
|
|
361
|
+
<Label>{tr('movethewheels.settings.localization.currency', 'Currency')}</Label>
|
|
292
362
|
<Select
|
|
293
363
|
value={form.localization.currency}
|
|
294
364
|
onValueChange={(value) => patchLocalization({ currency: value })}
|
|
@@ -304,7 +374,7 @@ export default function SettingsPage() {
|
|
|
304
374
|
</Select>
|
|
305
375
|
</div>
|
|
306
376
|
<div>
|
|
307
|
-
<Label>Language</Label>
|
|
377
|
+
<Label>{tr('movethewheels.settings.localization.language', 'Language')}</Label>
|
|
308
378
|
<Select
|
|
309
379
|
value={form.localization.language}
|
|
310
380
|
onValueChange={(value) => patchLocalization({ language: value })}
|
|
@@ -327,10 +397,12 @@ export default function SettingsPage() {
|
|
|
327
397
|
<TabsContent value="notifications" className="space-y-6">
|
|
328
398
|
<Card>
|
|
329
399
|
<CardHeader>
|
|
330
|
-
<CardTitle>
|
|
400
|
+
<CardTitle>
|
|
401
|
+
{tr('movethewheels.settings.notifications.title', 'Email Notifications')}
|
|
402
|
+
</CardTitle>
|
|
331
403
|
</CardHeader>
|
|
332
404
|
<CardContent className="space-y-4">
|
|
333
|
-
{
|
|
405
|
+
{notificationItems.map((item) => (
|
|
334
406
|
<div key={item.key} className="flex items-center justify-between">
|
|
335
407
|
<div>
|
|
336
408
|
<p className="font-medium">{item.label}</p>
|
|
@@ -350,22 +422,33 @@ export default function SettingsPage() {
|
|
|
350
422
|
<TabsContent value="security" className="space-y-6">
|
|
351
423
|
<Card>
|
|
352
424
|
<CardHeader>
|
|
353
|
-
<CardTitle>
|
|
425
|
+
<CardTitle>
|
|
426
|
+
{tr('movethewheels.settings.security.password.title', 'Password')}
|
|
427
|
+
</CardTitle>
|
|
354
428
|
<CardDescription>
|
|
355
|
-
|
|
356
|
-
|
|
429
|
+
{tr(
|
|
430
|
+
'movethewheels.settings.security.password.description',
|
|
431
|
+
'Your password and sign-in credentials are managed centrally by your workspace identity provider — update them from your account security settings, not here.'
|
|
432
|
+
)}
|
|
357
433
|
</CardDescription>
|
|
358
434
|
</CardHeader>
|
|
359
435
|
</Card>
|
|
360
436
|
<Card>
|
|
361
437
|
<CardHeader>
|
|
362
|
-
<CardTitle>
|
|
363
|
-
|
|
438
|
+
<CardTitle>
|
|
439
|
+
{tr('movethewheels.settings.security.twoFactor.title', 'Two-Factor Authentication')}
|
|
440
|
+
</CardTitle>
|
|
441
|
+
<CardDescription>
|
|
442
|
+
{tr(
|
|
443
|
+
'movethewheels.settings.security.twoFactor.description',
|
|
444
|
+
'Add an extra layer of security'
|
|
445
|
+
)}
|
|
446
|
+
</CardDescription>
|
|
364
447
|
</CardHeader>
|
|
365
448
|
<CardContent>
|
|
366
449
|
<div className="flex items-center justify-between">
|
|
367
450
|
<p>
|
|
368
|
-
Status:{' '}
|
|
451
|
+
{tr('movethewheels.settings.security.twoFactor.status', 'Status:')}{' '}
|
|
369
452
|
<span
|
|
370
453
|
className={`font-medium ${
|
|
371
454
|
form.twoFactorEnabled
|
|
@@ -373,7 +456,9 @@ export default function SettingsPage() {
|
|
|
373
456
|
: 'text-status-warning-text'
|
|
374
457
|
}`}
|
|
375
458
|
>
|
|
376
|
-
{form.twoFactorEnabled
|
|
459
|
+
{form.twoFactorEnabled
|
|
460
|
+
? tr('movethewheels.settings.security.twoFactor.enabled', 'Enabled')
|
|
461
|
+
: tr('movethewheels.settings.security.twoFactor.disabled', 'Disabled')}
|
|
377
462
|
</span>
|
|
378
463
|
</p>
|
|
379
464
|
<Button
|
|
@@ -383,7 +468,9 @@ export default function SettingsPage() {
|
|
|
383
468
|
setForm((prev) => ({ ...prev, twoFactorEnabled: !prev.twoFactorEnabled }))
|
|
384
469
|
}
|
|
385
470
|
>
|
|
386
|
-
{form.twoFactorEnabled
|
|
471
|
+
{form.twoFactorEnabled
|
|
472
|
+
? tr('movethewheels.settings.security.twoFactor.disable', 'Disable 2FA')
|
|
473
|
+
: tr('movethewheels.settings.security.twoFactor.enable', 'Enable 2FA')}
|
|
387
474
|
</Button>
|
|
388
475
|
</div>
|
|
389
476
|
</CardContent>
|
|
@@ -393,13 +480,18 @@ export default function SettingsPage() {
|
|
|
393
480
|
<TabsContent value="integrations" className="space-y-6">
|
|
394
481
|
<Card>
|
|
395
482
|
<CardHeader>
|
|
396
|
-
<CardTitle>
|
|
483
|
+
<CardTitle>
|
|
484
|
+
{tr('movethewheels.settings.integrations.title', 'Connected Services')}
|
|
485
|
+
</CardTitle>
|
|
397
486
|
<CardDescription>
|
|
398
|
-
|
|
487
|
+
{tr(
|
|
488
|
+
'movethewheels.settings.integrations.description',
|
|
489
|
+
'Connect the providers that power maps, payments and messaging.'
|
|
490
|
+
)}
|
|
399
491
|
</CardDescription>
|
|
400
492
|
</CardHeader>
|
|
401
493
|
<CardContent className="space-y-3">
|
|
402
|
-
{
|
|
494
|
+
{integrations.map((service) => {
|
|
403
495
|
const connected = form.integrations[service.key] === true;
|
|
404
496
|
return (
|
|
405
497
|
<div
|
|
@@ -409,7 +501,12 @@ export default function SettingsPage() {
|
|
|
409
501
|
<div>
|
|
410
502
|
<span className="font-medium">{service.name}</span>
|
|
411
503
|
<p className="text-sm text-text-secondary">
|
|
412
|
-
{connected
|
|
504
|
+
{connected
|
|
505
|
+
? tr('movethewheels.settings.integrations.connected', 'Connected')
|
|
506
|
+
: tr(
|
|
507
|
+
'movethewheels.settings.integrations.notConfigured',
|
|
508
|
+
'Not configured'
|
|
509
|
+
)}
|
|
413
510
|
</p>
|
|
414
511
|
</div>
|
|
415
512
|
<Button
|
|
@@ -426,7 +523,9 @@ export default function SettingsPage() {
|
|
|
426
523
|
}))
|
|
427
524
|
}
|
|
428
525
|
>
|
|
429
|
-
{connected
|
|
526
|
+
{connected
|
|
527
|
+
? tr('movethewheels.settings.integrations.disconnect', 'Disconnect')
|
|
528
|
+
: tr('movethewheels.settings.integrations.connect', 'Connect')}
|
|
430
529
|
</Button>
|
|
431
530
|
</div>
|
|
432
531
|
);
|
|
@@ -438,7 +537,10 @@ export default function SettingsPage() {
|
|
|
438
537
|
|
|
439
538
|
<div className="flex justify-end">
|
|
440
539
|
<Button onClick={() => void handleSave()} disabled={isSaving || isLoading}>
|
|
441
|
-
<Save size={16} />
|
|
540
|
+
<Save size={16} />{' '}
|
|
541
|
+
{isSaving
|
|
542
|
+
? tr('movethewheels.settings.saving', 'Saving…')
|
|
543
|
+
: tr('movethewheels.settings.saveChanges', 'Save Changes')}
|
|
442
544
|
</Button>
|
|
443
545
|
</div>
|
|
444
546
|
</PageLayout>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* useTr — translation hook with fallback semantics.
|
|
6
|
+
*
|
|
7
|
+
* Wraps `useI18n().t()` so that when a key has no translation (i.e. the
|
|
8
|
+
* provider returns the key itself), the caller's English fallback is used
|
|
9
|
+
* instead. This keeps consumer call sites consistent and avoids leaking
|
|
10
|
+
* untranslated keys into the UI.
|
|
11
|
+
*
|
|
12
|
+
* Supports optional interpolation params using `{{key}}` placeholders.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const tr = useTr();
|
|
16
|
+
* return <h1>{tr('movethewheels.dashboard.title', 'Dashboard')}</h1>;
|
|
17
|
+
* return <p>{tr('movethewheels.delete.confirm', 'Delete {{count}} items?', { count: 5 })}</p>;
|
|
18
|
+
*/
|
|
19
|
+
export function useTr(): (
|
|
20
|
+
key: string,
|
|
21
|
+
fallback: string,
|
|
22
|
+
params?: Record<string, string | number>
|
|
23
|
+
) => string {
|
|
24
|
+
const { t } = useI18n();
|
|
25
|
+
return useCallback(
|
|
26
|
+
(key: string, fallback: string, params?: Record<string, string | number>): string => {
|
|
27
|
+
const translated = t(key);
|
|
28
|
+
const raw = translated === key ? fallback : translated;
|
|
29
|
+
if (!params) return raw;
|
|
30
|
+
return Object.entries(params).reduce(
|
|
31
|
+
(str, [k, v]) => str.replace(new RegExp(`\\{\\{${k}\\}\\}`, 'g'), String(v)),
|
|
32
|
+
raw
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
|
+
[t]
|
|
36
|
+
);
|
|
37
|
+
}
|