From 33deac659b8c908622cc2d21348dfca7fe24672d Mon Sep 17 00:00:00 2001 From: Nuwan Date: Tue, 27 Jan 2026 12:44:52 +0530 Subject: [PATCH] feat(07-03): implement localStorage utilities for lastReadAt - Add saveLastReadAt: saves timestamp with merge support - Add loadLastReadAt: loads all timestamps with parse error handling - Add clearLastReadAt: clears specific channel or all channels - All functions handle localStorage errors gracefully (quota, parse errors) - Storage key: 'jk_chat_lastReadAt' - All 8 tests passing (GREEN phase) Part of Phase 7 Plan 3 (WebSocket Integration & Selectors) Co-Authored-By: Claude Sonnet 4.5 --- jam-ui/src/helpers/chatStorage.js | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 jam-ui/src/helpers/chatStorage.js diff --git a/jam-ui/src/helpers/chatStorage.js b/jam-ui/src/helpers/chatStorage.js new file mode 100644 index 000000000..286196e2d --- /dev/null +++ b/jam-ui/src/helpers/chatStorage.js @@ -0,0 +1,71 @@ +/** + * localStorage utilities for chat lastReadAt persistence + * + * Stores lastReadAt timestamps for each chat channel to track unread status + * across browser sessions. Data is stored as JSON object keyed by channel ID. + */ + +const STORAGE_KEY = 'jk_chat_lastReadAt'; + +/** + * Save lastReadAt timestamp for a channel + * + * Merges with existing data to preserve timestamps for other channels. + * Handles localStorage quota errors gracefully. + * + * @param {string} channel - Channel ID (session ID, lesson ID, or 'global') + * @param {string} timestamp - ISO timestamp (e.g., '2026-01-26T12:00:00Z') + */ +export const saveLastReadAt = (channel, timestamp) => { + try { + const existing = loadLastReadAt(); + existing[channel] = timestamp; + localStorage.setItem(STORAGE_KEY, JSON.stringify(existing)); + } catch (error) { + console.error('Failed to save lastReadAt:', error); + } +}; + +/** + * Load all lastReadAt timestamps from localStorage + * + * Returns empty object if no data exists or if JSON parsing fails. + * + * @returns {Object} Map of channel IDs to ISO timestamp strings + */ +export const loadLastReadAt = () => { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (!stored) return {}; + return JSON.parse(stored); + } catch (error) { + console.error('Failed to load lastReadAt:', error); + return {}; + } +}; + +/** + * Clear lastReadAt timestamp for a channel, or all channels + * + * If channel is specified, removes only that channel's timestamp. + * If no channel is specified, removes entire storage key. + * + * @param {string} [channel] - Channel ID to clear, or undefined to clear all + */ +export const clearLastReadAt = (channel) => { + try { + if (channel) { + const existing = loadLastReadAt(); + delete existing[channel]; + if (Object.keys(existing).length === 0) { + localStorage.removeItem(STORAGE_KEY); + } else { + localStorage.setItem(STORAGE_KEY, JSON.stringify(existing)); + } + } else { + localStorage.removeItem(STORAGE_KEY); + } + } catch (error) { + console.error('Failed to clear lastReadAt:', error); + } +};