@@ -5,8 +5,9 @@ import Image from 'next/image'
55import { Input } from '@/components/ui/input'
66import { Select , SelectContent , SelectItem , SelectTrigger , SelectValue } from '@/components/ui/select'
77import { Lock , Loader2 } from 'lucide-react'
8- import { useAtomValue , useSetAtom } from 'jotai'
8+ import { useAtomValue , useSetAtom , useAtom } from 'jotai'
99import { githubConnectionAtom } from '@/lib/atoms/github-connection'
10+ import { githubOwnersAtom , githubReposAtomFamily } from '@/lib/atoms/github-cache'
1011
1112interface 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