Skip to content

Commit fd405bc

Browse files
authored
Use jotai local storage util for all stored data (#178)
* Use jotai local storage util for all local storage * fix(format): Resolve ci failure caused by format check --------- Co-authored-by: Chris Tate <ctate@users.noreply.github.com>
1 parent 0a3e638 commit fd405bc

File tree

5 files changed

+117
-130
lines changed

5 files changed

+117
-130
lines changed

components/repo-selector.tsx

Lines changed: 47 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import Image from 'next/image'
55
import { Input } from '@/components/ui/input'
66
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
77
import { Lock, Loader2 } from 'lucide-react'
8-
import { useAtomValue, useSetAtom } from 'jotai'
8+
import { useAtomValue, useSetAtom, useAtom } from 'jotai'
99
import { githubConnectionAtom } from '@/lib/atoms/github-connection'
10+
import { githubOwnersAtom, githubReposAtomFamily } from '@/lib/atoms/github-cache'
1011

1112
interface GitHubOwner {
1213
login: string
@@ -42,19 +43,8 @@ export function RepoSelector({
4243
}: RepoSelectorProps) {
4344
const [repoFilter, setRepoFilter] = useState('')
4445
// Initialize with selected owner to prevent flash
45-
const [owners, setOwners] = useState<GitHubOwner[]>(() => {
46-
if (selectedOwner) {
47-
return [
48-
{
49-
login: selectedOwner,
50-
name: selectedOwner,
51-
avatar_url: `https://github.com/${selectedOwner}.png`,
52-
},
53-
]
54-
}
55-
return []
56-
})
57-
const [repos, setRepos] = useState<GitHubRepo[]>([])
46+
const [owners, setOwners] = useAtom(githubOwnersAtom)
47+
const [repos, setRepos] = useAtom(githubReposAtomFamily(selectedOwner))
5848
const [loadingOwners, setLoadingOwners] = useState(true)
5949
const [loadingRepos, setLoadingRepos] = useState(false)
6050
const [repoDropdownOpen, setRepoDropdownOpen] = useState(false)
@@ -72,30 +62,26 @@ export function RepoSelector({
7262
useEffect(() => {
7363
// If GitHub was disconnected, clear data and cache
7464
if (githubConnectionRef.current && !githubConnection.connected) {
75-
// Clear cache
76-
localStorage.removeItem('github-owners')
77-
Object.keys(localStorage).forEach((key) => {
78-
if (key.startsWith('github-repos-')) {
79-
localStorage.removeItem(key)
80-
}
81-
})
65+
// Clear cache using atoms
66+
setOwners(null)
67+
// Clear all repos - we need to iterate through all possible owners
68+
// Since we can't clear all atomFamily members easily, we'll just clear the current one
69+
setRepos(null)
8270

8371
// Clear state
84-
setOwners([])
85-
setRepos([])
8672
onOwnerChange('')
8773
onRepoChange('')
8874
}
8975

9076
// If GitHub was reconnected, reload owners
9177
if (!githubConnectionRef.current && githubConnection.connected) {
9278
setLoadingOwners(true)
93-
setOwners([])
94-
setRepos([])
79+
setOwners(null)
80+
setRepos(null)
9581
}
9682

9783
githubConnectionRef.current = githubConnection.connected
98-
}, [githubConnection.connected, onOwnerChange, onRepoChange])
84+
}, [githubConnection.connected, onOwnerChange, onRepoChange, setOwners, setRepos])
9985

10086
// Load owners on component mount and when GitHub is connected
10187
useEffect(() => {
@@ -107,17 +93,14 @@ export function RepoSelector({
10793
const loadOwners = async () => {
10894
try {
10995
// Only show loading state if we don't have owners yet
110-
if (owners.length === 0) {
96+
if (!owners || owners.length === 0) {
11197
setLoadingOwners(true)
11298
} else {
11399
setIsRefreshing(true)
114100
}
115101

116102
// Check cache first - but only use it if we're not forcing a refresh
117-
const cachedOwners = localStorage.getItem('github-owners')
118-
if (cachedOwners && owners.length === 0) {
119-
const parsedOwners = JSON.parse(cachedOwners)
120-
setOwners(parsedOwners)
103+
if (owners && owners.length > 0) {
121104
setLoadingOwners(false)
122105
// Continue fetching in background to update
123106
}
@@ -128,13 +111,8 @@ export function RepoSelector({
128111
// Check for authentication errors - disconnect GitHub if auth fails
129112
if (!userResponse.ok) {
130113
if (userResponse.status === 401 || userResponse.status === 403) {
131-
// Clear cache
132-
localStorage.removeItem('github-owners')
133-
Object.keys(localStorage).forEach((key) => {
134-
if (key.startsWith('github-repos-')) {
135-
localStorage.removeItem(key)
136-
}
137-
})
114+
// Clear cache using atoms
115+
setOwners(null)
138116

139117
// Call backend to disconnect GitHub
140118
try {
@@ -183,8 +161,7 @@ export function RepoSelector({
183161
sortedOwners.push(...organizations)
184162

185163
setOwners(sortedOwners)
186-
// Cache the owners
187-
localStorage.setItem('github-owners', JSON.stringify(sortedOwners))
164+
// Cache is automatic with atomWithStorage
188165
} catch (error) {
189166
console.error('Error loading owners:', error)
190167

@@ -208,15 +185,15 @@ export function RepoSelector({
208185

209186
loadOwners()
210187
// eslint-disable-next-line react-hooks/exhaustive-deps
211-
}, [githubConnection.connected, setGitHubConnection])
188+
}, [githubConnection.connected, setGitHubConnection, setOwners])
212189

213190
// Auto-select user's personal account if no owner is selected and no saved owner exists
214191
useEffect(() => {
215-
if (owners.length > 0 && !selectedOwner) {
192+
if (owners && owners.length > 0 && !selectedOwner) {
216193
// Only auto-select if we have owners loaded and no owner is currently selected
217194
// This allows the parent component to set a saved owner from cookies first
218195
const timer = setTimeout(() => {
219-
if (!selectedOwner && owners.length > 0) {
196+
if (!selectedOwner && owners && owners.length > 0) {
220197
// Auto-select the first owner (user's personal account)
221198
// Since we add the user first in the loadOwners function, owners[0] will be the personal account
222199
onOwnerChange(owners[0].login)
@@ -233,32 +210,20 @@ export function RepoSelector({
233210
const loadRepos = async () => {
234211
try {
235212
// Check cache first - show cached data immediately if available
236-
const cacheKey = `github-repos-${selectedOwner}`
237-
const cachedRepos = localStorage.getItem(cacheKey)
238-
if (cachedRepos && repos.length === 0) {
239-
const parsedRepos = JSON.parse(cachedRepos)
240-
setRepos(parsedRepos)
213+
if (repos && repos.length > 0) {
241214
setLoadingRepos(false)
242215
// Continue fetching in background to update
243-
} else if (!cachedRepos && repos.length === 0) {
216+
} else {
244217
// Only show loading if we don't have cached data or existing repos
245218
setLoadingRepos(true)
246-
} else if (repos.length > 0) {
247-
// If we have repos, just refresh in background
248-
setIsRefreshing(true)
249219
}
250220

251221
const response = await fetch(`/api/github/repos?owner=${selectedOwner}`)
252222

253223
if (!response.ok) {
254224
if (response.status === 401 || response.status === 403) {
255-
// Clear cache
256-
localStorage.removeItem('github-owners')
257-
Object.keys(localStorage).forEach((key) => {
258-
if (key.startsWith('github-repos-')) {
259-
localStorage.removeItem(key)
260-
}
261-
})
225+
// Clear cache using atoms
226+
setOwners(null)
262227

263228
// Call backend to disconnect GitHub
264229
try {
@@ -281,8 +246,7 @@ export function RepoSelector({
281246

282247
const reposList = await response.json()
283248
setRepos(reposList)
284-
// Cache the repos
285-
localStorage.setItem(cacheKey, JSON.stringify(reposList))
249+
// Cache is automatic with atomWithStorage
286250
} catch (error) {
287251
console.error('Error loading repos:', error)
288252

@@ -306,11 +270,11 @@ export function RepoSelector({
306270

307271
loadRepos()
308272
} else {
309-
setRepos([])
273+
setRepos(null)
310274
setLoadingRepos(false)
311275
}
312276
// eslint-disable-next-line react-hooks/exhaustive-deps
313-
}, [selectedOwner, setGitHubConnection])
277+
}, [selectedOwner, setGitHubConnection, setOwners, setRepos])
314278

315279
// Focus filter input when dropdown opens (but not on mobile to prevent keyboard popup)
316280
useEffect(() => {
@@ -343,7 +307,7 @@ export function RepoSelector({
343307
const hasMoreRepos = filteredRepos.length > 50
344308

345309
// Ensure selected repo is in the displayed list (if it matches current filter)
346-
if (selectedRepo && repos.length > 0) {
310+
if (selectedRepo && repos && repos.length > 0) {
347311
const isInFilteredRepos = filteredRepos.find((repo) => repo.name === selectedRepo)
348312
const isInDisplayedRepos = displayedRepos.find((repo) => repo.name === selectedRepo)
349313

@@ -357,7 +321,7 @@ export function RepoSelector({
357321
onOwnerChange(value)
358322
onRepoChange('') // Reset repo when owner changes
359323
setRepoFilter('') // Reset filter when owner changes
360-
setRepos([]) // Clear repos to trigger loading state for new owner
324+
setRepos(null) // Clear repos to trigger loading state for new owner
361325
}
362326

363327
const handleRepoChange = (value: string) => {
@@ -375,11 +339,11 @@ export function RepoSelector({
375339
: 'w-auto min-w-[160px] border-0 bg-transparent shadow-none focus:ring-0 h-8'
376340

377341
// Find the selected owner for avatar display
378-
const selectedOwnerData = owners.find((owner) => owner.login === selectedOwner)
342+
const selectedOwnerData = owners?.find((owner) => owner.login === selectedOwner)
379343

380344
// Determine if we should show loading indicators
381-
const showOwnersLoading = loadingOwners && owners.length === 0
382-
const showReposLoading = loadingRepos && repos.length === 0
345+
const showOwnersLoading = loadingOwners && (!owners || owners.length === 0)
346+
const showReposLoading = loadingRepos && (!repos || repos.length === 0)
383347

384348
return (
385349
<div className="flex items-center gap-1 sm:gap-2 h-8">
@@ -409,20 +373,21 @@ export function RepoSelector({
409373
)}
410374
</SelectTrigger>
411375
<SelectContent>
412-
{owners.map((owner) => (
413-
<SelectItem key={owner.login} value={owner.login}>
414-
<div className="flex items-center gap-2">
415-
<Image
416-
src={owner.avatar_url}
417-
alt={owner.login}
418-
width={16}
419-
height={16}
420-
className="w-4 h-4 rounded-full"
421-
/>
422-
<span>{owner.login}</span>
423-
</div>
424-
</SelectItem>
425-
))}
376+
{owners &&
377+
owners.map((owner) => (
378+
<SelectItem key={owner.login} value={owner.login}>
379+
<div className="flex items-center gap-2">
380+
<Image
381+
src={owner.avatar_url}
382+
alt={owner.login}
383+
width={16}
384+
height={16}
385+
className="w-4 h-4 rounded-full"
386+
/>
387+
<span>{owner.login}</span>
388+
</div>
389+
</SelectItem>
390+
))}
426391
</SelectContent>
427392
</Select>
428393

0 commit comments

Comments
 (0)