feat: landing page + logo, dev-mode banner + sync cap, non-blocking sync with progress splash

This commit is contained in:
cesnimda
2026-06-30 16:58:35 +02:00
parent dcb939e4f2
commit fe5920919f
29 changed files with 552 additions and 39 deletions
+27 -4
View File
@@ -1,8 +1,9 @@
import { useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import GridLayout from 'react-grid-layout';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import { AnalyticsApi, LayoutApi, ExportApi } from '../api/client.js';
import { AnalyticsApi, LayoutApi, ExportApi, SyncApi } from '../api/client.js';
import SyncSplash from '../components/SyncSplash.jsx';
import {
HealthWidget, StatCard, TopSendersWidget, VolumeWidget,
HeatmapWidget, AttachmentsWidget, StorageWidget
@@ -26,16 +27,37 @@ export default function Dashboard() {
const [data, setData] = useState(null);
const [layout, setLayout] = useState(DEFAULT_LAYOUT);
const [hidden, setHidden] = useState([]);
const [syncing, setSyncing] = useState(false);
const loadData = useCallback(() => {
AnalyticsApi.dashboard().then(setData).catch(() => {});
}, []);
useEffect(() => {
AnalyticsApi.dashboard().then(setData).catch(() => {});
LayoutApi.get().then((saved) => {
if (saved?.length) {
setLayout(saved.map((w) => ({ i: w.widgetKey, x: w.x, y: w.y, w: w.w, h: w.h })));
setHidden(saved.filter((w) => !w.visible).map((w) => w.widgetKey));
}
}).catch(() => {});
}, []);
// Decide whether to show the loading splash: if a sync is running, watch it;
// if the inbox has never been synced, kick one off automatically.
SyncApi.status().then((s) => {
if (s.isRunning) {
setSyncing(true);
} else if (!s.lastSuccessfulSyncUtc) {
SyncApi.incremental().then(() => setSyncing(true)).catch(loadData);
} else {
loadData();
}
}).catch(loadData);
}, [loadData]);
const onSyncDone = useCallback(() => {
setSyncing(false);
loadData();
}, [loadData]);
const persist = (nextLayout, nextHidden) => {
const dto = nextLayout.map((l, idx) => ({
@@ -69,6 +91,7 @@ export default function Dashboard() {
return (
<div className="dashboard">
{syncing && <SyncSplash onDone={onSyncDone} />}
<div className="toolbar">
<div className="widget-toggles">
{ALL_WIDGETS.map((k) => (