From 284cb751edfcea06e0287e5ff46353a92f5e1953 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Mon, 20 Apr 2026 16:40:49 +0000 Subject: [PATCH] Add per-user clock style preference for the home status bar. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original task #20: Let each user pick their own home-screen clock style (analog/digital/minimal/etc.), persisted on the user record and restored on login / other devices. Default = "full". Changes: - DB: added `clock_style varchar(30)` (nullable) to `users` (lib/db/src/schema/users.ts) and applied via direct ALTER TABLE (drizzle-kit push had unrelated interactive prompts about app_opens / user_sessions which were not safe to answer). - OpenAPI: added `ClockStyle` enum (full/digital/digital-no-seconds /analog/minimal), `UpdateClockStyleBody`, exposed `clockStyle` on AuthUser and UserProfile, and added PATCH /auth/me/clock-style. Regenerated typed client and zod schemas. - API: `buildAuthUser` and `buildUserProfile` include `clockStyle`; new authenticated endpoint updates the current user's style and returns the refreshed AuthUser. - Frontend: new `Clock` component (artifacts/teaboy-os/src/ components/clock.tsx) with five variants sharing a single `useNow` tick hook, plus an SVG analog clock; honors existing Latin-digit/locale formatting helpers. New `ClockStylePicker` (popover) shown in the status bar with a live preview for each option and optimistic update through the AuthUser query cache. - home.tsx replaces the hard-coded clock block with `` driven by `user.clockStyle`; trigger button placed next to the language toggle. - i18n: added `home.clockStyle.label` and per-style option labels to en.json and ar.json. Verified via e2e: register → default "full" rendered → switch to analog → reload → analog persists → switch to minimal → time-only renders. RTL layout + Latin digits both correct after language toggle. Replit-Task-Id: 475a7439-b357-400d-805f-5f2fda20ed24 --- artifacts/api-server/src/routes/auth.ts | 24 +++ artifacts/api-server/src/routes/users.ts | 1 + artifacts/teaboy-os/public/opengraph.jpg | Bin 25939 -> 25906 bytes .../src/components/clock-style-picker.tsx | 102 +++++++++ artifacts/teaboy-os/src/components/clock.tsx | 197 ++++++++++++++++++ artifacts/teaboy-os/src/locales/ar.json | 10 + artifacts/teaboy-os/src/locales/en.json | 10 + artifacts/teaboy-os/src/pages/home.tsx | 28 +-- .../src/generated/api.schemas.ts | 20 ++ lib/api-client-react/src/generated/api.ts | 87 ++++++++ lib/api-spec/openapi.yaml | 49 +++++ lib/api-zod/src/generated/api.ts | 83 ++++++++ lib/db/src/schema/users.ts | 1 + replit.md | 2 +- 14 files changed, 591 insertions(+), 23 deletions(-) create mode 100644 artifacts/teaboy-os/src/components/clock-style-picker.tsx create mode 100644 artifacts/teaboy-os/src/components/clock.tsx diff --git a/artifacts/api-server/src/routes/auth.ts b/artifacts/api-server/src/routes/auth.ts index 122d332d..312a184e 100644 --- a/artifacts/api-server/src/routes/auth.ts +++ b/artifacts/api-server/src/routes/auth.ts @@ -15,6 +15,7 @@ import { RegisterBody, LoginBody, UpdateLanguageBody, + UpdateClockStyleBody, ForgotPasswordBody, ResetPasswordBody, VerifyResetTokenBody, @@ -36,6 +37,7 @@ function buildAuthUser(user: typeof usersTable.$inferSelect, roles: string[]) { displayNameAr: user.displayNameAr, displayNameEn: user.displayNameEn, preferredLanguage: user.preferredLanguage, + clockStyle: user.clockStyle, avatarUrl: user.avatarUrl, isActive: user.isActive, roles, @@ -324,4 +326,26 @@ router.patch("/auth/me/language", requireAuth, async (req, res): Promise = res.json(buildAuthUser(user, roles)); }); +router.patch("/auth/me/clock-style", requireAuth, async (req, res): Promise => { + const parsed = UpdateClockStyleBody.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: parsed.error.message }); + return; + } + + const [user] = await db + .update(usersTable) + .set({ clockStyle: parsed.data.clockStyle ?? null }) + .where(eq(usersTable.id, req.session.userId!)) + .returning(); + + if (!user) { + res.status(401).json({ error: "User not found" }); + return; + } + + const roles = await getUserRoles(user.id); + res.json(buildAuthUser(user, roles)); +}); + export default router; diff --git a/artifacts/api-server/src/routes/users.ts b/artifacts/api-server/src/routes/users.ts index dacd50d0..ed42c81d 100644 --- a/artifacts/api-server/src/routes/users.ts +++ b/artifacts/api-server/src/routes/users.ts @@ -37,6 +37,7 @@ function buildUserProfile(user: typeof usersTable.$inferSelect, roles: string[]) displayNameAr: user.displayNameAr, displayNameEn: user.displayNameEn, preferredLanguage: user.preferredLanguage, + clockStyle: user.clockStyle, avatarUrl: user.avatarUrl, isActive: user.isActive, roles, diff --git a/artifacts/teaboy-os/public/opengraph.jpg b/artifacts/teaboy-os/public/opengraph.jpg index 7168fb9524662ce301eeaa44d41f827b474332f2..70c131e1a6161e00094d46744458c533d5f8edfa 100644 GIT binary patch literal 25906 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 N1gIxz#09VJ{SUYL5zYVr diff --git a/artifacts/teaboy-os/src/components/clock-style-picker.tsx b/artifacts/teaboy-os/src/components/clock-style-picker.tsx new file mode 100644 index 00000000..3af0c9b3 --- /dev/null +++ b/artifacts/teaboy-os/src/components/clock-style-picker.tsx @@ -0,0 +1,102 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useQueryClient } from "@tanstack/react-query"; +import { + useUpdateClockStyle, + getGetMeQueryKey, + type AuthUser, + type ClockStyle as ClockStyleType, +} from "@workspace/api-client-react"; +import { Clock as ClockIcon, Check } from "lucide-react"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Clock, CLOCK_STYLES, resolveClockStyle, useNow } from "@/components/clock"; + +type Props = { + currentStyle: ClockStyleType | null | undefined; +}; + +export function ClockStylePicker({ currentStyle }: Props) { + const { t, i18n: i18nInstance } = useTranslation(); + const lang = i18nInstance.language; + const queryClient = useQueryClient(); + const now = useNow(); + const [open, setOpen] = useState(false); + + const updateClockStyle = useUpdateClockStyle(); + const active = resolveClockStyle(currentStyle); + + const choose = (style: ClockStyleType) => { + const previous = queryClient.getQueryData(getGetMeQueryKey()); + if (previous) { + queryClient.setQueryData(getGetMeQueryKey(), { + ...previous, + clockStyle: style, + }); + } + updateClockStyle.mutate( + { data: { clockStyle: style } }, + { + onSuccess: (data) => { + queryClient.setQueryData(getGetMeQueryKey(), data); + }, + onError: () => { + if (previous) { + queryClient.setQueryData(getGetMeQueryKey(), previous); + } else { + queryClient.invalidateQueries({ queryKey: getGetMeQueryKey() }); + } + }, + }, + ); + setOpen(false); + }; + + return ( + + + + + +
+ {t("home.clockStyle.label")} +
+
+ {CLOCK_STYLES.map((style) => { + const isActive = style === active; + return ( + + ); + })} +
+
+
+ ); +} diff --git a/artifacts/teaboy-os/src/components/clock.tsx b/artifacts/teaboy-os/src/components/clock.tsx new file mode 100644 index 00000000..936a955b --- /dev/null +++ b/artifacts/teaboy-os/src/components/clock.tsx @@ -0,0 +1,197 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import type { ClockStyle } from "@workspace/api-client-react"; +import { + formatTime, + formatDate, + formatHijri, + formatWeekday, +} from "@/lib/i18n-format"; + +export const CLOCK_STYLES: ClockStyle[] = [ + "full", + "digital", + "digital-no-seconds", + "analog", + "minimal", +]; + +export const DEFAULT_CLOCK_STYLE: ClockStyle = "full"; + +export function resolveClockStyle(value: ClockStyle | null | undefined): ClockStyle { + if (!value) return DEFAULT_CLOCK_STYLE; + return CLOCK_STYLES.includes(value) ? value : DEFAULT_CLOCK_STYLE; +} + +export function useNow(intervalMs = 1000): Date { + const [now, setNow] = useState(() => new Date()); + useEffect(() => { + const t = setInterval(() => setNow(new Date()), intervalMs); + return () => clearInterval(t); + }, [intervalMs]); + return now; +} + +type ClockProps = { + style: ClockStyle | null | undefined; + time: Date; + lang: string; + size?: "sm" | "md"; +}; + +function AnalogClock({ time, size = "md" }: { time: Date; size?: "sm" | "md" }) { + const hours = time.getHours() % 12; + const minutes = time.getMinutes(); + const seconds = time.getSeconds(); + + const hourAngle = (hours + minutes / 60) * 30; + const minuteAngle = (minutes + seconds / 60) * 6; + const secondAngle = seconds * 6; + + const px = size === "sm" ? 38 : 52; + const center = px / 2; + + const ticks = Array.from({ length: 12 }, (_, i) => { + const angle = (i * 30 * Math.PI) / 180; + const outer = center - 2; + const inner = center - (i % 3 === 0 ? 6 : 4); + const x1 = center + Math.sin(angle) * inner; + const y1 = center - Math.cos(angle) * inner; + const x2 = center + Math.sin(angle) * outer; + const y2 = center - Math.cos(angle) * outer; + return ( + + ); + }); + + const hand = (angle: number, length: number, width: number, color: string, opacity = 1) => { + const rad = (angle * Math.PI) / 180; + const x = center + Math.sin(rad) * length; + const y = center - Math.cos(rad) * length; + return ( + + ); + }; + + return ( + + + {ticks} + {hand(hourAngle, center - 14, 2.2, "currentColor", 0.95)} + {hand(minuteAngle, center - 8, 1.6, "currentColor", 0.85)} + {hand(secondAngle, center - 6, 1, "hsl(var(--primary))", 0.95)} + + + ); +} + +export function Clock({ style, time, lang, size = "md" }: ClockProps) { + const resolved = resolveClockStyle(style); + const dayName = formatWeekday(time, lang); + const hijriDate = formatHijri(time, lang); + const gregorianDate = formatDate(time, lang); + const timeNoSec = formatTime(time, lang, { hour: "2-digit", minute: "2-digit" }); + const timeWithSec = formatTime(time, lang, { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); + + const timeFontClass = + size === "sm" + ? "font-mono text-sm font-bold tabular-nums tracking-tight" + : "font-mono text-base sm:text-lg font-bold tabular-nums tracking-tight"; + + if (resolved === "analog") { + return ( +
+ +
+
{timeNoSec}
+
+ {dayName} +
+
+
+ ); + } + + if (resolved === "minimal") { + return ( +
+
{timeNoSec}
+
+ ); + } + + if (resolved === "digital-no-seconds") { + return ( +
+
{timeNoSec}
+
+ {gregorianDate} +
+
+ ); + } + + if (resolved === "digital") { + return ( +
+
{timeWithSec}
+
+ {gregorianDate} +
+
+ ); + } + + // full (default) + return ( +
+
{timeNoSec}
+
+ {dayName} + + {hijriDate} +
+
+ {gregorianDate} +
+
+ ); +} diff --git a/artifacts/teaboy-os/src/locales/ar.json b/artifacts/teaboy-os/src/locales/ar.json index cb696b7c..ae367d0e 100644 --- a/artifacts/teaboy-os/src/locales/ar.json +++ b/artifacts/teaboy-os/src/locales/ar.json @@ -77,6 +77,16 @@ "services": "الخدمات", "messages": "الرسائل", "alerts": "التنبيهات" + }, + "clockStyle": { + "label": "نمط الساعة", + "options": { + "full": "كامل (الوقت واليوم والتقويمان الهجري والميلادي)", + "digital": "رقمي مع الثواني", + "digital-no-seconds": "رقمي بدون ثواني", + "analog": "ساعة عقاربية", + "minimal": "مبسّط (الوقت فقط)" + } } }, "services": { diff --git a/artifacts/teaboy-os/src/locales/en.json b/artifacts/teaboy-os/src/locales/en.json index 4add5209..1ecd3de2 100644 --- a/artifacts/teaboy-os/src/locales/en.json +++ b/artifacts/teaboy-os/src/locales/en.json @@ -77,6 +77,16 @@ "services": "Services", "messages": "Messages", "alerts": "Alerts" + }, + "clockStyle": { + "label": "Clock style", + "options": { + "full": "Full (time, weekday, Hijri & Gregorian)", + "digital": "Digital with seconds", + "digital-no-seconds": "Digital without seconds", + "analog": "Analog clock", + "minimal": "Minimal (time only)" + } } }, "services": { diff --git a/artifacts/teaboy-os/src/pages/home.tsx b/artifacts/teaboy-os/src/pages/home.tsx index 703a5e0a..89dce112 100644 --- a/artifacts/teaboy-os/src/pages/home.tsx +++ b/artifacts/teaboy-os/src/pages/home.tsx @@ -47,13 +47,13 @@ import { } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { - formatTime, formatDate, - formatHijri, formatWeekday, formatNumber, greetingKey, } from "@/lib/i18n-format"; +import { Clock, useNow } from "@/components/clock"; +import { ClockStylePicker } from "@/components/clock-style-picker"; type IconName = keyof typeof LucideIcons; function isIconName(x: string): x is IconName { @@ -148,7 +148,7 @@ export default function HomePage() { const [, setLocation] = useLocation(); const { user } = useAuth(); const queryClient = useQueryClient(); - const [time, setTime] = useState(new Date()); + const time = useNow(); const lang = i18nInstance.language; const { data: apps } = useListApps({ query: { queryKey: getListAppsQueryKey() } }); @@ -214,14 +214,7 @@ export default function HomePage() { setLocation(app.route); }; - useEffect(() => { - const timer = setInterval(() => setTime(new Date()), 1000); - return () => clearInterval(timer); - }, []); - - 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; @@ -274,22 +267,13 @@ export default function HomePage() { {/* Clock + dates */} -
-
- {clock} -
-
- {dayName} - - {hijriDate} -
-
- {gregorianDate} -
+
+
{/* Controls */}
+