From 17d2387918173618115c5fc06c70f8482a705ca0 Mon Sep 17 00:00:00 2001 From: Riyadh Date: Mon, 20 Apr 2026 15:05:11 +0000 Subject: [PATCH] Task #19: Force Latin digits + redesign home screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with numberingSystem: "latn" so numerals always render 0-9 even in Arabic, while month/weekday names stay localized. Exports formatTime, formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber and a greetingKey() helper for time-of-day greetings. - Replaced every toLocaleTimeString / toLocaleDateString / toLocaleString / direct Intl.DateTimeFormat call in home, admin, chat and notifications with the new helpers. No remaining Arabic-Indic digits anywhere in the UI (verified e2e). - Redesigned the home screen: * Tighter status bar with three balanced sections: identity (avatar + name + admin tag), centered clock + Hijri/Gregorian dates, grouped controls (language, bell, logout). * Personal greeting line ("Good morning / صباح الخير …") driven by local hour and the user's display name. * Admin-only 4-card stat row (Apps / Services / Messages / Alerts) with soft glass + small colored icon tiles. * Polished apps grid with section count badge and a friendly empty state. Drag-and-drop reorder behavior preserved. * Type-safe Lucide icon resolver (no unsafe casts). - Added i18n keys home.greeting.{morning,afternoon,evening}, home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json. - Cleaned up obsolete follow-up plan file now that this work shipped. Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and English (LTR): login → home redesign → notifications → chat, all numerals Latin in both languages. --- artifacts/teaboy-os/public/opengraph.jpg | Bin 34853 -> 25359 bytes artifacts/teaboy-os/src/lib/i18n-format.ts | 101 +++++++ artifacts/teaboy-os/src/locales/ar.json | 15 +- artifacts/teaboy-os/src/locales/en.json | 15 +- artifacts/teaboy-os/src/pages/admin.tsx | 12 +- artifacts/teaboy-os/src/pages/chat.tsx | 6 +- artifacts/teaboy-os/src/pages/home.tsx | 248 ++++++++++++------ .../teaboy-os/src/pages/notifications.tsx | 3 +- 8 files changed, 311 insertions(+), 89 deletions(-) create mode 100644 artifacts/teaboy-os/src/lib/i18n-format.ts diff --git a/artifacts/teaboy-os/public/opengraph.jpg b/artifacts/teaboy-os/public/opengraph.jpg index 530741e05a9a6cd14899319f807bd1b9ac6ed98d..7dfb16c813d4ef6de10d9cb63a2ed2f5bbaf9b9a 100644 GIT binary patch literal 25359 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 LyE*ybPyhH2;nNeq literal 34853 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 diff --git a/artifacts/teaboy-os/src/lib/i18n-format.ts b/artifacts/teaboy-os/src/lib/i18n-format.ts new file mode 100644 index 00000000..30e6c1fc --- /dev/null +++ b/artifacts/teaboy-os/src/lib/i18n-format.ts @@ -0,0 +1,101 @@ +/** + * Locale-aware date/number formatters that ALWAYS render Latin (0-9) digits, + * even when the active language is Arabic. Month and weekday names still + * localize to Arabic as expected. + */ + +function withLatn(locale: string): string { + return locale.includes("-u-") ? `${locale}-nu-latn` : `${locale}-u-nu-latn`; +} + +function baseLocale(lang: string): string { + return lang === "ar" ? "ar" : "en-US"; +} + +export function formatTime( + date: Date | string | number, + lang: string, + options: Intl.DateTimeFormatOptions = { hour: "2-digit", minute: "2-digit" }, +): string { + const d = date instanceof Date ? date : new Date(date); + return new Intl.DateTimeFormat(withLatn(baseLocale(lang)), { + ...options, + hour12: false, + numberingSystem: "latn", + }).format(d); +} + +export function formatDate( + date: Date | string | number, + lang: string, + options: Intl.DateTimeFormatOptions = { + day: "numeric", + month: "long", + year: "numeric", + }, +): string { + const d = date instanceof Date ? date : new Date(date); + return new Intl.DateTimeFormat(withLatn(baseLocale(lang)), { + ...options, + numberingSystem: "latn", + }).format(d); +} + +export function formatHijri( + date: Date | string | number, + lang: string, + options: Intl.DateTimeFormatOptions = { + day: "numeric", + month: "long", + year: "numeric", + }, +): string { + const d = date instanceof Date ? date : new Date(date); + const loc = lang === "ar" ? "ar-u-ca-islamic-umalqura-nu-latn" : "en-u-ca-islamic-umalqura-nu-latn"; + return new Intl.DateTimeFormat(loc, { + ...options, + numberingSystem: "latn", + }).format(d); +} + +export function formatWeekday( + date: Date | string | number, + lang: string, + weekday: "long" | "short" | "narrow" = "long", +): string { + const d = date instanceof Date ? date : new Date(date); + return new Intl.DateTimeFormat(withLatn(baseLocale(lang)), { + weekday, + numberingSystem: "latn", + }).format(d); +} + +export function formatDateTime( + date: Date | string | number, + lang: string, +): string { + const d = date instanceof Date ? date : new Date(date); + return new Intl.DateTimeFormat(withLatn(baseLocale(lang)), { + dateStyle: "medium", + timeStyle: "short", + numberingSystem: "latn", + hour12: false, + }).format(d); +} + +export function formatNumber(value: number, lang: string): string { + return new Intl.NumberFormat(withLatn(baseLocale(lang)), { + numberingSystem: "latn", + }).format(value); +} + +/** + * Localized time-of-day greeting key. Uses the local hour to pick one of + * "morning", "afternoon", or "evening". Caller provides the i18n strings. + */ +export function greetingKey(date: Date = new Date()): "morning" | "afternoon" | "evening" { + const h = date.getHours(); + if (h < 12) return "morning"; + if (h < 18) return "afternoon"; + return "evening"; +} diff --git a/artifacts/teaboy-os/src/locales/ar.json b/artifacts/teaboy-os/src/locales/ar.json index 706bff69..466080a4 100644 --- a/artifacts/teaboy-os/src/locales/ar.json +++ b/artifacts/teaboy-os/src/locales/ar.json @@ -35,7 +35,20 @@ "rights": "جميع الحقوق محفوظة." }, "home": { - "myApps": "تطبيقاتي" + "myApps": "تطبيقاتي", + "noApps": "لا توجد تطبيقات بعد", + "noAppsHint": "ستظهر هنا التطبيقات التي تملك صلاحية الوصول إليها.", + "greeting": { + "morning": "صباح الخير، {{name}}", + "afternoon": "مساء الخير، {{name}}", + "evening": "مساء الخير، {{name}}" + }, + "stats": { + "apps": "التطبيقات", + "services": "الخدمات", + "messages": "الرسائل", + "alerts": "التنبيهات" + } }, "services": { "title": "الخدمات", diff --git a/artifacts/teaboy-os/src/locales/en.json b/artifacts/teaboy-os/src/locales/en.json index bfc6b059..eb4a5492 100644 --- a/artifacts/teaboy-os/src/locales/en.json +++ b/artifacts/teaboy-os/src/locales/en.json @@ -35,7 +35,20 @@ "rights": "All rights reserved." }, "home": { - "myApps": "My Apps" + "myApps": "My Apps", + "noApps": "No apps yet", + "noAppsHint": "Apps you have access to will appear here.", + "greeting": { + "morning": "Good morning, {{name}}", + "afternoon": "Good afternoon, {{name}}", + "evening": "Good evening, {{name}}" + }, + "stats": { + "apps": "Apps", + "services": "Services", + "messages": "Messages", + "alerts": "Alerts" + } }, "services": { "title": "Services", diff --git a/artifacts/teaboy-os/src/pages/admin.tsx b/artifacts/teaboy-os/src/pages/admin.tsx index 70f682cc..d1c1deb7 100644 --- a/artifacts/teaboy-os/src/pages/admin.tsx +++ b/artifacts/teaboy-os/src/pages/admin.tsx @@ -36,6 +36,7 @@ import { Switch } from "@/components/ui/switch"; import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; +import { formatDate as formatDateUtil, formatWeekday as formatWeekdayUtil } from "@/lib/i18n-format"; type AdminSection = "dashboard" | "apps" | "services" | "users" | "settings"; @@ -778,15 +779,10 @@ function DashboardSection({ ? [...apps].sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? ""))[0] : undefined; - const dateLocale = lang === "ar" ? "ar" : "en"; const formatDate = (iso?: string) => { if (!iso) return ""; try { - return new Date(iso).toLocaleDateString(dateLocale, { - year: "numeric", - month: "short", - day: "numeric", - }); + return formatDateUtil(iso, lang, { year: "numeric", month: "short", day: "numeric" }); } catch { return ""; } @@ -814,9 +810,7 @@ function DashboardSection({ const maxSignup = Math.max(1, ...signups.map((s) => s.count)); const dayLabel = (iso: string) => { try { - return new Date(iso + "T00:00:00Z").toLocaleDateString(dateLocale, { - weekday: "short", - }); + return formatWeekdayUtil(new Date(iso + "T00:00:00Z"), lang, "short"); } catch { return ""; } diff --git a/artifacts/teaboy-os/src/pages/chat.tsx b/artifacts/teaboy-os/src/pages/chat.tsx index 78de046b..c6d49a28 100644 --- a/artifacts/teaboy-os/src/pages/chat.tsx +++ b/artifacts/teaboy-os/src/pages/chat.tsx @@ -16,6 +16,7 @@ import { io } from "socket.io-client"; import { ArrowRight, ArrowLeft, MessageCircle, Plus, Send } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { formatTime } from "@/lib/i18n-format"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; @@ -265,10 +266,7 @@ export default function ChatPage() { {msg.content}
- {new Date(msg.createdAt).toLocaleTimeString(lang === "ar" ? "ar-SA" : "en-US", { - hour: "2-digit", - minute: "2-digit", - })} + {formatTime(msg.createdAt, lang)}
diff --git a/artifacts/teaboy-os/src/pages/home.tsx b/artifacts/teaboy-os/src/pages/home.tsx index ff2cad03..54dc4095 100644 --- a/artifacts/teaboy-os/src/pages/home.tsx +++ b/artifacts/teaboy-os/src/pages/home.tsx @@ -16,8 +16,18 @@ import { } from "@workspace/api-client-react"; import { useQueryClient } from "@tanstack/react-query"; import { useAuth } from "@/contexts/AuthContext"; -import { Bell, LogOut, Globe } from "lucide-react"; +import { + Bell, + LogOut, + Globe, + Grid2X2, + Coffee, + MessageSquare, + BellRing, + Sparkles, +} from "lucide-react"; import * as LucideIcons from "lucide-react"; +import type { LucideIcon } from "lucide-react"; import i18n from "@/i18n"; import { DndContext, @@ -35,6 +45,28 @@ import { useSortable, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; +import { + formatTime, + formatDate, + formatHijri, + formatWeekday, + formatNumber, + greetingKey, +} from "@/lib/i18n-format"; + +type IconName = keyof typeof LucideIcons; +function isIconName(x: string): x is IconName { + return x in LucideIcons; +} +function resolveIcon(name: string): LucideIcon { + if (isIconName(name)) { + const Candidate = LucideIcons[name] as unknown; + if (typeof Candidate === "function" || typeof Candidate === "object") { + return Candidate as LucideIcon; + } + } + return LucideIcons.Grid2X2; +} function gradientForColor(color: string): string { const c = (color ?? "").toLowerCase(); @@ -51,18 +83,17 @@ function AppIcon({ app, onClick }: { app: { nameAr: string; nameEn: string; icon const lang = i18nInstance.language; const name = lang === "ar" ? app.nameAr : app.nameEn; - const Icon = (LucideIcons as unknown as Record>)[app.iconName] - ?? LucideIcons.Grid2X2; + const Icon = resolveIcon(app.iconName); return ( @@ -85,6 +116,27 @@ function SortableAppIcon({ app, onClick }: { app: App; onClick: () => void }) { ); } +type StatCardProps = { + label: string; + value: string; + Icon: typeof Grid2X2; + iconClass: string; +}; + +function StatCard({ label, value, Icon, iconClass }: StatCardProps) { + return ( +
+
+ +
+
+
{value}
+
{label}
+
+
+ ); +} + export default function HomePage() { const { t, i18n: i18nInstance } = useTranslation(); const [, setLocation] = useLocation(); @@ -152,25 +204,21 @@ export default function HomePage() { return () => clearInterval(timer); }, []); - const dateLocale = lang === "ar" ? "ar-SA" : "en-US"; - const dayName = new Intl.DateTimeFormat(dateLocale, { weekday: "long" }).format(time); - const hijriDate = new Intl.DateTimeFormat( - lang === "ar" ? "ar-SA-u-ca-islamic-umalqura" : "en-US-u-ca-islamic-umalqura", - { day: "numeric", month: "long", year: "numeric" }, - ).format(time); - const gregorianDate = new Intl.DateTimeFormat( - lang === "ar" ? "ar-SA-u-ca-gregory" : "en-US", - { day: "numeric", month: "long", year: "numeric" }, - ).format(time); + const clock = formatTime(time, lang); + const dayName = formatWeekday(time, lang); + const hijriDate = formatHijri(time, lang); + const gregorianDate = formatDate(time, lang); const unreadCount = notifications?.filter((n) => !n.isRead).length ?? stats?.unreadNotifications ?? 0; const displayName = lang === "ar" - ? (user?.displayNameAr ?? user?.username) - : (user?.displayNameEn ?? user?.username); + ? (user?.displayNameAr ?? user?.username ?? "") + : (user?.displayNameEn ?? user?.username ?? ""); const isAdmin = user?.roles?.includes("admin") ?? false; + const greeting = t(`home.greeting.${greetingKey(time)}`, { name: displayName }); + const handleLogout = () => { logout.mutate(undefined, { onSuccess: () => { @@ -191,52 +239,63 @@ export default function HomePage() { const dockApps = (orderedApps ?? apps)?.filter((a) => ["services", "chat", "notifications", "admin"].includes(a.slug)) ?? []; + const initials = (displayName || "?").trim().slice(0, 1).toUpperCase(); + return (
{/* Status Bar */} -
-
- {displayName} +
+ {/* Identity */} +
+
+ {initials} +
+
+
{displayName}
+ {isAdmin && ( +
Admin
+ )} +
-
-
- {time.toLocaleTimeString(dateLocale, { - hour: "2-digit", - minute: "2-digit", - })} + {/* Clock + dates */} +
+
+ {clock}
-
+
{dayName} - {hijriDate} + {hijriDate}
-
+
{gregorianDate}
-
+ {/* Controls */} +
@@ -244,59 +303,97 @@ export default function HomePage() {
{/* Main Content */} -
+
+ {/* Greeting */} +
+

+ {greeting} +

+

+ {dayName} · {gregorianDate} +

+
+ {/* Stats Row — admin only */} {isAdmin && stats && ( -
-
-
{stats.totalApps}
-
{t("home.myApps")}
-
-
-
{stats.totalServices}
-
{t("nav.services")}
-
-
-
{stats.unreadMessages}
-
{t("nav.chat")}
-
-
-
{stats.unreadNotifications}
-
{t("nav.notifications")}
-
+
+ + + +
)} {/* Apps Grid */}
-

{t("home.myApps")}

- - -
- {orderedApps?.map((app) => ( - setLocation(app.route)} - /> - ))} +
+

+ {t("home.myApps")} +

+ {orderedApps && orderedApps.length > 0 && ( + + {formatNumber(orderedApps.length, lang)} + + )} +
+ + {orderedApps && orderedApps.length === 0 ? ( +
+
+
- - +
{t("home.noApps")}
+
{t("home.noAppsHint")}
+
+ ) : ( + + +
+ {orderedApps?.map((app) => ( + setLocation(app.route)} + /> + ))} +
+
+
+ )}
{/* Bottom Dock */} -
-
+
+
{dockApps.slice(0, 4).map((app) => { - const Icon = (LucideIcons as unknown as Record>)[app.iconName] - ?? LucideIcons.Grid2X2; + const Icon = resolveIcon(app.iconName); + const showBadge = app.slug === "notifications" && unreadCount > 0; return ( ); })} diff --git a/artifacts/teaboy-os/src/pages/notifications.tsx b/artifacts/teaboy-os/src/pages/notifications.tsx index 3ae410e7..99cbc872 100644 --- a/artifacts/teaboy-os/src/pages/notifications.tsx +++ b/artifacts/teaboy-os/src/pages/notifications.tsx @@ -10,6 +10,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { ArrowRight, ArrowLeft, Bell, CheckCheck } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; +import { formatDateTime } from "@/lib/i18n-format"; export default function NotificationsPage() { const { t, i18n } = useTranslation(); @@ -113,7 +114,7 @@ export default function NotificationsPage() { )}
- {new Date(n.createdAt).toLocaleString(lang === "ar" ? "ar-SA" : "en-US")} + {formatDateTime(n.createdAt, lang)}
);