diff --git a/frontend-v2/src/app/(authed)/events/activist-storage.ts b/frontend-v2/src/app/(authed)/events/activist-storage.ts index 2b4fb43a..3969df7a 100644 --- a/frontend-v2/src/app/(authed)/events/activist-storage.ts +++ b/frontend-v2/src/app/(authed)/events/activist-storage.ts @@ -21,13 +21,17 @@ interface SyncMetadata { lastSyncTime: string // ISO 8601 timestamp } -const DB_NAME = 'activist-registry' const DB_VERSION = 2 const STORE_NAME = 'activists' const METADATA_STORE = 'metadata' export class ActivistStorage { private dbPromise: Promise | null = null + private readonly dbName: string + + constructor(chapterId: number) { + this.dbName = `adb-chapter-${chapterId}` + } /** * Initialize the IndexedDB database with required object stores. @@ -35,7 +39,7 @@ export class ActivistStorage { private openDB(): Promise { if (!this.dbPromise) { this.dbPromise = new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, DB_VERSION) + const request = indexedDB.open(this.dbName, DB_VERSION) request.onerror = () => reject(request.error) request.onsuccess = () => { @@ -98,7 +102,7 @@ export class ActivistStorage { return new Promise((resolve, reject) => { const transaction = db.transaction([METADATA_STORE], 'readonly') const store = transaction.objectStore(METADATA_STORE) - const request = store.get('lastSync') + const request = store.get('lastActivistSync') request.onsuccess = () => { const metadata = request.result as SyncMetadata | undefined @@ -120,10 +124,10 @@ export class ActivistStorage { const request = timestamp === null - ? store.delete('lastSync') + ? store.delete('lastActivistSync') : store.put( { lastSyncTime: timestamp } satisfies SyncMetadata, - 'lastSync', + 'lastActivistSync', ) request.onsuccess = () => resolve() @@ -219,6 +223,15 @@ function isIndexedDBAvailable(): boolean { } } -// Singleton instance - only create if IndexedDB is available -export const activistStorage: ActivistStorage | undefined = - isIndexedDBAvailable() ? new ActivistStorage() : undefined +const storageByChapter = new Map() + +export function getActivistStorage( + chapterId: number, +): ActivistStorage | undefined { + if (!isIndexedDBAvailable()) return undefined + const existing = storageByChapter.get(chapterId) + if (existing) return existing + const storage = new ActivistStorage(chapterId) + storageByChapter.set(chapterId, storage) + return storage +} diff --git a/frontend-v2/src/app/(authed)/events/event-form.tsx b/frontend-v2/src/app/(authed)/events/event-form.tsx index c3d0c4e6..df2fe3f7 100644 --- a/frontend-v2/src/app/(authed)/events/event-form.tsx +++ b/frontend-v2/src/app/(authed)/events/event-form.tsx @@ -97,7 +97,7 @@ export const EventForm = ({ mode }: EventFormProps) => { // The registry loads cached data from IndexedDB first, then syncs any // new/updated activists from the server in the background. const { registry: activistRegistry, isLoading: isLoadingActivists } = - useActivistRegistry() + useActivistRegistry(user.ChapterID) // Fetch existing event/connection, if editing. const { diff --git a/frontend-v2/src/app/(authed)/events/events-page.tsx b/frontend-v2/src/app/(authed)/events/events-page.tsx index eea3b413..60efdc20 100644 --- a/frontend-v2/src/app/(authed)/events/events-page.tsx +++ b/frontend-v2/src/app/(authed)/events/events-page.tsx @@ -36,6 +36,7 @@ import { } from '@/components/ui/select' import { DatePicker } from '@/components/ui/date-picker' import { useActivistRegistry } from './useActivistRegistry' +import { useAuthedPageContext } from '@/hooks/useAuthedPageContext' import { SuggestionInput } from './suggestion-input' import { EventListTable } from './event-list-table' import { cn } from '@/lib/utils' @@ -119,7 +120,8 @@ export default function EventsPage({ mode = 'events' }: Props) { const defaultParams = useMemo(() => buildDefaultParams(mode), [mode]) const isConnections = mode === 'connections' const queryClient = useQueryClient() - const { registry } = useActivistRegistry() + const { user } = useAuthedPageContext() + const { registry } = useActivistRegistry(user.ChapterID) // URL params drive the query and are the source of truth for committed filters const [urlParams, setUrlParams] = useQueryStates({ diff --git a/frontend-v2/src/app/(authed)/events/useActivistRegistry.ts b/frontend-v2/src/app/(authed)/events/useActivistRegistry.ts index 2c4e9e83..0efc4d1a 100644 --- a/frontend-v2/src/app/(authed)/events/useActivistRegistry.ts +++ b/frontend-v2/src/app/(authed)/events/useActivistRegistry.ts @@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from 'react' import { useQuery } from '@tanstack/react-query' import { apiClient, API_PATH } from '@/lib/api' import { ActivistRegistry, type ActivistRecord } from './activist-registry' -import { activistStorage } from './activist-storage' +import { getActivistStorage } from './activist-storage' import toast from 'react-hot-toast' /** @@ -13,7 +13,7 @@ import toast from 'react-hot-toast' * * @returns Object containing the registry instance and query state */ -export function useActivistRegistry() { +export function useActivistRegistry(chapterId: number) { const registryRef = useRef(new ActivistRegistry()) const [isStorageLoaded, setIsStorageLoaded] = useState(false) const [isServerLoaded, setIsServerLoaded] = useState(false) @@ -22,8 +22,10 @@ export function useActivistRegistry() { useEffect(() => { let mounted = true + const storage = getActivistStorage(chapterId) + // If IndexedDB is not available (e.g., iOS lockdown mode), skip loading from storage - if (!activistStorage) { + if (!storage) { console.info( '[Registry] IndexedDB not available - running without local caching', ) @@ -32,7 +34,7 @@ export function useActivistRegistry() { } registryRef.current - .loadFromStorage(activistStorage) + .loadFromStorage(storage) .then(() => { if (mounted) setIsStorageLoaded(true) }) @@ -56,7 +58,7 @@ export function useActivistRegistry() { return () => { mounted = false } - }, []) + }, [chapterId]) const query = useQuery({ queryKey: [API_PATH.ACTIVIST_LIST_BASIC], diff --git a/frontend-v2/src/components/nav.tsx b/frontend-v2/src/components/nav.tsx index 8dfbf190..14047abb 100644 --- a/frontend-v2/src/components/nav.tsx +++ b/frontend-v2/src/components/nav.tsx @@ -16,7 +16,7 @@ import { usePathname, useSearchParams } from 'next/navigation' import { useAuthedPageContext } from '@/hooks/useAuthedPageContext' import buefyStyles from './nav.module.css' import clsx from 'clsx' -import { useQuery, useQueryClient } from '@tanstack/react-query' +import { useQuery } from '@tanstack/react-query' import { useQueryStates } from 'nuqs' import { API_PATH, apiClient } from '@/lib/api' import { SF_BAY_CHAPTER_ID } from '@/lib/constants' @@ -201,7 +201,6 @@ const DropdownItem = ({ const ChapterSwitcher = () => { const { user } = useAuthedPageContext() - const queryClient = useQueryClient() const { data, isLoading, isError } = useQuery({ queryKey: [API_PATH.CHAPTER_LIST], @@ -229,7 +228,6 @@ const ChapterSwitcher = () => { } const switchChapter = (e: React.ChangeEvent) => { - queryClient.invalidateQueries() // invalidate existing cache for previous chapter window.location.href = `/auth/switch_chapter?chapter_id=${e.target.value}` }