From 1b780ed127c28ad67501e2224cb8237af2b7b756 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Thu, 2 Apr 2026 22:00:26 +0200 Subject: [PATCH] refactor: use date times with hooks everywhere --- app/client/lib/__tests__/datetime.test.ts | 16 ++++++------- app/client/lib/datetime.ts | 24 +++++++++++++------ .../routes/repository-details.tsx | 3 ++- app/client/modules/repositories/tabs/info.tsx | 3 ++- .../modules/settings/routes/settings.tsx | 13 ++++------ .../modules/volumes/routes/volume-details.tsx | 3 ++- 6 files changed, 35 insertions(+), 27 deletions(-) diff --git a/app/client/lib/__tests__/datetime.test.ts b/app/client/lib/__tests__/datetime.test.ts index 10580407..12ed04f5 100644 --- a/app/client/lib/__tests__/datetime.test.ts +++ b/app/client/lib/__tests__/datetime.test.ts @@ -1,6 +1,11 @@ import { afterEach, describe, expect, test, vi } from "vitest"; -import { - DEFAULT_TIME_FORMAT, +import { DEFAULT_TIME_FORMAT, inferDateTimePreferences, rawFormatters } from "../datetime"; + +afterEach(() => { + vi.restoreAllMocks(); +}); + +const { formatDate, formatDateTime, formatDateWithMonth, @@ -8,12 +13,7 @@ import { formatShortDateTime, formatTime, formatTimeAgo, - inferDateTimePreferences, -} from "../datetime"; - -afterEach(() => { - vi.restoreAllMocks(); -}); +} = rawFormatters; const sampleDate = new Date("2026-01-10T14:30:00.000Z"); diff --git a/app/client/lib/datetime.ts b/app/client/lib/datetime.ts index 8c212390..f77bf4be 100644 --- a/app/client/lib/datetime.ts +++ b/app/client/lib/datetime.ts @@ -149,7 +149,7 @@ export function inferDateTimePreferences(locale?: string) { } // 1/10/2026, 2:30 PM -export function formatDateTime(date: DateInput, options: DateFormatOptions = {}): string { +function formatDateTime(date: DateInput, options: DateFormatOptions = {}): string { return formatValidDate( date, (validDate) => `${formatConfiguredDate(validDate, options, true)}, ${formatConfiguredTime(validDate, options)}`, @@ -157,22 +157,22 @@ export function formatDateTime(date: DateInput, options: DateFormatOptions = {}) } // Jan 10, 2026 -export function formatDateWithMonth(date: DateInput, options: DateFormatOptions = {}): string { +function formatDateWithMonth(date: DateInput, options: DateFormatOptions = {}): string { return formatValidDate(date, (validDate) => formatConfiguredDateWithMonth(validDate, options)); } // 1/10/2026 -export function formatDate(date: DateInput, options: DateFormatOptions = {}): string { +function formatDate(date: DateInput, options: DateFormatOptions = {}): string { return formatValidDate(date, (validDate) => formatConfiguredDate(validDate, options, true)); } // 1/10 -export function formatShortDate(date: DateInput, options: DateFormatOptions = {}): string { +function formatShortDate(date: DateInput, options: DateFormatOptions = {}): string { return formatValidDate(date, (validDate) => formatConfiguredDate(validDate, options, false)); } // 1/10, 2:30 PM -export function formatShortDateTime(date: DateInput, options: DateFormatOptions = {}): string { +function formatShortDateTime(date: DateInput, options: DateFormatOptions = {}): string { return formatValidDate( date, (validDate) => `${formatConfiguredDate(validDate, options, false)}, ${formatConfiguredTime(validDate, options)}`, @@ -180,12 +180,12 @@ export function formatShortDateTime(date: DateInput, options: DateFormatOptions } // 2:30 PM -export function formatTime(date: DateInput, options: DateFormatOptions = {}): string { +function formatTime(date: DateInput, options: DateFormatOptions = {}): string { return formatValidDate(date, (validDate) => formatConfiguredTime(validDate, options)); } // 5 minutes ago -export function formatTimeAgo(date: DateInput, now = Date.now()): string { +function formatTimeAgo(date: DateInput, now = Date.now()): string { return formatValidDate(date, (validDate) => { if (Math.abs(now - validDate.getTime()) < 120_000) { return "just now"; @@ -222,3 +222,13 @@ export function useTimeFormat() { [locale, timeZone, currentNow, dateFormat, timeFormat], ); } + +export const rawFormatters = { + formatDateTime, + formatDateWithMonth, + formatDate, + formatShortDate, + formatShortDateTime, + formatTime, + formatTimeAgo, +}; diff --git a/app/client/modules/repositories/routes/repository-details.tsx b/app/client/modules/repositories/routes/repository-details.tsx index 14b80360..4e79a73f 100644 --- a/app/client/modules/repositories/routes/repository-details.tsx +++ b/app/client/modules/repositories/routes/repository-details.tsx @@ -31,13 +31,13 @@ import { } from "~/client/components/ui/dropdown-menu"; import { Separator } from "~/client/components/ui/separator"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs"; -import { formatDateTime, formatTimeAgo } from "~/client/lib/datetime"; import { parseError } from "~/client/lib/errors"; import { cn } from "~/client/lib/utils"; import type { BackupSchedule, Snapshot } from "~/client/lib/types"; import type { GetRepositoryStatsResponse } from "~/client/api-client/types.gen"; import { RepositoryInfoTabContent } from "../tabs/info"; import { RepositorySnapshotsTabContent } from "../tabs/snapshots"; +import { useTimeFormat } from "~/client/lib/datetime"; export default function RepositoryDetailsPage({ repositoryId, @@ -50,6 +50,7 @@ export default function RepositoryDetailsPage({ initialBackupSchedules?: BackupSchedule[]; initialStats?: GetRepositoryStatsResponse; }) { + const { formatDateTime, formatTimeAgo } = useTimeFormat(); const navigate = useNavigate(); const { tab } = useSearch({ from: "/(dashboard)/repositories/$repositoryId/" }); const activeTab = tab || "info"; diff --git a/app/client/modules/repositories/tabs/info.tsx b/app/client/modules/repositories/tabs/info.tsx index 02a0af9e..89c9b144 100644 --- a/app/client/modules/repositories/tabs/info.tsx +++ b/app/client/modules/repositories/tabs/info.tsx @@ -2,11 +2,11 @@ import { Archive, Clock, FolderOpen, HardDrive, Lock, Settings, Shield } from "l import { Card, CardContent, CardTitle } from "~/client/components/ui/card"; import type { Repository } from "~/client/lib/types"; import type { GetRepositoryStatsResponse } from "~/client/api-client/types.gen"; -import { formatDateTime, formatTimeAgo } from "~/client/lib/datetime"; import type { RepositoryConfig } from "@zerobyte/core/restic"; import { DoctorReport } from "../components/doctor-report"; import { CompressionStatsChart } from "../components/compression-stats-chart"; import { cn } from "~/client/lib/utils"; +import { useTimeFormat } from "~/client/lib/datetime"; type Props = { repository: Repository; @@ -33,6 +33,7 @@ function ConfigRow({ icon, label, value, mono, valueClassName }: ConfigRowProps) export const RepositoryInfoTabContent = ({ repository, initialStats }: Props) => { const effectiveLocalPath = getEffectiveLocalPath(repository); + const { formatDateTime, formatTimeAgo } = useTimeFormat(); const config = repository.config as RepositoryConfig; const hasLocalPath = Boolean(effectiveLocalPath); diff --git a/app/client/modules/settings/routes/settings.tsx b/app/client/modules/settings/routes/settings.tsx index fb30ef9e..dfe3c5be 100644 --- a/app/client/modules/settings/routes/settings.tsx +++ b/app/client/modules/settings/routes/settings.tsx @@ -24,9 +24,9 @@ import { authClient } from "~/client/lib/auth-client"; import { DATE_FORMATS, type DateFormatPreference, - formatDateTime, TIME_FORMATS, type TimeFormatPreference, + useTimeFormat, } from "~/client/lib/datetime"; import { logger } from "~/client/lib/logger"; import { type AppContext } from "~/context"; @@ -50,7 +50,7 @@ export function SettingsPage({ appContext, initialMembers, initialSsoSettings, i const [downloadDialogOpen, setDownloadDialogOpen] = useState(false); const [downloadPassword, setDownloadPassword] = useState(""); const [isChangingPassword, setIsChangingPassword] = useState(false); - const { locale, dateFormat, timeFormat } = useRootLoaderData(); + const { dateFormat, timeFormat } = useRootLoaderData(); const { tab } = useSearch({ from: "/(dashboard)/settings/" }); const activeTab = tab || "account"; @@ -58,12 +58,7 @@ export function SettingsPage({ appContext, initialMembers, initialSsoSettings, i const navigate = useNavigate(); const { activeMember, activeOrganization } = useOrganizationContext(); const isOrgAdmin = activeMember?.role === "owner" || activeMember?.role === "admin"; - const dateTimePreview = formatDateTime("2026-01-10T14:30:00.000Z", { - locale, - timeZone: "UTC", - dateFormat, - timeFormat, - }); + const { formatDateTime } = useTimeFormat(); const handleLogout = async () => { await authClient.signOut({ @@ -275,7 +270,7 @@ export function SettingsPage({ appContext, initialMembers, initialSsoSettings, i -

Preview: {dateTimePreview}

+

Preview: {formatDateTime(new Date())}

diff --git a/app/client/modules/volumes/routes/volume-details.tsx b/app/client/modules/volumes/routes/volume-details.tsx index 68fd18a8..2a35bec4 100644 --- a/app/client/modules/volumes/routes/volume-details.tsx +++ b/app/client/modules/volumes/routes/volume-details.tsx @@ -35,16 +35,17 @@ import { Separator } from "~/client/components/ui/separator"; import { Switch } from "~/client/components/ui/switch"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs"; import { ManagedBadge } from "~/client/components/managed-badge"; -import { formatDateTime, formatTimeAgo } from "~/client/lib/datetime"; import { parseError } from "~/client/lib/errors"; import { cn } from "~/client/lib/utils"; import { VolumeInfoTabContent } from "../tabs/info"; import { FilesTabContent } from "../tabs/files"; +import { useTimeFormat } from "~/client/lib/datetime"; export function VolumeDetails({ volumeId }: { volumeId: string }) { const navigate = useNavigate(); const searchParams = useSearch({ from: "/(dashboard)/volumes/$volumeId/" }); const activeTab = searchParams.tab || "info"; + const { formatDateTime, formatTimeAgo } = useTimeFormat(); const { data } = useSuspenseQuery({ ...getVolumeOptions({ path: { shortId: volumeId } }),