@burdenoff/microfe-movethewheels 2026.828.1 → 2026.830.1
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 +5 -2
- package/src/movethewheels/hooks/index.ts +3 -0
- package/src/movethewheels/pages/AIAssistantPage.tsx +50 -32
- package/src/movethewheels/pages/SettingsPage.tsx +154 -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,62 @@ 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
|
+
{ key: 'googleMaps', name: tr('movethewheels.settings.integrations.googleMaps', 'Google Maps') },
|
|
100
|
+
{ key: 'stripe', name: tr('movethewheels.settings.integrations.stripe', 'Stripe') },
|
|
101
|
+
{ key: 'twilio', name: tr('movethewheels.settings.integrations.twilio', 'Twilio') },
|
|
102
|
+
],
|
|
103
|
+
[tr]
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const notificationItems: { key: keyof NotificationPrefs; label: string; desc: string }[] = useMemo(
|
|
107
|
+
() => [
|
|
108
|
+
{
|
|
109
|
+
key: 'orderUpdates',
|
|
110
|
+
label: tr('movethewheels.settings.notifications.orderUpdates.label', 'Order updates'),
|
|
111
|
+
desc: tr(
|
|
112
|
+
'movethewheels.settings.notifications.orderUpdates.desc',
|
|
113
|
+
'Receive notifications for order status changes'
|
|
114
|
+
),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
key: 'deliveryAlerts',
|
|
118
|
+
label: tr('movethewheels.settings.notifications.deliveryAlerts.label', 'Delivery alerts'),
|
|
119
|
+
desc: tr(
|
|
120
|
+
'movethewheels.settings.notifications.deliveryAlerts.desc',
|
|
121
|
+
'Get notified about delivery issues'
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
key: 'systemNotifications',
|
|
126
|
+
label: tr(
|
|
127
|
+
'movethewheels.settings.notifications.systemNotifications.label',
|
|
128
|
+
'System notifications'
|
|
129
|
+
),
|
|
130
|
+
desc: tr(
|
|
131
|
+
'movethewheels.settings.notifications.systemNotifications.desc',
|
|
132
|
+
'Important system updates and maintenance'
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
key: 'weeklyReports',
|
|
137
|
+
label: tr('movethewheels.settings.notifications.weeklyReports.label', 'Weekly reports'),
|
|
138
|
+
desc: tr(
|
|
139
|
+
'movethewheels.settings.notifications.weeklyReports.desc',
|
|
140
|
+
'Receive weekly performance summary'
|
|
141
|
+
),
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
[tr]
|
|
145
|
+
);
|
|
146
|
+
|
|
116
147
|
const loadedForm = useMemo(() => toForm(preferences), [preferences]);
|
|
117
148
|
useEffect(() => {
|
|
118
149
|
setForm(loadedForm);
|
|
@@ -143,15 +174,15 @@ export default function SettingsPage() {
|
|
|
143
174
|
|
|
144
175
|
return (
|
|
145
176
|
<PageLayout
|
|
146
|
-
title=
|
|
147
|
-
subtitle=
|
|
177
|
+
title={tr('movethewheels.settings.title', 'Settings')}
|
|
178
|
+
subtitle={tr('movethewheels.settings.subtitle', 'Manage your account and preferences')}
|
|
148
179
|
icon={<Settings size={24} />}
|
|
149
180
|
>
|
|
150
181
|
<PagePurpose icon={<Settings size={18} />}>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
182
|
+
{tr(
|
|
183
|
+
'movethewheels.settings.purpose',
|
|
184
|
+
'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.'
|
|
185
|
+
)}
|
|
155
186
|
</PagePurpose>
|
|
156
187
|
|
|
157
188
|
{(saved || error) && (
|
|
@@ -163,7 +194,7 @@ export default function SettingsPage() {
|
|
|
163
194
|
: 'bg-status-success-bg-subtle text-status-success-text'
|
|
164
195
|
}`}
|
|
165
196
|
>
|
|
166
|
-
{error ? error.message : 'Settings saved.'}
|
|
197
|
+
{error ? error.message : tr('movethewheels.settings.saved', 'Settings saved.')}
|
|
167
198
|
</div>
|
|
168
199
|
)}
|
|
169
200
|
|
|
@@ -172,29 +203,41 @@ export default function SettingsPage() {
|
|
|
172
203
|
steps={[
|
|
173
204
|
{
|
|
174
205
|
id: 'company',
|
|
175
|
-
label: 'Confirm company details & localization',
|
|
176
|
-
description:
|
|
206
|
+
label: tr('movethewheels.settings.nextSteps.company.label', 'Confirm company details & localization'),
|
|
207
|
+
description: tr(
|
|
208
|
+
'movethewheels.settings.nextSteps.company.description',
|
|
209
|
+
'Set your name, contact info, timezone, currency and language.'
|
|
210
|
+
),
|
|
177
211
|
icon: <User size={16} />,
|
|
178
212
|
onClick: () => setTab('general'),
|
|
179
213
|
},
|
|
180
214
|
{
|
|
181
215
|
id: 'notifications',
|
|
182
|
-
label: 'Choose your notifications',
|
|
183
|
-
description:
|
|
216
|
+
label: tr('movethewheels.settings.nextSteps.notifications.label', 'Choose your notifications'),
|
|
217
|
+
description: tr(
|
|
218
|
+
'movethewheels.settings.nextSteps.notifications.description',
|
|
219
|
+
'Decide which order, delivery and report emails you receive.'
|
|
220
|
+
),
|
|
184
221
|
icon: <Bell size={16} />,
|
|
185
222
|
onClick: () => setTab('notifications'),
|
|
186
223
|
},
|
|
187
224
|
{
|
|
188
225
|
id: 'security',
|
|
189
|
-
label: 'Secure your account',
|
|
190
|
-
description:
|
|
226
|
+
label: tr('movethewheels.settings.nextSteps.security.label', 'Secure your account'),
|
|
227
|
+
description: tr(
|
|
228
|
+
'movethewheels.settings.nextSteps.security.description',
|
|
229
|
+
'Enable two-factor authentication for an extra layer of security.'
|
|
230
|
+
),
|
|
191
231
|
icon: <Shield size={16} />,
|
|
192
232
|
onClick: () => setTab('security'),
|
|
193
233
|
},
|
|
194
234
|
{
|
|
195
235
|
id: 'integrations',
|
|
196
|
-
label: 'Connect your tools',
|
|
197
|
-
description:
|
|
236
|
+
label: tr('movethewheels.settings.nextSteps.integrations.label', 'Connect your tools'),
|
|
237
|
+
description: tr(
|
|
238
|
+
'movethewheels.settings.nextSteps.integrations.description',
|
|
239
|
+
'Link maps, payments and messaging providers.'
|
|
240
|
+
),
|
|
198
241
|
icon: <Webhook size={16} />,
|
|
199
242
|
onClick: () => setTab('integrations'),
|
|
200
243
|
},
|
|
@@ -204,58 +247,74 @@ export default function SettingsPage() {
|
|
|
204
247
|
<Tabs value={tab} onValueChange={setTab}>
|
|
205
248
|
<TabsList className="mb-6">
|
|
206
249
|
<TabsTrigger value="general">
|
|
207
|
-
<User size={16} /> General
|
|
250
|
+
<User size={16} /> {tr('movethewheels.settings.tabs.general', 'General')}
|
|
208
251
|
</TabsTrigger>
|
|
209
252
|
<TabsTrigger value="notifications">
|
|
210
|
-
<Bell size={16} /> Notifications
|
|
253
|
+
<Bell size={16} /> {tr('movethewheels.settings.tabs.notifications', 'Notifications')}
|
|
211
254
|
</TabsTrigger>
|
|
212
255
|
<TabsTrigger value="security">
|
|
213
|
-
<Shield size={16} /> Security
|
|
256
|
+
<Shield size={16} /> {tr('movethewheels.settings.tabs.security', 'Security')}
|
|
214
257
|
</TabsTrigger>
|
|
215
258
|
<TabsTrigger value="integrations">
|
|
216
|
-
<Webhook size={16} /> Integrations
|
|
259
|
+
<Webhook size={16} /> {tr('movethewheels.settings.tabs.integrations', 'Integrations')}
|
|
217
260
|
</TabsTrigger>
|
|
218
261
|
</TabsList>
|
|
219
262
|
|
|
220
263
|
<TabsContent value="general" className="space-y-6">
|
|
221
264
|
{/* Company Information — the page's single emphasis zone */}
|
|
222
265
|
<EmphasisPanel>
|
|
223
|
-
<h2 className="text-base font-semibold text-text-primary">
|
|
224
|
-
|
|
266
|
+
<h2 className="text-base font-semibold text-text-primary">
|
|
267
|
+
{tr('movethewheels.settings.company.title', 'Company Information')}
|
|
268
|
+
</h2>
|
|
269
|
+
<p className="text-sm text-text-secondary mb-4">
|
|
270
|
+
{tr('movethewheels.settings.company.basicDetails', 'Basic company details')}
|
|
271
|
+
</p>
|
|
225
272
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
226
273
|
<div>
|
|
227
|
-
<Label>Company Name</Label>
|
|
274
|
+
<Label>{tr('movethewheels.settings.company.name', 'Company Name')}</Label>
|
|
228
275
|
<Input
|
|
229
276
|
value={form.company.name}
|
|
230
|
-
placeholder=
|
|
277
|
+
placeholder={tr(
|
|
278
|
+
'movethewheels.settings.company.placeholders.name',
|
|
279
|
+
'Your company name'
|
|
280
|
+
)}
|
|
231
281
|
disabled={isLoading}
|
|
232
282
|
onChange={(e) => patchCompany({ name: e.target.value })}
|
|
233
283
|
/>
|
|
234
284
|
</div>
|
|
235
285
|
<div>
|
|
236
|
-
<Label>Contact Email</Label>
|
|
286
|
+
<Label>{tr('movethewheels.settings.company.email', 'Contact Email')}</Label>
|
|
237
287
|
<Input
|
|
238
288
|
type="email"
|
|
239
289
|
value={form.company.email}
|
|
240
|
-
placeholder=
|
|
290
|
+
placeholder={tr(
|
|
291
|
+
'movethewheels.settings.company.placeholders.email',
|
|
292
|
+
'contact@example.com'
|
|
293
|
+
)}
|
|
241
294
|
disabled={isLoading}
|
|
242
295
|
onChange={(e) => patchCompany({ email: e.target.value })}
|
|
243
296
|
/>
|
|
244
297
|
</div>
|
|
245
298
|
<div>
|
|
246
|
-
<Label>Phone</Label>
|
|
299
|
+
<Label>{tr('movethewheels.settings.company.phone', 'Phone')}</Label>
|
|
247
300
|
<Input
|
|
248
301
|
value={form.company.phone}
|
|
249
|
-
placeholder=
|
|
302
|
+
placeholder={tr(
|
|
303
|
+
'movethewheels.settings.company.placeholders.phone',
|
|
304
|
+
'+1-555-0100'
|
|
305
|
+
)}
|
|
250
306
|
disabled={isLoading}
|
|
251
307
|
onChange={(e) => patchCompany({ phone: e.target.value })}
|
|
252
308
|
/>
|
|
253
309
|
</div>
|
|
254
310
|
<div>
|
|
255
|
-
<Label>Website</Label>
|
|
311
|
+
<Label>{tr('movethewheels.settings.company.website', 'Website')}</Label>
|
|
256
312
|
<Input
|
|
257
313
|
value={form.company.website}
|
|
258
|
-
placeholder=
|
|
314
|
+
placeholder={tr(
|
|
315
|
+
'movethewheels.settings.company.placeholders.website',
|
|
316
|
+
'https://example.com'
|
|
317
|
+
)}
|
|
259
318
|
disabled={isLoading}
|
|
260
319
|
onChange={(e) => patchCompany({ website: e.target.value })}
|
|
261
320
|
/>
|
|
@@ -266,13 +325,13 @@ export default function SettingsPage() {
|
|
|
266
325
|
<Card>
|
|
267
326
|
<CardHeader>
|
|
268
327
|
<CardTitle className="flex items-center gap-2">
|
|
269
|
-
<Globe size={18} /> Localization
|
|
328
|
+
<Globe size={18} /> {tr('movethewheels.settings.localization.title', 'Localization')}
|
|
270
329
|
</CardTitle>
|
|
271
330
|
</CardHeader>
|
|
272
331
|
<CardContent className="space-y-4">
|
|
273
332
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
274
333
|
<div>
|
|
275
|
-
<Label>Timezone</Label>
|
|
334
|
+
<Label>{tr('movethewheels.settings.localization.timezone', 'Timezone')}</Label>
|
|
276
335
|
<Select
|
|
277
336
|
value={form.localization.timezone}
|
|
278
337
|
onValueChange={(value) => patchLocalization({ timezone: value })}
|
|
@@ -288,7 +347,7 @@ export default function SettingsPage() {
|
|
|
288
347
|
</Select>
|
|
289
348
|
</div>
|
|
290
349
|
<div>
|
|
291
|
-
<Label>Currency</Label>
|
|
350
|
+
<Label>{tr('movethewheels.settings.localization.currency', 'Currency')}</Label>
|
|
292
351
|
<Select
|
|
293
352
|
value={form.localization.currency}
|
|
294
353
|
onValueChange={(value) => patchLocalization({ currency: value })}
|
|
@@ -304,7 +363,7 @@ export default function SettingsPage() {
|
|
|
304
363
|
</Select>
|
|
305
364
|
</div>
|
|
306
365
|
<div>
|
|
307
|
-
<Label>Language</Label>
|
|
366
|
+
<Label>{tr('movethewheels.settings.localization.language', 'Language')}</Label>
|
|
308
367
|
<Select
|
|
309
368
|
value={form.localization.language}
|
|
310
369
|
onValueChange={(value) => patchLocalization({ language: value })}
|
|
@@ -327,10 +386,10 @@ export default function SettingsPage() {
|
|
|
327
386
|
<TabsContent value="notifications" className="space-y-6">
|
|
328
387
|
<Card>
|
|
329
388
|
<CardHeader>
|
|
330
|
-
<CardTitle>Email Notifications</CardTitle>
|
|
389
|
+
<CardTitle>{tr('movethewheels.settings.notifications.title', 'Email Notifications')}</CardTitle>
|
|
331
390
|
</CardHeader>
|
|
332
391
|
<CardContent className="space-y-4">
|
|
333
|
-
{
|
|
392
|
+
{notificationItems.map((item) => (
|
|
334
393
|
<div key={item.key} className="flex items-center justify-between">
|
|
335
394
|
<div>
|
|
336
395
|
<p className="font-medium">{item.label}</p>
|
|
@@ -350,22 +409,31 @@ export default function SettingsPage() {
|
|
|
350
409
|
<TabsContent value="security" className="space-y-6">
|
|
351
410
|
<Card>
|
|
352
411
|
<CardHeader>
|
|
353
|
-
<CardTitle>Password</CardTitle>
|
|
412
|
+
<CardTitle>{tr('movethewheels.settings.security.password.title', 'Password')}</CardTitle>
|
|
354
413
|
<CardDescription>
|
|
355
|
-
|
|
356
|
-
|
|
414
|
+
{tr(
|
|
415
|
+
'movethewheels.settings.security.password.description',
|
|
416
|
+
'Your password and sign-in credentials are managed centrally by your workspace identity provider — update them from your account security settings, not here.'
|
|
417
|
+
)}
|
|
357
418
|
</CardDescription>
|
|
358
419
|
</CardHeader>
|
|
359
420
|
</Card>
|
|
360
421
|
<Card>
|
|
361
422
|
<CardHeader>
|
|
362
|
-
<CardTitle>
|
|
363
|
-
|
|
423
|
+
<CardTitle>
|
|
424
|
+
{tr('movethewheels.settings.security.twoFactor.title', 'Two-Factor Authentication')}
|
|
425
|
+
</CardTitle>
|
|
426
|
+
<CardDescription>
|
|
427
|
+
{tr(
|
|
428
|
+
'movethewheels.settings.security.twoFactor.description',
|
|
429
|
+
'Add an extra layer of security'
|
|
430
|
+
)}
|
|
431
|
+
</CardDescription>
|
|
364
432
|
</CardHeader>
|
|
365
433
|
<CardContent>
|
|
366
434
|
<div className="flex items-center justify-between">
|
|
367
435
|
<p>
|
|
368
|
-
Status:{' '}
|
|
436
|
+
{tr('movethewheels.settings.security.twoFactor.status', 'Status:')}{' '}
|
|
369
437
|
<span
|
|
370
438
|
className={`font-medium ${
|
|
371
439
|
form.twoFactorEnabled
|
|
@@ -373,7 +441,9 @@ export default function SettingsPage() {
|
|
|
373
441
|
: 'text-status-warning-text'
|
|
374
442
|
}`}
|
|
375
443
|
>
|
|
376
|
-
{form.twoFactorEnabled
|
|
444
|
+
{form.twoFactorEnabled
|
|
445
|
+
? tr('movethewheels.settings.security.twoFactor.enabled', 'Enabled')
|
|
446
|
+
: tr('movethewheels.settings.security.twoFactor.disabled', 'Disabled')}
|
|
377
447
|
</span>
|
|
378
448
|
</p>
|
|
379
449
|
<Button
|
|
@@ -383,7 +453,9 @@ export default function SettingsPage() {
|
|
|
383
453
|
setForm((prev) => ({ ...prev, twoFactorEnabled: !prev.twoFactorEnabled }))
|
|
384
454
|
}
|
|
385
455
|
>
|
|
386
|
-
{form.twoFactorEnabled
|
|
456
|
+
{form.twoFactorEnabled
|
|
457
|
+
? tr('movethewheels.settings.security.twoFactor.disable', 'Disable 2FA')
|
|
458
|
+
: tr('movethewheels.settings.security.twoFactor.enable', 'Enable 2FA')}
|
|
387
459
|
</Button>
|
|
388
460
|
</div>
|
|
389
461
|
</CardContent>
|
|
@@ -393,13 +465,18 @@ export default function SettingsPage() {
|
|
|
393
465
|
<TabsContent value="integrations" className="space-y-6">
|
|
394
466
|
<Card>
|
|
395
467
|
<CardHeader>
|
|
396
|
-
<CardTitle>
|
|
468
|
+
<CardTitle>
|
|
469
|
+
{tr('movethewheels.settings.integrations.title', 'Connected Services')}
|
|
470
|
+
</CardTitle>
|
|
397
471
|
<CardDescription>
|
|
398
|
-
|
|
472
|
+
{tr(
|
|
473
|
+
'movethewheels.settings.integrations.description',
|
|
474
|
+
'Connect the providers that power maps, payments and messaging.'
|
|
475
|
+
)}
|
|
399
476
|
</CardDescription>
|
|
400
477
|
</CardHeader>
|
|
401
478
|
<CardContent className="space-y-3">
|
|
402
|
-
{
|
|
479
|
+
{integrations.map((service) => {
|
|
403
480
|
const connected = form.integrations[service.key] === true;
|
|
404
481
|
return (
|
|
405
482
|
<div
|
|
@@ -409,7 +486,9 @@ export default function SettingsPage() {
|
|
|
409
486
|
<div>
|
|
410
487
|
<span className="font-medium">{service.name}</span>
|
|
411
488
|
<p className="text-sm text-text-secondary">
|
|
412
|
-
{connected
|
|
489
|
+
{connected
|
|
490
|
+
? tr('movethewheels.settings.integrations.connected', 'Connected')
|
|
491
|
+
: tr('movethewheels.settings.integrations.notConfigured', 'Not configured')}
|
|
413
492
|
</p>
|
|
414
493
|
</div>
|
|
415
494
|
<Button
|
|
@@ -426,7 +505,9 @@ export default function SettingsPage() {
|
|
|
426
505
|
}))
|
|
427
506
|
}
|
|
428
507
|
>
|
|
429
|
-
{connected
|
|
508
|
+
{connected
|
|
509
|
+
? tr('movethewheels.settings.integrations.disconnect', 'Disconnect')
|
|
510
|
+
: tr('movethewheels.settings.integrations.connect', 'Connect')}
|
|
430
511
|
</Button>
|
|
431
512
|
</div>
|
|
432
513
|
);
|
|
@@ -438,7 +519,10 @@ export default function SettingsPage() {
|
|
|
438
519
|
|
|
439
520
|
<div className="flex justify-end">
|
|
440
521
|
<Button onClick={() => void handleSave()} disabled={isSaving || isLoading}>
|
|
441
|
-
<Save size={16} />
|
|
522
|
+
<Save size={16} />{' '}
|
|
523
|
+
{isSaving
|
|
524
|
+
? tr('movethewheels.settings.saving', 'Saving…')
|
|
525
|
+
: tr('movethewheels.settings.saveChanges', 'Save Changes')}
|
|
442
526
|
</Button>
|
|
443
527
|
</div>
|
|
444
528
|
</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
|
+
}
|