Skip to content

Commit 972fd83

Browse files
Merge pull request #18 from offendingcommit/feat/sidebar-last-updated
feat(web): show sidebar last-updated status
2 parents edc498d + 7cf94aa commit 972fd83

1 file changed

Lines changed: 41 additions & 0 deletions

File tree

packages/web/src/components/layout/Sidebar.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useQueryClient } from "@tanstack/react-query";
12
import { Link, useMatchRoute } from "@tanstack/react-router";
23
import { AnimatePresence, motion } from "framer-motion";
34
import {
@@ -39,13 +40,50 @@ const WORKSPACE_SECTIONS = [
3940
{ label: "Webhooks", icon: Webhook, section: "webhooks" },
4041
] as const;
4142

43+
function formatLastUpdated(value: number | null, now = Date.now()): string {
44+
if (!value) return "Not updated yet";
45+
const elapsed = Math.max(0, now - value);
46+
if (elapsed < 10_000) return "Updated just now";
47+
if (elapsed < 60_000) return `Updated ${Math.floor(elapsed / 1000)}s ago`;
48+
if (elapsed < 3_600_000) return `Updated ${Math.floor(elapsed / 60_000)}m ago`;
49+
return `Updated ${Math.floor(elapsed / 3_600_000)}h ago`;
50+
}
51+
52+
function useLastDataUpdate(): string {
53+
const queryClient = useQueryClient();
54+
const [updatedAt, setUpdatedAt] = useState<number | null>(null);
55+
const [now, setNow] = useState(() => Date.now());
56+
57+
useEffect(() => {
58+
function refresh() {
59+
setNow(Date.now());
60+
const latest = queryClient
61+
.getQueryCache()
62+
.getAll()
63+
.reduce((max, query) => Math.max(max, query.state.dataUpdatedAt || 0), 0);
64+
setUpdatedAt(latest || null);
65+
}
66+
67+
refresh();
68+
const unsubscribe = queryClient.getQueryCache().subscribe(refresh);
69+
const interval = window.setInterval(refresh, 30_000);
70+
return () => {
71+
unsubscribe();
72+
window.clearInterval(interval);
73+
};
74+
}, [queryClient]);
75+
76+
return formatLastUpdated(updatedAt, now);
77+
}
78+
4279
export function Sidebar() {
4380
const matchRoute = useMatchRoute();
4481
const { instances, active, activate } = useInstances();
4582
const { theme, toggle } = useTheme();
4683
const { demo, toggle: toggleDemo, mask } = useDemo();
4784
const { showMetadata, toggle: toggleMeta } = useMetadata();
4885
const { data: health } = useHealthStatus();
86+
const lastUpdated = useLastDataUpdate();
4987
const [switcherOpen, setSwitcherOpen] = useState(false);
5088
const switcherRef = useRef<HTMLDivElement | null>(null);
5189

@@ -121,6 +159,9 @@ export function Sidebar() {
121159
<p className="text-xs font-mono truncate" style={{ color: "var(--text-4)" }}>
122160
{mask(active.baseUrl.replace(/^https?:\/\//, ""))}
123161
</p>
162+
<p className="text-[10px] font-mono truncate" style={{ color: "var(--text-4)" }}>
163+
{lastUpdated}
164+
</p>
124165
</div>
125166
{instances.length > 1 && (
126167
<ChevronsUpDown

0 commit comments

Comments
 (0)