diff --git a/apps/dashboard/src/app/dashboard/commands/page.tsx b/apps/dashboard/src/app/dashboard/commands/page.tsx index 6b7a23d..bc71d8f 100644 --- a/apps/dashboard/src/app/dashboard/commands/page.tsx +++ b/apps/dashboard/src/app/dashboard/commands/page.tsx @@ -13,22 +13,23 @@ import { PaymentMethodBadge } from '@/components/payment-method-badge'; import { EmptyState } from '@/components/empty-state'; import { LoadingState } from '@/components/loading-state'; import { ErrorState } from '@/components/error-state'; -import { formatRelativeTime, formatDateTime } from '@/lib/utils'; import { Filter } from 'lucide-react'; import { useRealtime } from '@/lib/realtime'; +import { useLang } from '@/lib/i18n'; const statusFilters = [ - { value: '', label: 'All' }, - { value: 'pending', label: 'Pending' }, - { value: 'completed', label: 'Completed' }, - { value: 'failed', label: 'Failed' }, - { value: 'conflict', label: 'Conflict' }, - { value: 'expired', label: 'Expired' }, + { value: '', label: 'commands.all' }, + { value: 'pending', label: 'status.pending' }, + { value: 'completed', label: 'status.completed' }, + { value: 'failed', label: 'status.failed' }, + { value: 'conflict', label: 'status.conflict' }, + { value: 'expired', label: 'status.expired' }, ]; export default function CommandsPage() { // Live updates make polling unnecessary; it only runs while the socket is down const { connected: live } = useRealtime(); + const { t, tn, relativeTime } = useLang(); const router = useRouter(); const [venueId, setVenueId] = useState(null); const [statusFilter, setStatusFilter] = useState(''); @@ -61,13 +62,13 @@ export default function CommandsPage() { if (!venueId) return ; if (isLoading) return ; - if (error) return ; + if (error) return ; return (
{statusFilters.map((f) => ( @@ -77,7 +78,7 @@ export default function CommandsPage() { size="sm" onClick={() => { setStatusFilter(f.value); setPage(0); }} > - {f.label} + {t(f.label)} ))}
@@ -85,20 +86,20 @@ export default function CommandsPage() { /> {commands.length === 0 ? ( - + ) : ( <>
- Tab - Requested By - Payment - Created - Status - Processed - Result + {t('commands.tab')} + {t('commands.requested')} + {t('commands.payment')} + {t('commands.created')} + {t('commands.status')} + {t('commands.processed')} + {t('commands.result')} @@ -107,9 +108,9 @@ export default function CommandsPage() { {cmd.tabId.substring(0, 8)} {cmd.requestedBy} - {formatRelativeTime(cmd.createdAt)} + {relativeTime(cmd.createdAt)} - {cmd.processedAt ? formatRelativeTime(cmd.processedAt) : '-'} + {cmd.processedAt ? relativeTime(cmd.processedAt) : '-'} {cmd.result || '-'} ))} @@ -120,7 +121,7 @@ export default function CommandsPage() { {totalPages > 1 && (

- Page {page + 1} of {totalPages} + {t('commands.page', { page: page + 1, total: totalPages })}

diff --git a/apps/dashboard/src/app/dashboard/layout.tsx b/apps/dashboard/src/app/dashboard/layout.tsx index fcadaa7..5403812 100644 --- a/apps/dashboard/src/app/dashboard/layout.tsx +++ b/apps/dashboard/src/app/dashboard/layout.tsx @@ -74,7 +74,7 @@ export default function DashboardLayout({ function handleLogout() { authApi.logout(); - toast({ title: 'Logged out', variant: 'default' }); + toast({ title: t('common.logged_out'), variant: 'default' }); router.push('/login'); } diff --git a/apps/dashboard/src/app/dashboard/page.tsx b/apps/dashboard/src/app/dashboard/page.tsx index e6e24e9..6ec6637 100644 --- a/apps/dashboard/src/app/dashboard/page.tsx +++ b/apps/dashboard/src/app/dashboard/page.tsx @@ -9,13 +9,14 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Skeleton } from '@/components/ui/skeleton'; import { LoadingState } from '@/components/loading-state'; import { CommandStatusBadge } from '@/components/command-status-badge'; -import { formatRelativeTime } from '@/lib/utils'; import { ClipboardList, Terminal, Monitor } from 'lucide-react'; import { useRealtime } from '@/lib/realtime'; +import { useLang } from '@/lib/i18n'; export default function DashboardPage() { // Live updates make polling unnecessary; it only runs while the socket is down const { connected: live } = useRealtime(); + const { t, relativeTime } = useLang(); const router = useRouter(); const [venueId, setVenueId] = useState(null); @@ -64,7 +65,7 @@ export default function DashboardPage() { return (
- + {isLoading ? (
@@ -76,24 +77,24 @@ export default function DashboardPage() {
- Open Tabs + {t('overview.open_tabs')}
{tabs.length}
- Device + {t('overview.device')} -
{device?.name || 'Not connected'}
- {device &&

Last seen {formatRelativeTime(device.lastSeenAt)}

} +
{device?.name || t('overview.not_connected')}
+ {device &&

{t('overview.last_seen', { time: relativeTime(device.lastSeenAt) })}

}
- Pending Commands + {t('overview.pending')}
{pendingCommands.length}
@@ -102,10 +103,10 @@ export default function DashboardPage() { )} - Recent Commands + {t('overview.recent')} {commands.length === 0 ? ( -

No commands yet

+

{t('overview.no_commands')}

) : (
{commands.map((cmd: any) => ( @@ -116,7 +117,7 @@ export default function DashboardPage() {
- {formatRelativeTime(cmd.createdAt)} + {relativeTime(cmd.createdAt)}
))} diff --git a/apps/dashboard/src/app/dashboard/reminders/page.tsx b/apps/dashboard/src/app/dashboard/reminders/page.tsx index da5e252..22c8d76 100644 --- a/apps/dashboard/src/app/dashboard/reminders/page.tsx +++ b/apps/dashboard/src/app/dashboard/reminders/page.tsx @@ -14,14 +14,14 @@ import { EmptyState } from '@/components/empty-state'; import { LoadingState } from '@/components/loading-state'; import { ErrorState } from '@/components/error-state'; import { toast } from '@/components/ui/toast'; -import { formatCents, formatRelativeTime } from '@/lib/utils'; +import { formatCents } from '@/lib/utils'; import { useLang } from '@/lib/i18n'; import { Send, History } from 'lucide-react'; export default function RemindersPage() { const router = useRouter(); const queryClient = useQueryClient(); - const { t } = useLang(); + const { t, relativeTime } = useLang(); const [venueId, setVenueId] = useState(null); const [sendDialogOpen, setSendDialogOpen] = useState(false); const [showHistory, setShowHistory] = useState(false); @@ -51,7 +51,7 @@ export default function RemindersPage() { setSendDialogOpen(false); if (result.errors && result.errors.length > 0) { const firstError = result.errors[0]; - toast({ title: `${result.sent}/${result.total} sent`, description: `Error: ${firstError.error}`, variant: 'destructive' }); + toast({ title: t('reminders.partial', { sent: result.sent, total: result.total }), description: t('reminders.error_detail', { error: firstError.error }), variant: 'destructive' }); } else if (result.sent > 0) { toast({ title: t('reminders.sent_title'), description: t('reminders.success', { sent: result.sent }), variant: 'success' }); } else { @@ -61,7 +61,7 @@ export default function RemindersPage() { queryClient.invalidateQueries({ queryKey: ['reminder-history'] }); }, onError: (err: any) => { - toast({ title: 'Failed', description: err.message || 'An error occurred', variant: 'destructive' }); + toast({ title: t('common.failed'), description: err.message || t('common.unknown_error'), variant: 'destructive' }); }, }); @@ -111,8 +111,8 @@ export default function RemindersPage() { {r.customerName} {r.email} {formatCents(r.amountInCents)} - {formatRelativeTime(r.sentAt)} - {r.status} + {relativeTime(r.sentAt)} + {r.status === 'sent' ? t('reminders.status_sent') : r.status} ))} @@ -135,7 +135,7 @@ export default function RemindersPage() { ) : candidatesQuery.error ? (

{t('common.error')}

) : candidates.length === 0 ? ( - + ) : ( <>
@@ -176,12 +176,12 @@ export default function RemindersPage() { {t('reminders.send')} - {t('reminders.result', { sent: 0, total: candidates.length })} + {t('reminders.candidates_desc')}

- This will send payment reminder emails to {candidates.length} customer(s) with open tabs. + {t('reminders.confirm', { count: candidates.length })}

    {candidates.map((c: any) => ( diff --git a/apps/dashboard/src/app/dashboard/settings/page.tsx b/apps/dashboard/src/app/dashboard/settings/page.tsx index 6cd95aa..5905c30 100644 --- a/apps/dashboard/src/app/dashboard/settings/page.tsx +++ b/apps/dashboard/src/app/dashboard/settings/page.tsx @@ -10,7 +10,7 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { toast } from '@/components/ui/toast'; -import { formatRelativeTime, formatDateTime } from '@/lib/utils'; +import { useLang } from '@/lib/i18n'; import { Monitor, Copy, Plus } from 'lucide-react'; export default function SettingsPage() { @@ -19,6 +19,7 @@ export default function SettingsPage() { const [userRole, setUserRole] = useState(null); const [regDialog, setRegDialog] = useState<{ open: boolean; code: string; expiresAt: string } | null>(null); const [generating, setGenerating] = useState(false); + const { t, relativeTime, dateTime, time } = useLang(); useEffect(() => { const cookie = document.cookie.split('; ').find(row => row.startsWith('access_token=')); @@ -50,7 +51,7 @@ export default function SettingsPage() { const result = await devicesApi.generateCode(); setRegDialog({ open: true, code: result.code, expiresAt: result.expiresAt }); } catch (err: any) { - toast({ title: 'Failed', description: err.message, variant: 'destructive' }); + toast({ title: t('common.failed'), description: err.message, variant: 'destructive' }); } finally { setGenerating(false); } @@ -59,50 +60,50 @@ export default function SettingsPage() { function copyCode() { if (regDialog) { navigator.clipboard.writeText(regDialog.code); - toast({ title: 'Copied', description: 'Registration code copied to clipboard' }); + toast({ title: t('common.copied'), description: t('settings.copied_desc') }); } } return (
    - +
    - Venue + {t('settings.venue')} -

    {venue?.name || 'Loading...'}

    +

    {venue?.name || t('common.loading')}

    ID: {venueId}

    - Device + {t('settings.device')} {device - ? `Paired as "${device.name}" — last seen ${formatRelativeTime(device.lastSeenAt)}` - : 'No device paired yet'} + ? t('settings.paired', { name: device.name, time: relativeTime(device.lastSeenAt) }) + : t('settings.not_paired')} {device ? (
    - Connected - Since {formatDateTime(device.createdAt)} + {t('settings.connected')} + {t('settings.since', { date: dateTime(device.createdAt) })}
    ) : (

    - Pair a bar terminal to start receiving tabs. + {t('settings.pair_desc')}

    )} {userRole === 'admin' && ( )}
    @@ -114,35 +115,35 @@ export default function SettingsPage() { !open && setRegDialog(null)}> - Pair a Device + {t('settings.pair_title')} - Enter this code on the bar terminal to connect it to your venue. + {t('settings.pair_instructions')}
    -

    Registration Code

    +

    {t('settings.pair_code')}

    {regDialog.code}
    - Expires at {new Date(regDialog.expiresAt).toLocaleTimeString()} + {t('settings.pair_expires', { time: time(regDialog.expiresAt) })}
    -

    Instructions:

    +

    {t('settings.pair_steps')}

      -
    1. Open the bar terminal app
    2. -
    3. Select your venue from the list
    4. -
    5. Enter this registration code
    6. -
    7. Give the terminal a name
    8. -
    9. Complete registration
    10. +
    11. {t('settings.pair_step1')}
    12. +
    13. {t('settings.pair_step2')}
    14. +
    15. {t('settings.pair_step3')}
    16. +
    17. {t('settings.pair_step4')}
    18. +
    19. {t('settings.pair_step5')}
    - - + +
    diff --git a/apps/dashboard/src/app/dashboard/tabs/page.tsx b/apps/dashboard/src/app/dashboard/tabs/page.tsx index 35c0da9..bb04b12 100644 --- a/apps/dashboard/src/app/dashboard/tabs/page.tsx +++ b/apps/dashboard/src/app/dashboard/tabs/page.tsx @@ -16,7 +16,7 @@ import { EmptyState } from '@/components/empty-state'; import { LoadingState } from '@/components/loading-state'; import { ErrorState } from '@/components/error-state'; import { toast } from '@/components/ui/toast'; -import { formatCents, formatRelativeTime } from '@/lib/utils'; +import { formatCents } from '@/lib/utils'; import { RefreshCw, Plus, X, Users, Mail, Send, Upload, ChevronRight, ChevronDown } from 'lucide-react'; import { ImportCustomersDialog } from '@/components/import-customers-dialog'; import { useLang } from '@/lib/i18n'; @@ -37,7 +37,7 @@ export default function TabsPage() { const [plannedEmail, setPlannedEmail] = useState(''); const [userRole, setUserRole] = useState(null); const [importOpen, setImportOpen] = useState(false); - const { t } = useLang(); + const { t, tn, relativeTime } = useLang(); useEffect(() => { const cookie = document.cookie.split('; ').find(row => row.startsWith('access_token=')); @@ -78,24 +78,25 @@ export default function TabsPage() { const newSet = new Set(pendingCommandIds); newSet.delete(cmd.id); setPendingCommandIds(newSet); - toast({ title: `Command ${cmd.status}`, description: `Close request for tab ${cmd.tabId?.substring(0, 8)} is now ${cmd.status}`, variant: cmd.status === 'completed' ? 'success' : 'destructive' }); + const customerName = data?.tabs?.find((tab: any) => tab.tabId === cmd.tabId)?.customerName ?? cmd.tabId?.substring(0, 8); + toast({ title: t(`status.${cmd.status}`), description: t('tabs.request_result', { name: customerName, status: t(`status.${cmd.status}`) }), variant: cmd.status === 'completed' ? 'success' : 'destructive' }); queryClient.invalidateQueries({ queryKey: ['tabs'] }); queryClient.invalidateQueries({ queryKey: ['commands'] }); } } } - }, [commandsQuery.data, pendingCommandIds, queryClient]); + }, [commandsQuery.data, pendingCommandIds, queryClient, data, t]); const createCommandMutation = useMutation({ mutationFn: (data: { tabId: string; expectedVersion: number; paymentMethod: string }) => commandsApi.create(venueId!, { ...data, type: 'close_tab' }), onSuccess: (result) => { setPendingCommandIds(new Set([...pendingCommandIds, result.id])); - toast({ title: 'Close request sent', description: result.warning || 'Command sent to the terminal', variant: result.warning ? 'default' : 'success' }); + toast({ title: t('tabs.request_sent'), description: result.warning || t('tabs.request_sent_desc'), variant: result.warning ? 'default' : 'success' }); queryClient.invalidateQueries({ queryKey: ['commands'] }); }, onError: (err: any) => { - toast({ title: 'Failed', description: err.message || 'An error occurred', variant: 'destructive' }); + toast({ title: t('common.failed'), description: err.message || t('common.unknown_error'), variant: 'destructive' }); }, }); @@ -114,9 +115,9 @@ export default function TabsPage() { setPlannedName(''); setPlannedEmail(''); queryClient.invalidateQueries({ queryKey: ['planned-tabs'] }); - toast({ title: 'Added', description: `"${plannedName.trim()}" added to planned tabs` }); + toast({ title: t('planned.added'), description: t('planned.added_desc', { name: plannedName.trim() }) }); } catch (err: any) { - toast({ title: 'Failed', description: err.message, variant: 'destructive' }); + toast({ title: t('common.failed'), description: err.message, variant: 'destructive' }); } } @@ -125,39 +126,39 @@ export default function TabsPage() { await plannedTabsApi.remove(venueId!, id); queryClient.invalidateQueries({ queryKey: ['planned-tabs'] }); } catch (err: any) { - toast({ title: 'Failed', description: err.message, variant: 'destructive' }); + toast({ title: t('common.failed'), description: err.message, variant: 'destructive' }); } } const sendTestMutation = useMutation({ mutationFn: (plannedTabId: string) => remindersApi.sendTest(venueId!, plannedTabId), onSuccess: (result) => { - toast({ title: 'Test email sent', description: `Sent to ${result.email}`, variant: 'success' }); + toast({ title: t('tabs.test_sent'), description: t('tabs.test_sent_desc', { email: result.email }), variant: 'success' }); }, onError: (err: any) => { - toast({ title: 'Test email failed', description: err.message || 'An error occurred', variant: 'destructive' }); + toast({ title: t('tabs.test_failed'), description: err.message || t('common.unknown_error'), variant: 'destructive' }); }, }); if (!venueId) return ; if (isLoading) return ; - if (error) return ; + if (error) return ; const tabs = data?.tabs || []; return (
    queryClient.invalidateQueries({ queryKey: ['tabs'] })}>Refresh} + title={t('tabs.title')} + description={tn('tabs.count', tabs.length)} + actions={} /> {/* Planned Customers */}
    - Planned Customers + {t('planned.title')} {userRole === 'admin' && (
    )}
    {plannedQuery.data && pendingPlanned.length === 0 && ( -

    No customers waiting for the terminal.

    +

    {t('planned.waiting_empty')}

    )} {pendingPlanned.map((pt: any) => (
    @@ -201,13 +202,14 @@ export default function TabsPage() { onClick={() => sendTestMutation.mutate(pt.id)} disabled={sendTestMutation.isPending} className="text-muted-foreground hover:text-primary ml-0.5" - title="Send test email" + title={t('tabs.send_test')} + aria-label={t('tabs.send_test')} > )} {userRole === 'admin' && ( - )} @@ -218,19 +220,19 @@ export default function TabsPage() { {tabs.length === 0 ? ( - + ) : (
- Customer - Email - Amount - Items - Version - Last Reported - Actions + {t('tabs.customer')} + {t('tabs.email')} + {t('tabs.amount')} + {t('tabs.items')} + {t('tabs.version')} + {t('tabs.reported')} + {t('tabs.actions')} @@ -245,7 +247,7 @@ export default function TabsPage() { - @@ -260,7 +262,8 @@ export default function TabsPage() { onClick={() => sendTestMutation.mutate(tab.plannedTabId)} disabled={sendTestMutation.isPending} className="hover:text-primary" - title="Send test email" + title={t('tabs.send_test')} + aria-label={t('tabs.send_test')} > @@ -273,10 +276,10 @@ export default function TabsPage() { {formatCents(tab.totalInCents)} {tab.itemCount} {tab.version} - {formatRelativeTime(tab.lastReportedAt)} + {relativeTime(tab.lastReportedAt)} {/* Closing settles the tab on the terminal; a tab without a balance has nothing to settle */} - + {expanded && ( @@ -310,27 +313,27 @@ export default function TabsPage() { !open && setCloseDialog({ open: false, tab: null })}> - Close {closeDialog.tab.customerName}'s tab? - Send a close request to the terminal. + {t('tabs.close_title', { name: closeDialog.tab.customerName })} + {t('tabs.close_desc')}
-
Amount{formatCents(closeDialog.tab.totalInCents)}
-
Items{closeDialog.tab.itemCount}
+
{t('tabs.amount')}{formatCents(closeDialog.tab.totalInCents)}
+
{t('tabs.items')}{closeDialog.tab.itemCount}
-
Version{closeDialog.tab.version}
+
{t('tabs.version')}{closeDialog.tab.version}
- + -
-
-
+
+
+
- {!paymentMethod &&

Please select a payment method

} + {!paymentMethod &&

{t('tabs.select_payment')}

}
- - + +
@@ -343,11 +346,12 @@ type TabLine = { productName: string; quantity: number; unitPriceInCents: number /** The lines on a tab, e.g. "10× Cola · €2.50 each · €25.00". */ function TabLines({ items }: { items: TabLine[] | null | undefined }) { + const { t } = useLang(); if (!items) { - return

Items aren't reported by this terminal yet. Update the app on the tablet to see them.

; + return

{t('tabs.lines_unknown')}

; } if (items.length === 0) { - return

No items on this tab.

; + return

{t('tabs.lines_empty')}

; } return (
    @@ -355,7 +359,7 @@ function TabLines({ items }: { items: TabLine[] | null | undefined }) {
  • {item.quantity}× {item.productName} - · {formatCents(item.unitPriceInCents)} each + · {formatCents(item.unitPriceInCents)} {t('tabs.each')} {formatCents(item.quantity * item.unitPriceInCents)}
  • diff --git a/apps/dashboard/src/app/dashboard/users/page.tsx b/apps/dashboard/src/app/dashboard/users/page.tsx index bce2be7..fd2d7b2 100644 --- a/apps/dashboard/src/app/dashboard/users/page.tsx +++ b/apps/dashboard/src/app/dashboard/users/page.tsx @@ -16,7 +16,7 @@ import { EmptyState } from '@/components/empty-state'; import { LoadingState } from '@/components/loading-state'; import { ErrorState } from '@/components/error-state'; import { toast } from '@/components/ui/toast'; -import { formatRelativeTime } from '@/lib/utils'; +import { useLang } from '@/lib/i18n'; import { RefreshCw, Plus } from 'lucide-react'; export default function UsersPage() { @@ -27,6 +27,7 @@ export default function UsersPage() { const [addDialog, setAddDialog] = useState(false); const [form, setForm] = useState({ email: '', password: '', name: '', role: 'viewer' }); const [submitting, setSubmitting] = useState(false); + const { t, tn, relativeTime } = useLang(); useEffect(() => { const cookie = document.cookie @@ -51,12 +52,12 @@ export default function UsersPage() { setSubmitting(true); try { await usersApi.create(venueId!, form); - toast({ title: 'User created', description: `${form.email} added as ${form.role}`, variant: 'success' }); + toast({ title: t('users.created_toast'), description: t('users.created_toast_desc', { email: form.email, role: t(`users.role.${form.role}`) }), variant: 'success' }); setAddDialog(false); setForm({ email: '', password: '', name: '', role: 'viewer' }); queryClient.invalidateQueries({ queryKey: ['users'] }); } catch (err: any) { - toast({ title: 'Failed to create user', description: err.message, variant: 'destructive' }); + toast({ title: t('users.create_failed'), description: err.message, variant: 'destructive' }); } finally { setSubmitting(false); } @@ -64,21 +65,21 @@ export default function UsersPage() { if (!venueId) return ; if (isLoading) return ; - if (error) return ; + if (error) return ; return (
    {userRole === 'admin' && ( )}
    @@ -86,16 +87,16 @@ export default function UsersPage() { /> {users.length === 0 ? ( - + ) : (
- Name - Email - Role - Created + {t('users.name')} + {t('users.email')} + {t('users.role')} + {t('users.created')} @@ -103,8 +104,8 @@ export default function UsersPage() { {u.name} {u.email} - {u.role} - {formatRelativeTime(u.createdAt)} + {t(`users.role.${u.role}`)} + {relativeTime(u.createdAt)} ))} @@ -115,37 +116,37 @@ export default function UsersPage() { - Add User - Create a new user who can access the dashboard. + {t('users.add_title')} + {t('users.add_desc')}
- - setForm({ ...form, name: e.target.value })} placeholder="Bartender name" /> + + setForm({ ...form, name: e.target.value })} placeholder={t('users.name_placeholder')} />
- + setForm({ ...form, email: e.target.value })} placeholder="email@example.com" />
- - setForm({ ...form, password: e.target.value })} placeholder="Min 6 characters" /> + + setForm({ ...form, password: e.target.value })} placeholder={t('users.password_placeholder')} />
- +
- +
diff --git a/apps/dashboard/src/app/layout.tsx b/apps/dashboard/src/app/layout.tsx index f892d0b..18d2fd0 100644 --- a/apps/dashboard/src/app/layout.tsx +++ b/apps/dashboard/src/app/layout.tsx @@ -6,7 +6,7 @@ import { Providers } from './providers'; const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { - title: 'KoolTap - Remote Tab Control', + title: 'KoolTap', description: 'Manage tabs remotely across your venue', }; diff --git a/apps/dashboard/src/app/login/page.tsx b/apps/dashboard/src/app/login/page.tsx index ba9393c..1a9900f 100644 --- a/apps/dashboard/src/app/login/page.tsx +++ b/apps/dashboard/src/app/login/page.tsx @@ -7,6 +7,8 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { LanguageSelector } from '@/components/language-selector'; +import { useLang } from '@/lib/i18n'; export default function LoginPage() { const router = useRouter(); @@ -14,6 +16,7 @@ export default function LoginPage() { const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); + const { t } = useLang(); async function handleSubmit(e: FormEvent) { e.preventDefault(); @@ -25,7 +28,7 @@ export default function LoginPage() { setTokens(result.accessToken, result.refreshToken); router.push('/dashboard'); } catch (err: any) { - setError(err.message || 'Invalid email or password'); + setError(err.message || t('auth.invalid')); } finally { setLoading(false); } @@ -40,7 +43,7 @@ export default function LoginPage() {
- +
- + )} +
+ +
diff --git a/apps/dashboard/src/components/command-status-badge.tsx b/apps/dashboard/src/components/command-status-badge.tsx index 54d8817..d0574e0 100644 --- a/apps/dashboard/src/components/command-status-badge.tsx +++ b/apps/dashboard/src/components/command-status-badge.tsx @@ -1,14 +1,18 @@ -import { Badge } from '@/components/ui/badge'; +'use client'; -const statusConfig: Record = { - pending: { label: 'Pending', variant: 'warning' }, - completed: { label: 'Completed', variant: 'success' }, - failed: { label: 'Failed', variant: 'destructive' }, - conflict: { label: 'Conflict', variant: 'destructive' }, - expired: { label: 'Expired', variant: 'secondary' }, +import { Badge } from '@/components/ui/badge'; +import { useLang } from '@/lib/i18n'; + +const statusVariants: Record = { + pending: 'warning', + completed: 'success', + failed: 'destructive', + conflict: 'destructive', + expired: 'secondary', }; export function CommandStatusBadge({ status }: { status: string }) { - const config = statusConfig[status] || { label: status, variant: 'default' as const }; - return {config.label}; -} \ No newline at end of file + const { t } = useLang(); + const variant = statusVariants[status]; + return {variant ? t(`status.${status}`) : status}; +} diff --git a/apps/dashboard/src/components/device-status-badge.tsx b/apps/dashboard/src/components/device-status-badge.tsx index 4c7f496..9b150cb 100644 --- a/apps/dashboard/src/components/device-status-badge.tsx +++ b/apps/dashboard/src/components/device-status-badge.tsx @@ -1,12 +1,15 @@ +'use client'; + import { Badge } from '@/components/ui/badge'; import { getDeviceStatus, DeviceStatus } from '@/lib/device-status'; +import { useLang } from '@/lib/i18n'; -const statusConfig: Record = { - online: { label: 'Online', variant: 'success' }, - recently_seen: { label: 'Recently seen', variant: 'secondary' }, - stale: { label: 'Stale', variant: 'warning' }, - offline: { label: 'Offline', variant: 'destructive' }, - revoked: { label: 'Revoked', variant: 'destructive' }, +const statusVariants: Record = { + online: 'success', + recently_seen: 'secondary', + stale: 'warning', + offline: 'destructive', + revoked: 'destructive', }; export function DeviceStatusBadge({ @@ -16,7 +19,7 @@ export function DeviceStatusBadge({ lastSeenAt: string | Date; revokedAt?: string | Date | null; }) { + const { t } = useLang(); const status = getDeviceStatus(lastSeenAt, revokedAt); - const config = statusConfig[status]; - return {config.label}; -} \ No newline at end of file + return {t(`device_status.${status}`)}; +} diff --git a/apps/dashboard/src/components/error-state.tsx b/apps/dashboard/src/components/error-state.tsx index b9ea113..a3c1a3b 100644 --- a/apps/dashboard/src/components/error-state.tsx +++ b/apps/dashboard/src/components/error-state.tsx @@ -1,11 +1,15 @@ +'use client'; + import { Card, CardContent } from '@/components/ui/card'; +import { useLang } from '@/lib/i18n'; export function ErrorState({ message }: { message: string }) { + const { t } = useLang(); return (
⚠️
-

Something went wrong

+

{t('common.error')}

{message}

diff --git a/apps/dashboard/src/components/import-customers-dialog.tsx b/apps/dashboard/src/components/import-customers-dialog.tsx index 6565db2..e71a209 100644 --- a/apps/dashboard/src/components/import-customers-dialog.tsx +++ b/apps/dashboard/src/components/import-customers-dialog.tsx @@ -206,7 +206,7 @@ export function ImportCustomersDialog({ venueId, open, onOpenChange, onImported