Skip to content

API management centralize store#89

Merged
Ayush8923 merged 4 commits intomainfrom
feat/api-management-centralize-storage
Mar 26, 2026
Merged

API management centralize store#89
Ayush8923 merged 4 commits intomainfrom
feat/api-management-centralize-storage

Conversation

@Ayush8923
Copy link
Copy Markdown
Collaborator

@Ayush8923 Ayush8923 commented Mar 24, 2026

Issue: #86

Summary:

  1. Introduce an AuthContext to manage API keys globally
  2. Remove duplicated useEffect logic from individual pages
  3. Replace direct localStorage usage with controlled approaches
  4. Move sidebar state into a global context
  5. Use the apiClient, apiFetch helper function in the route.ts and page.tsx files.

Summary by CodeRabbit

Release Notes

  • New Features

    • Centralized API key management across the application for more consistent authentication.
  • Improvements

    • Sidebar navigation state now persists consistently across all pages.
    • Enhanced date and time display formatting for better readability.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the application's state management by introducing two new React context providers (AuthContext and AppContext) to centralize API key management and UI state (sidebar collapse). All pages are updated to source these values from the context hooks instead of maintaining local state and reading from localStorage.

Changes

Cohort / File(s) Summary
Context Infrastructure
app/lib/context/AuthContext.tsx, app/lib/context/AppContext.tsx
New context providers managing API keys (hydrated/persisted to localStorage) and sidebar UI state, replacing scattered local state across pages.
Layout & Root Setup
app/layout.tsx
Wrapped children with AuthProvider and AppProvider to enable context access throughout the app.
Configuration Pages
app/components/ConfigModal.tsx, app/configurations/page.tsx, app/configurations/prompt-editor/page.tsx
Migrated API key sourcing from localStorage to useAuth().activeKey; sidebar state now via useApp(); ConfigModal refactored tool/KB conditional rendering and uses stricter typing.
Content Management Pages
app/datasets/page.tsx, app/document/page.tsx, app/knowledge-base/page.tsx
Replaced local API key and sidebar state with context hooks; removed localStorage initialization logic.
Evaluation & Keystore Pages
app/evaluations/page.tsx, app/evaluations/[id]/page.tsx, app/keystore/page.tsx
Migrated to context-driven API key and sidebar state; keystore now calls addKey/removeKey from useAuth() instead of mutating local state.
Settings & Media Pages
app/settings/credentials/page.tsx, app/speech-to-text/page.tsx, app/text-to-speech/page.tsx
Updated to use useApp() and useAuth() for state; removed localStorage parsing and component-local state management.
Utilities & Type Definitions
app/lib/utils.ts, app/lib/configTypes.ts
timeAgo() now conditionally appends timezone indicator for consistent date parsing; removed outdated doc comment from configTypes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • Prajna1999
  • vprashrex

Poem

🐰 No more scattered keys in the local state,
Contexts unite them—oh, how we celebrate!
Sidebar collapses from shared UI ground,
localStorage whispers its secrets around,
One hook to bind them, the refactor is done! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main objective: centralizing API key management into global context stores, removing scattered localStorage and component-level state management.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/api-management-centralize-storage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Ayush8923 Ayush8923 self-assigned this Mar 24, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/document/page.tsx (1)

33-45: ⚠️ Potential issue | 🟠 Major

Clear document state on API-key loss/switch.

With context-driven keys, previous documents/preview can remain visible after key removal or key change. Reset local document state before/when key becomes unavailable.

