feat(pwa): web manifest, icons, read-through entry cache
- Add manifest.webmanifest, generated 192/512 icons, theme-color, apple-touch-icon - Cache entries in localStorage per user; hydrate on load before network refresh - Single info toast when offline on cached data; cache cleared on logout - Fix inaccurate local-first meta description
This commit is contained in:
+35
-15
@@ -51,6 +51,7 @@ import {
|
||||
} from "./lib/userLimits";
|
||||
import { createExcelExport, downloadBlob, parseExcelImport } from "./lib/excel";
|
||||
import { buildEntryDerived, groupByDay, groupByFlavour, groupByWeek, humanDateTime, makeId } from "./lib/metrics";
|
||||
import { clearCachedEntries, readCachedEntries, writeCachedEntries } from "./lib/entryCache";
|
||||
import { buildJsonImportPreview, exportPayload, parseImport } from "./lib/storage";
|
||||
import { useToasts } from "./lib/toasts";
|
||||
import type {
|
||||
@@ -113,6 +114,7 @@ function App() {
|
||||
const jsonFileInputRef = useRef<HTMLInputElement>(null);
|
||||
const importInFlightRef = useRef(false);
|
||||
const refreshDebounceRef = useRef<number | null>(null);
|
||||
const cacheHydratedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(THEME_STORAGE_KEY, normaliseThemeId(themeId));
|
||||
@@ -131,21 +133,31 @@ function App() {
|
||||
return () => media.removeEventListener("change", handleChange);
|
||||
}, []);
|
||||
|
||||
const refreshEntries = useCallback(async (userId: string, showLoader = true) => {
|
||||
if (showLoader) setDataLoading(true);
|
||||
setSyncError("");
|
||||
try {
|
||||
const remoteEntries = await listEntries(userId);
|
||||
setEntries(sortEntries(remoteEntries));
|
||||
setNotice(`Synced ${remoteEntries.length} Appwrite entr${remoteEntries.length === 1 ? "y" : "ies"}.`);
|
||||
} catch (error) {
|
||||
const message = appwriteErrorMessage(error);
|
||||
setSyncError(message);
|
||||
setNotice("Appwrite sync failed.");
|
||||
} finally {
|
||||
if (showLoader) setDataLoading(false);
|
||||
}
|
||||
}, []);
|
||||
const refreshEntries = useCallback(
|
||||
async (userId: string, showLoader = true) => {
|
||||
if (showLoader) setDataLoading(true);
|
||||
setSyncError("");
|
||||
try {
|
||||
const remoteEntries = await listEntries(userId);
|
||||
setEntries(sortEntries(remoteEntries));
|
||||
writeCachedEntries(userId, remoteEntries);
|
||||
cacheHydratedRef.current = false;
|
||||
setNotice(`Synced ${remoteEntries.length} Appwrite entr${remoteEntries.length === 1 ? "y" : "ies"}.`);
|
||||
} catch (error) {
|
||||
if (cacheHydratedRef.current) {
|
||||
// Cached entries are on screen, so one info toast replaces the generic error toast.
|
||||
pushToast({ tone: "info", message: "Couldn't reach the server — showing saved data" });
|
||||
} else {
|
||||
const message = appwriteErrorMessage(error);
|
||||
setSyncError(message);
|
||||
setNotice("Appwrite sync failed.");
|
||||
}
|
||||
} finally {
|
||||
if (showLoader) setDataLoading(false);
|
||||
}
|
||||
},
|
||||
[pushToast],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
@@ -197,9 +209,16 @@ function App() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
cacheHydratedRef.current = false;
|
||||
setEntries([]);
|
||||
return;
|
||||
}
|
||||
const cachedEntries = readCachedEntries(user.$id);
|
||||
cacheHydratedRef.current = cachedEntries !== null;
|
||||
if (cachedEntries) {
|
||||
// Render the last synced entries immediately; the refresh below replaces them.
|
||||
setEntries(sortEntries(cachedEntries));
|
||||
}
|
||||
void refreshEntries(user.$id);
|
||||
}, [refreshEntries, user]);
|
||||
|
||||
@@ -314,6 +333,7 @@ function App() {
|
||||
setSyncError("");
|
||||
try {
|
||||
await account.deleteSession({ sessionId: "current" });
|
||||
if (user) clearCachedEntries(user.$id);
|
||||
setUser(null);
|
||||
setEntries([]);
|
||||
setNotice("Logged out.");
|
||||
|
||||
Reference in New Issue
Block a user