diff --git a/app/client/modules/repositories/routes/snapshot-details.tsx b/app/client/modules/repositories/routes/snapshot-details.tsx
index 1d1d14c5..43a6f301 100644
--- a/app/client/modules/repositories/routes/snapshot-details.tsx
+++ b/app/client/modules/repositories/routes/snapshot-details.tsx
@@ -8,7 +8,7 @@ import type { GetSnapshotDetailsResponse } from "~/client/api-client/types.gen";
import { Button } from "~/client/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
import { SnapshotFileBrowser } from "~/client/modules/backups/components/snapshot-file-browser";
-import { formatDateTime } from "~/client/lib/datetime";
+import { useTimeFormat } from "~/client/lib/datetime";
import { BackupSummaryCard } from "~/client/components/backup-summary-card";
import { useState } from "react";
import { Database } from "lucide-react";
@@ -58,6 +58,7 @@ type Props = {
export function SnapshotDetailsPage({ repositoryId, snapshotId, initialSnapshot }: Props) {
const [showAllPaths, setShowAllPaths] = useState(false);
+ const { formatDateTime } = useTimeFormat();
const { data: repository } = useSuspenseQuery({
...getRepositoryOptions({ path: { shortId: repositoryId } }),
diff --git a/app/client/modules/repositories/tabs/info.tsx b/app/client/modules/repositories/tabs/info.tsx
index 6b73e69b..fdaae655 100644
--- a/app/client/modules/repositories/tabs/info.tsx
+++ b/app/client/modules/repositories/tabs/info.tsx
@@ -15,7 +15,6 @@ import {
} from "~/client/components/ui/alert-dialog";
import type { Repository } from "~/client/lib/types";
import type { GetRepositoryStatsResponse } from "~/client/api-client/types.gen";
-import { formatDateTime, formatTimeAgo } from "~/client/lib/datetime";
import {
cancelDoctorMutation,
deleteRepositoryMutation,
@@ -23,6 +22,8 @@ import {
unlockRepositoryMutation,
} from "~/client/api-client/@tanstack/react-query.gen";
import type { RepositoryConfig } from "@zerobyte/core/restic";
+import { TimeAgo } from "~/client/components/time-ago";
+import { useTimeFormat } from "~/client/lib/datetime";
import { DoctorReport } from "../components/doctor-report";
import { parseError } from "~/client/lib/errors";
import { useNavigate } from "@tanstack/react-router";
@@ -41,6 +42,7 @@ const getEffectiveLocalPath = (repository: Repository): string | null => {
};
export const RepositoryInfoTabContent = ({ repository, initialStats }: Props) => {
+ const { formatDateTime } = useTimeFormat();
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const navigate = useNavigate();
@@ -206,7 +208,7 @@ export const RepositoryInfoTabContent = ({ repository, initialStats }: Props) =>
Last Checked
-
{formatTimeAgo(repository.lastChecked)}
+
{hasLocalPath && (
diff --git a/app/client/modules/sso/components/sso-settings-section.tsx b/app/client/modules/sso/components/sso-settings-section.tsx
index 6b5adcad..ca424247 100644
--- a/app/client/modules/sso/components/sso-settings-section.tsx
+++ b/app/client/modules/sso/components/sso-settings-section.tsx
@@ -29,7 +29,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~
import { Switch } from "~/client/components/ui/switch";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table";
import { useOrganizationContext } from "~/client/hooks/use-org-context";
-import { formatDateWithMonth } from "~/client/lib/datetime";
+import { useTimeFormat } from "~/client/lib/datetime";
import { getOrigin } from "~/client/functions/get-origin";
import { authClient } from "~/client/lib/auth-client";
import { cn } from "~/client/lib/utils";
@@ -47,6 +47,7 @@ export function SsoSettingsSection({ initialSettings, initialOrigin }: Props) {
const { data } = useSuspenseQuery({ queryKey: ["app-origin"], queryFn: originQuery, initialData: initialOrigin });
const navigate = useNavigate();
+ const { formatDateWithMonth } = useTimeFormat();
const { activeOrganization } = useOrganizationContext();
const [inviteEmail, setInviteEmail] = useState("");
const [inviteRole, setInviteRole] = useState
("member");
diff --git a/app/client/modules/volumes/components/healthchecks-card.tsx b/app/client/modules/volumes/components/healthchecks-card.tsx
index 36ead717..de3f6ad2 100644
--- a/app/client/modules/volumes/components/healthchecks-card.tsx
+++ b/app/client/modules/volumes/components/healthchecks-card.tsx
@@ -5,8 +5,8 @@ import { healthCheckVolumeMutation, updateVolumeMutation } from "~/client/api-cl
import { OnOff } from "~/client/components/onoff";
import { Button } from "~/client/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
-import { formatTimeAgo } from "~/client/lib/datetime";
import type { Volume } from "~/client/lib/types";
+import { TimeAgo } from "~/client/components/time-ago";
type Props = {
volume: Volume;
@@ -53,9 +53,11 @@ export const HealthchecksCard = ({ volume }: Props) => {
{volume.lastError && {volume.lastError}}
{volume.status === "mounted" && Healthy}
{volume.status !== "unmounted" && (
- Checked {formatTimeAgo(volume.lastHealthCheck)}
+
+ Checked
+
)}
-
+
Remount on error
{
enabledLabel="Enabled"
disabledLabel="Paused"
/>
-
+
{volume.status !== "unmounted" && (
diff --git a/app/client/modules/volumes/tabs/info.tsx b/app/client/modules/volumes/tabs/info.tsx
index b6bebcc1..d01dd98e 100644
--- a/app/client/modules/volumes/tabs/info.tsx
+++ b/app/client/modules/volumes/tabs/info.tsx
@@ -81,9 +81,9 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
const deleteVol = useMutation({
...deleteVolumeMutation(),
- onSuccess: () => {
+ onSuccess: async () => {
toast.success("Volume deleted successfully");
- void navigate({ to: "/volumes" });
+ await navigate({ to: "/volumes" });
},
onError: (error) => {
toast.error("Failed to delete volume", {
diff --git a/app/routes/__root.tsx b/app/routes/__root.tsx
index 7938de8c..d95d4cdb 100644
--- a/app/routes/__root.tsx
+++ b/app/routes/__root.tsx
@@ -8,7 +8,7 @@ import { useServerEvents } from "~/client/hooks/use-server-events";
import { useEffect } from "react";
import { ThemeProvider, THEME_COOKIE_NAME } from "~/client/components/theme-provider";
import { createServerFn } from "@tanstack/react-start";
-import { getCookie } from "@tanstack/react-start/server";
+import { getCookie, getRequestHeaders } from "@tanstack/react-start/server";
import { isAuthRoute } from "~/lib/auth-routes";
const fetchTheme = createServerFn({ method: "GET" }).handler(async () => {
@@ -16,13 +16,22 @@ const fetchTheme = createServerFn({ method: "GET" }).handler(async () => {
return (themeCookie === "light" ? "light" : "dark") as "light" | "dark";
});
+const fetchTimeConfig = createServerFn({ method: "GET" }).handler(async () => {
+ const acceptLanguage = getRequestHeaders().get("accept-language");
+
+ return {
+ locale: (acceptLanguage?.split(",")[0] || "en-US") as string,
+ timeZone: process.env.TZ || Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
+ };
+});
+
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
server: {
middleware: [apiClientMiddleware],
},
loader: async () => {
- const theme = await fetchTheme();
- return { theme };
+ const [theme, timeConfig] = await Promise.all([fetchTheme(), fetchTimeConfig()]);
+ return { theme, ...timeConfig };
},
head: () => ({
meta: [{ title: "Zerobyte - Open Source Backup Solution" }],