💡 Suggested fix
   useEffect(() => {
-    if (apiKey) {
-      fetchDocuments();
-    }
+    setCurrentPage(1);
+    setSelectedDocument(null);
+    setDocuments([]);
+
+    if (!apiKey) {
+      setError('No API key found. Please add an API key in the Keystore.');
+      return;
+    }
+
+    fetchDocuments();
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [apiKey]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/document/page.tsx` around lines 33 - 45, The page currently leaves
previous documents/preview visible when the apiKey is removed or changed; update
the useEffect that watches apiKey so that when apiKey is falsy or changes you
clear local state first (call setDocuments([]), setPreviewDocument(null) and
reset setCurrentPage(1)), and when apiKey becomes truthy call fetchDocuments()
(for a key switch, clear state then fetch). Locate the useEffect and the state
variables (apiKey from useAuth, fetchDocuments, setDocuments,
setPreviewDocument, setCurrentPage) and add the else/branch or conditional
handling to perform the resets before/when fetching.
app/lib/useConfigs.ts (1)

195-209: ⚠️ Potential issue | 🔴 Critical

Scope cache by active key to prevent cross-key data bleed.

After switching activeKey, the hook can still return cached configs from the previous key (shared inMemoryCache/CACHE_KEY) before refetch. This risks showing another key’s data in the current session.

💡 Suggested fix (invalidate cache on key switch)
 export function useConfigs(): UseConfigsResult {
   const { activeKey } = useAuth();
+  const lastKeyIdRef = useRef<string | null>(null);
   const [configs, setConfigs] = useState<SavedConfig[]>([]);
@@
   const fetchConfigs = useCallback(async (force: boolean = false) => {
     // Prevent concurrent fetches
     if (fetchInProgress.current) return;

+    const currentKeyId = activeKey?.id ?? null;
+    if (lastKeyIdRef.current !== currentKeyId) {
+      inMemoryCache = null;
+      if (typeof window !== 'undefined') {
+        localStorage.removeItem(CACHE_KEY);
+      }
+      lastKeyIdRef.current = currentKeyId;
+    }
+
     const apiKey = activeKey?.key ?? null;

Also applies to: 347-347

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/lib/useConfigs.ts` around lines 195 - 209, The cached configs are shared
across different active keys causing cross-key data bleed; update the cache
logic used by useConfigs by scoping the inMemoryCache/CACHE_KEY to the activeKey
(or by clearing the cached entry whenever activeKey changes) so fetchConfigs and
any cache reads/writes use a unique key per apiKey (reference useAuth.activeKey,
useConfigs.fetchConfigs, inMemoryCache and CACHE_KEY) and ensure the effect that
reacts to activeKey invalidates or refetches (and resets isCached) to avoid
showing another key’s data.
🧹 Nitpick comments (3)
app/keystore/page.tsx (1)

22-22: Redundant STORAGE_KEY export.

This constant is now defined in AuthContext.tsx. Consider removing this export to avoid duplication and potential drift. If other modules depend on it, they should import from the canonical source or the constant should be moved to a shared utility file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/keystore/page.tsx` at line 22, Remove the redundant STORAGE_KEY export
from page.tsx and update callers to import the canonical constant from
AuthContext.tsx (or move STORAGE_KEY into a shared utility and import from
there); locate STORAGE_KEY in app/keystore/page.tsx and replace any local
references or imports to use the single source of truth defined in
AuthContext.tsx (or the new shared file) to avoid duplication and potential
drift.
app/lib/context/AppContext.tsx (1)

13-22: Sidebar collapsed state is not persisted to localStorage.

The context manages sidebarCollapsed state but doesn't persist it. Users will lose their sidebar preference on page refresh.

Based on learnings, sidebar state should be persisted using the sidebar-expanded-menus localStorage key.

♻️ Proposed fix to add localStorage persistence
+const SIDEBAR_STORAGE_KEY = 'sidebar-expanded-menus';
+
 export function AppProvider({ children }: { children: React.ReactNode }) {
-  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
+  const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
+    if (typeof window === 'undefined') return false;
+    try {
+      const stored = localStorage.getItem(SIDEBAR_STORAGE_KEY);
+      return stored ? JSON.parse(stored) : false;
+    } catch {
+      return false;
+    }
+  });
+
+  const updateSidebarCollapsed = (collapsed: boolean) => {
+    setSidebarCollapsed(collapsed);
+    localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(collapsed));
+  };
+
-  const toggleSidebar = () => setSidebarCollapsed(prev => !prev);
+  const toggleSidebar = () => {
+    setSidebarCollapsed(prev => {
+      const newValue = !prev;
+      localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(newValue));
+      return newValue;
+    });
+  };
 
   return (
-    <AppContext.Provider value={{ sidebarCollapsed, setSidebarCollapsed, toggleSidebar }}>
+    <AppContext.Provider value={{ sidebarCollapsed, setSidebarCollapsed: updateSidebarCollapsed, toggleSidebar }}>
       {children}
     </AppContext.Provider>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/lib/context/AppContext.tsx` around lines 13 - 22, The AppProvider
currently holds sidebarCollapsed but doesn't persist it; update AppProvider to
read the initial boolean from localStorage key "sidebar-expanded-menus"
(fallback to false) when initializing sidebarCollapsed, and whenever
setSidebarCollapsed or toggleSidebar changes the value also write the updated
boolean back to that same localStorage key; reference the AppProvider function
and the state setters sidebarCollapsed, setSidebarCollapsed and toggleSidebar so
the initialization and update logic are added there (use
JSON.stringify/JSON.parse or string "true"/"false" consistently).
app/configurations/prompt-editor/page.tsx (1)

212-220: Dependency array has redundant entries.

The dependencies provider, temperature, and tools at lines 216-218 are not directly referenced in the effect body. Changes to these values would already be reflected through currentConfigBlob (line 215), which is already a dependency. These extra entries are harmless but add noise.

♻️ Optional: Remove redundant dependencies
   }, [
     selectedConfigId,
     currentContent,
     currentConfigBlob,
-    provider,
-    temperature,
-    tools,
     savedConfigs,
   ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/configurations/prompt-editor/page.tsx` around lines 212 - 220, The effect
dependency array contains redundant entries: remove provider, temperature, and
tools from the dependencies in the useEffect that currently lists
selectedConfigId, currentContent, currentConfigBlob, provider, temperature,
tools, savedConfigs; those values are already represented via currentConfigBlob
(and the other listed deps) so update the dependency array to only include
selectedConfigId, currentContent, currentConfigBlob, and savedConfigs to
eliminate noise while preserving correct reactivity for the effect.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/configurations/page.tsx`:
- Around line 35-39: The handler that loads evaluations (the block using
activeKey and apiFetch in the component) never clears evaluationCounts when
activeKey is missing or changes; update the early return for if (!activeKey) to
reset evaluationCounts (e.g., call the state setter used for evaluationCounts)
before returning, and also reset evaluationCounts at the start of the fetch path
when activeKey changes so stale counts are cleared; reference the
evaluationCounts state and the function that fetches evaluations (the code
around apiFetch('/api/evaluations', activeKey.key) and the jobs variable) and
ensure the same reset is applied in the other similar load path noted in the
comment.

In `@app/evaluations/`[id]/page.tsx:
- Around line 68-73: The useEffect in page.tsx that initializes selectedKeyId
(watching [apiKeys, selectedKeyId]) doesn't handle removed/rotated keys, leaving
selectedKeyId stale; update that effect to also check whether the current
selectedKeyId exists in apiKeys and if not setSelectedKeyId to a valid fallback
(e.g., apiKeys[0].id or null/undefined when apiKeys is empty) so fetches resume;
reference the useEffect block, the selectedKeyId state, apiKeys array, and
setSelectedKeyId updater when implementing this change.

In `@app/lib/context/AuthContext.tsx`:
- Around line 37-38: The addKey/removeKey callbacks close over the rendered
apiKeys value and can overwrite concurrent updates; change them to use
functional updates via persist so they read the latest state (e.g. in addKey
call persist(prev => [...prev, key]) and in removeKey call persist(prev =>
prev.filter(k => k.id !== id))). Also update the useCallback dependencies to
remove apiKeys (keep persist) so addKey and removeKey do not capture stale
apiKeys.
- Around line 19-26: Replace the client-only localStorage access inside the
useState initializer to avoid SSR hydration mismatches: initialize apiKeys with
useState<APIKey[]>([]) and move the localStorage read into a useEffect that runs
on mount; inside that effect, try to read localStorage.getItem(STORAGE_KEY) and
if present JSON.parse it and call setApiKeys(...), catching errors for malformed
data—update the AuthContext.tsx code that currently defines apiKeys and
setApiKeys to use this pattern.

---

Outside diff comments:
In `@app/document/page.tsx`:
- Around line 33-45: The page currently leaves previous documents/preview
visible when the apiKey is removed or changed; update the useEffect that watches
apiKey so that when apiKey is falsy or changes you clear local state first (call
setDocuments([]), setPreviewDocument(null) and reset setCurrentPage(1)), and
when apiKey becomes truthy call fetchDocuments() (for a key switch, clear state
then fetch). Locate the useEffect and the state variables (apiKey from useAuth,
fetchDocuments, setDocuments, setPreviewDocument, setCurrentPage) and add the
else/branch or conditional handling to perform the resets before/when fetching.

In `@app/lib/useConfigs.ts`:
- Around line 195-209: The cached configs are shared across different active
keys causing cross-key data bleed; update the cache logic used by useConfigs by
scoping the inMemoryCache/CACHE_KEY to the activeKey (or by clearing the cached
entry whenever activeKey changes) so fetchConfigs and any cache reads/writes use
a unique key per apiKey (reference useAuth.activeKey, useConfigs.fetchConfigs,
inMemoryCache and CACHE_KEY) and ensure the effect that reacts to activeKey
invalidates or refetches (and resets isCached) to avoid showing another key’s
data.

---

Nitpick comments:
In `@app/configurations/prompt-editor/page.tsx`:
- Around line 212-220: The effect dependency array contains redundant entries:
remove provider, temperature, and tools from the dependencies in the useEffect
that currently lists selectedConfigId, currentContent, currentConfigBlob,
provider, temperature, tools, savedConfigs; those values are already represented
via currentConfigBlob (and the other listed deps) so update the dependency array
to only include selectedConfigId, currentContent, currentConfigBlob, and
savedConfigs to eliminate noise while preserving correct reactivity for the
effect.

In `@app/keystore/page.tsx`:
- Line 22: Remove the redundant STORAGE_KEY export from page.tsx and update
callers to import the canonical constant from AuthContext.tsx (or move
STORAGE_KEY into a shared utility and import from there); locate STORAGE_KEY in
app/keystore/page.tsx and replace any local references or imports to use the
single source of truth defined in AuthContext.tsx (or the new shared file) to
avoid duplication and potential drift.

In `@app/lib/context/AppContext.tsx`:
- Around line 13-22: The AppProvider currently holds sidebarCollapsed but
doesn't persist it; update AppProvider to read the initial boolean from
localStorage key "sidebar-expanded-menus" (fallback to false) when initializing
sidebarCollapsed, and whenever setSidebarCollapsed or toggleSidebar changes the
value also write the updated boolean back to that same localStorage key;
reference the AppProvider function and the state setters sidebarCollapsed,
setSidebarCollapsed and toggleSidebar so the initialization and update logic are
added there (use JSON.stringify/JSON.parse or string "true"/"false"
consistently).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1884eb9-a451-46e8-a640-9c8d89834a58

📥 Commits

Reviewing files that changed from the base of the PR and between fe9a695 and e1dc8e3.

📒 Files selected for processing (17)
  • app/components/ConfigModal.tsx
  • app/configurations/page.tsx
  • app/configurations/prompt-editor/page.tsx
  • app/datasets/page.tsx
  • app/document/page.tsx
  • app/evaluations/[id]/page.tsx
  • app/evaluations/page.tsx
  • app/keystore/page.tsx
  • app/knowledge-base/page.tsx
  • app/layout.tsx
  • app/lib/configTypes.ts
  • app/lib/context/AppContext.tsx
  • app/lib/context/AuthContext.tsx
  • app/lib/useConfigs.ts
  • app/settings/credentials/page.tsx
  • app/speech-to-text/page.tsx
  • app/text-to-speech/page.tsx
💤 Files with no reviewable changes (1)
  • app/lib/configTypes.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
app/lib/context/AuthContext.tsx (1)

48-55: ⚠️ Potential issue | 🟠 Major

Stale closure bug in addKey and removeKey still present.

These callbacks capture apiKeys at render time. If called in rapid succession before re-render, later calls operate on stale state and overwrite previous updates, causing data loss.

Use functional updates to access the latest state:

🐛 Proposed fix using functional setState
- const addKey = useCallback(
-   (key: APIKey) => persist([...apiKeys, key]),
-   [apiKeys, persist],
- );
- const removeKey = useCallback(
-   (id: string) => persist(apiKeys.filter((k) => k.id !== id)),
-   [apiKeys, persist],
- );
+ const addKey = useCallback((key: APIKey) => {
+   setApiKeys((prev) => {
+     const updated = [...prev, key];
+     localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
+     return updated;
+   });
+ }, []);
+
+ const removeKey = useCallback((id: string) => {
+   setApiKeys((prev) => {
+     const updated = prev.filter((k) => k.id !== id);
+     if (updated.length > 0) {
+       localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
+     } else {
+       localStorage.removeItem(STORAGE_KEY);
+     }
+     return updated;
+   });
+ }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/lib/context/AuthContext.tsx` around lines 48 - 55, addKey and removeKey
capture apiKeys in their closure and can overwrite concurrent updates; change
them to use functional updates so they always operate on the latest state by
calling persist with an updater function (i.e., replace persist([...apiKeys,
key]) with persist(prev => [...prev, key]) in addKey and replace
persist(apiKeys.filter(...)) with persist(prev => prev.filter(k => k.id !== id))
in removeKey), keeping the same useCallback wrappers referencing addKey,
removeKey, apiKeys, and persist.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/lib/context/AuthContext.tsx`:
- Around line 48-55: addKey and removeKey capture apiKeys in their closure and
can overwrite concurrent updates; change them to use functional updates so they
always operate on the latest state by calling persist with an updater function
(i.e., replace persist([...apiKeys, key]) with persist(prev => [...prev, key])
in addKey and replace persist(apiKeys.filter(...)) with persist(prev =>
prev.filter(k => k.id !== id)) in removeKey), keeping the same useCallback
wrappers referencing addKey, removeKey, apiKeys, and persist.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e09e51e-2a35-48f6-9bb4-17e2f8e5b7ff

📥 Commits

Reviewing files that changed from the base of the PR and between 98a9018 and 443d05b.

📒 Files selected for processing (1)
  • app/lib/context/AuthContext.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/lib/utils.ts`:
- Around line 13-18: The current logic that appends "Z" to date strings only
checks for "+" and therefore misses negative timezone offsets; update the check
in the function (the one that constructs `date` from `dateStr`—same area as
`formatRelativeTime`) to detect either "+" or "-" offsets (e.g. check for a sign
after the time separator or use a regex that matches timezone offsets like
/[+-]\d{2}:\d{2}$/) so strings like "2024-01-15T10:30:00-05:00" are recognized
as having an offset and do not get "Z" appended before calling `new Date()` and
`formatDistanceToNow`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 76b8b98e-dfc3-4ff8-a947-094ac33b505a

📥 Commits

Reviewing files that changed from the base of the PR and between 443d05b and 9a45ebf.

📒 Files selected for processing (3)
  • app/configurations/page.tsx
  • app/configurations/prompt-editor/page.tsx
  • app/lib/utils.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/configurations/prompt-editor/page.tsx
  • app/configurations/page.tsx

@Ayush8923 Ayush8923 merged commit 9b818fa into main Mar 26, 2026
2 checks passed
@Ayush8923 Ayush8923 deleted the feat/api-management-centralize-storage branch March 26, 2026 14:26
This was referenced Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants