From b74d9720d468d3a73784182d66463047f1e86efc Mon Sep 17 00:00:00 2001 From: Zaiba Machhaliya Date: Thu, 27 Aug 2026 13:23:10 +0530 Subject: [PATCH 1/3] refactor: centralize localStorage operations in storage utility --- src/context/AppContext.jsx | 38 ++++++++++++++++++++------------------ src/utils/storage.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 src/utils/storage.js diff --git a/src/context/AppContext.jsx b/src/context/AppContext.jsx index a013159..151153c 100644 --- a/src/context/AppContext.jsx +++ b/src/context/AppContext.jsx @@ -1,11 +1,13 @@ import { createContext, useContext, useState, useCallback, useEffect, useMemo } from 'react' import { fetchOrg, fetchRepos, fetchContributors, fetchIssues, fetchRateLimit, fetchPulls } from '../services/github' import { buildAnalyticalModel, getTopRepositories } from '../services/analytics' +import { storage, STORAGE_KEYS } from '@/utils/storage'; + const Ctx = createContext(null) function getStoredRateLimit() { - const stored = localStorage.getItem('oe_rate_limit') + const stored = storage.get(STORAGE_KEYS.RATE_LIMIT) if (!stored) return null @@ -13,19 +15,19 @@ function getStoredRateLimit() { const data = JSON.parse(stored) if (Date.now() > data.reset * 1000) { - localStorage.removeItem('oe_rate_limit') + storage.remove(STORAGE_KEYS.RATE_LIMIT) return null } return data } catch { - localStorage.removeItem('oe_rate_limit') + storage.remove(STORAGE_KEYS.RATE_LIMIT) return null } } export function AppProvider({ children }) { - const [pat, setPat] = useState(() => localStorage.getItem('oe_pat') || '') + const [pat, setPat] = useState(() => storage.get(STORAGE_KEYS.PAT) || '') const [orgs, setOrgs] = useState([]) const [model, setModel] = useState(null) const [issuesData, setIssuesData] = useState({}) @@ -37,7 +39,7 @@ export function AppProvider({ children }) { const [error, setError] = useState('') const [totalRepo, setTotalRepo] = useState(0) const [advanceAnalyticsLoading, setAdvanceAnalyticsLoading] = useState(false); - const [advanceAnalyticsComplete, setAdvanceAnalyticsComplete] = useState(false) + const [advanceAnalyticsComplete, setAdvanceAnalyticsComplete] = useState(false) const [isComplete, setIsComplete] = useState(false) const [auditComplete, setAuditComplete] = useState(false) const [lastOrgNames, setLastOrgNames] = useState([]) @@ -45,7 +47,7 @@ export function AppProvider({ children }) { useEffect(() => { const handler = e => { setRateLimit(e.detail) - localStorage.setItem('oe_rate_limit', JSON.stringify(e.detail)) + storage.set(STORAGE_KEYS.RATE_LIMIT, e.detail) } window.addEventListener('rate-limit-update', handler) @@ -59,7 +61,7 @@ export function AppProvider({ children }) { if (!rateLimit?.reset) return const timeout = setTimeout(() => { - localStorage.removeItem('oe_rate_limit') + storage.remove(STORAGE_KEYS.RATE_LIMIT) setRateLimit(null) }, Math.max(0, rateLimit.reset * 1000 - Date.now())) @@ -76,7 +78,7 @@ export function AppProvider({ children }) { }, [pat]) const savePat = useCallback(token => { setPat(token) - token ? localStorage.setItem('oe_pat', token) : localStorage.removeItem('oe_pat') + token ? storage.set(STORAGE_KEYS.PAT, token) : storage.remove(STORAGE_KEYS.PAT) }, []) // Multi-org explore @@ -128,9 +130,9 @@ export function AppProvider({ children }) { setIsComplete(!!pat) // Save to recent searches - const prev = JSON.parse(localStorage.getItem('oe_recent') || '[]') + const prev = storage.get(STORAGE_KEYS.RECENT_SEARCHES) || [] const entry = orgNames.join(', ') - localStorage.setItem('oe_recent', JSON.stringify([...new Set([entry, ...prev])].slice(0, 6))) + storage.set(STORAGE_KEYS.RECENT_SEARCHES, [...new Set([entry, ...prev])].slice(0, 6)) return builtModel } catch (err) { setError(err.message === 'RATE_LIMIT' @@ -288,28 +290,28 @@ export function AppProvider({ children }) { }, [model, isComplete, runFullExplore, auditRepos, selectAnalysisRepos, pat, govLoading, advanceAnalyticsLoading]) const STALE_DAYS = 90 - + const staleRepoStats = useMemo(() => { const now = Date.now() - + return Object.entries(issuesData || {}).map(([key, issues]) => { const [org, repo] = key.split('/') - + const normalIssues = issues.filter(i => !i.pull_request) - + const openIssues = normalIssues.filter(i => i.state === 'open') - + const staleIssues = openIssues.filter(i => { const updated = new Date(i.updated_at).getTime() const diffDays = (now - updated) / (1000 * 60 * 60 * 24) return diffDays >= STALE_DAYS }) - + const ratio = openIssues.length === 0 ? 0 : Math.round((staleIssues.length / openIssues.length) * 100) - + return { id: key, org, @@ -335,4 +337,4 @@ export function AppProvider({ children }) { ) } -export const useApp = () => useContext(Ctx) +export const useApp = () => useContext(Ctx) \ No newline at end of file diff --git a/src/utils/storage.js b/src/utils/storage.js new file mode 100644 index 0000000..401e8f6 --- /dev/null +++ b/src/utils/storage.js @@ -0,0 +1,35 @@ +// src/utils/storage.js + +export const STORAGE_KEYS = { + PAT: 'oe_pat', + RATE_LIMIT: 'oe_rate_limit', + RECENT_SEARCHES: 'oe_recent', +}; + +export const storage = { + get: (key) => { + try { + const value = localStorage.getItem(key); + return value ? JSON.parse(value) : null; + } catch (error) { + console.error(`Error reading ${key} from localStorage:`, error); + return null; + } + }, + + set: (key, value) => { + try { + localStorage.setItem(key, JSON.stringify(value)); + } catch (error) { + console.error(`Error writing ${key} to localStorage:`, error); + } + }, + + remove: (key) => { + try { + localStorage.removeItem(key); + } catch (error) { + console.error(`Error removing ${key} from localStorage:`, error); + } + }, +}; \ No newline at end of file From a2def5253bf0b44287d45d6199eb3621e75f3b6e Mon Sep 17 00:00:00 2001 From: Zaiba Machhaliya Date: Thu, 27 Aug 2026 13:55:58 +0530 Subject: [PATCH 2/3] fix: address CodeRabbit review comments --- src/context/AppContext.jsx | 5 ++--- src/pages/ContributorProfilePage.jsx | 25 ++++++++++++------------- src/pages/HomePage.jsx | 5 +++-- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/context/AppContext.jsx b/src/context/AppContext.jsx index 151153c..6278b98 100644 --- a/src/context/AppContext.jsx +++ b/src/context/AppContext.jsx @@ -1,8 +1,7 @@ import { createContext, useContext, useState, useCallback, useEffect, useMemo } from 'react' import { fetchOrg, fetchRepos, fetchContributors, fetchIssues, fetchRateLimit, fetchPulls } from '../services/github' import { buildAnalyticalModel, getTopRepositories } from '../services/analytics' -import { storage, STORAGE_KEYS } from '@/utils/storage'; - +import { storage,STORAGE_KEYS } from '../utils/storage' const Ctx = createContext(null) @@ -12,7 +11,7 @@ function getStoredRateLimit() { if (!stored) return null try { - const data = JSON.parse(stored) + if (Date.now() > data.reset * 1000) { storage.remove(STORAGE_KEYS.RATE_LIMIT) diff --git a/src/pages/ContributorProfilePage.jsx b/src/pages/ContributorProfilePage.jsx index 3cafec8..98b9bb1 100644 --- a/src/pages/ContributorProfilePage.jsx +++ b/src/pages/ContributorProfilePage.jsx @@ -4,6 +4,8 @@ import { FiArrowLeft, FiDownload, FiExternalLink, FiCalendar, FiBriefcase, FiAle import { useApp } from '../context/AppContext' import { C, PageTitle, Spinner, StatCard } from '../components/UI' import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' +import { storage,STORAGE_KEYS } from '../utils/storage' + // Reusable ContributionTable component function ContributionTable({ items, dateHeader, resolveStatus }) { @@ -128,15 +130,12 @@ export default function ContributorProfilePage() { let list = orgs.map(o => o.login) if (!list.length) { try { - const rawRecent = localStorage.getItem('oe_recent') - if (rawRecent) { - const recent = JSON.parse(rawRecent) - if (Array.isArray(recent) && recent.length && typeof recent[0] === 'string') { - list = recent[0].split(',').map(s => s.trim()).filter(Boolean) - } + const recent = storage.get(STORAGE_KEYS.RECENT_SEARCHES) + if (Array.isArray(recent) && recent.length && typeof recent[0] === 'string') { + list = recent[0].split(',').map(s => s.trim()).filter(Boolean) } } catch (e) { - console.error('Failed to parse oe_recent from localStorage:', e) + console.error('Failed to parse recent searches from storage:', e) } } return list @@ -305,18 +304,18 @@ export default function ContributorProfilePage() { // Time-series charting data (Chronological sorting by YYYY-MM) const chartData = useMemo(() => { const monthlyBuckets = {} - + filteredContribs.forEach(item => { const date = new Date(item.created_at) const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const yyyymm = `${year}-${month}` const displayName = date.toLocaleString('default', { month: 'short', year: '2-digit' }) // e.g. "May 26" - + if (!monthlyBuckets[yyyymm]) { monthlyBuckets[yyyymm] = { yyyymm, name: displayName, PRs: 0, Issues: 0 } } - + if (item.pull_request) { monthlyBuckets[yyyymm].PRs++ } else { @@ -492,9 +491,9 @@ export default function ContributorProfilePage() { p.isMerged).length} Merged`} accent="var(--blue)" /> i.state === 'closed').length} Closed`} accent="var(--amber)" /> - i.repository_url?.split('/').pop())).size} + i.repository_url?.split('/').pop())).size} sub="distinct repositories" accent="var(--green)" /> diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index cffcbd3..ec2195a 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom' import { FiSearch, FiX } from 'react-icons/fi' import { useApp } from '../context/AppContext' import { C, Spinner } from '../components/UI' - +import { storage, STORAGE_KEYS } from '../utils/storage' const QUICK = ['AOSSIE-Org', 'DjedAlliance', 'StabilityNexus'] export default function HomePage() { @@ -12,7 +12,8 @@ export default function HomePage() { const [input, setInput] = useState('') const [chips, setChips] = useState([]) - const recent = JSON.parse(localStorage.getItem('oe_recent') || '[]') + const recent = storage.get(STORAGE_KEYS.RECENT_SEARCHES) || [] + const addChip = raw => { const parts = raw.split(/[,+\s]+/).map(s => s.trim()).filter(Boolean) From 9b0f638cffda31e0582a4064c99f05382305d96f Mon Sep 17 00:00:00 2001 From: Zaiba Machhaliya Date: Thu, 27 Aug 2026 14:09:38 +0530 Subject: [PATCH 3/3] fix: finalize storage utility refactor --- src/context/AppContext.jsx | 4 ++-- src/utils/storage.js | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/context/AppContext.jsx b/src/context/AppContext.jsx index 6278b98..fab7aa1 100644 --- a/src/context/AppContext.jsx +++ b/src/context/AppContext.jsx @@ -13,12 +13,12 @@ function getStoredRateLimit() { try { - if (Date.now() > data.reset * 1000) { + if (Date.now() > stored.reset * 1000) { storage.remove(STORAGE_KEYS.RATE_LIMIT) return null } - return data + return stored } catch { storage.remove(STORAGE_KEYS.RATE_LIMIT) return null diff --git a/src/utils/storage.js b/src/utils/storage.js index 401e8f6..8d5cd74 100644 --- a/src/utils/storage.js +++ b/src/utils/storage.js @@ -1,16 +1,21 @@ // src/utils/storage.js export const STORAGE_KEYS = { - PAT: 'oe_pat', - RATE_LIMIT: 'oe_rate_limit', - RECENT_SEARCHES: 'oe_recent', + PAT: "oe_pat", + RATE_LIMIT: "oe_rate_limit", + RECENT_SEARCHES: "oe_recent", }; export const storage = { get: (key) => { try { const value = localStorage.getItem(key); - return value ? JSON.parse(value) : null; + if (!value) return null; + try { + return JSON.parse(value); + } catch { + return value; + } } catch (error) { console.error(`Error reading ${key} from localStorage:`, error); return null; @@ -32,4 +37,4 @@ export const storage = { console.error(`Error removing ${key} from localStorage:`, error); } }, -}; \ No newline at end of file +};