11import { Menu , MenuButton , MenuItem , MenuItems } from '@headlessui/react' ;
22import { OptionsStore } from '@shared/storage/OptionsStore' ;
3+ import { UTRP_LOGIN_URL } from '@shared/util/appUrls' ;
34import styles from '@views/components/calendar/CalendarHeader/CalendarHeader.module.scss' ;
45import { Button } from '@views/components/common/Button' ;
56import Divider from '@views/components/common/Divider' ;
@@ -13,7 +14,7 @@ import { useActiveSchedule } from '@views/hooks/useSchedules';
1314import refreshCourses from '@views/lib/refreshCourses' ;
1415import clsx from 'clsx' ;
1516import type { JSX } from 'react' ;
16- import { useCallback , useEffect , useRef , useState } from 'react' ;
17+ import { useCallback , useState } from 'react' ;
1718import ArrowsClockwiseIcon from '~icons/ph/arrows-clockwise' ;
1819import CalendarDotsIcon from '~icons/ph/calendar-dots' ;
1920import ExportIcon from '~icons/ph/export' ;
@@ -37,28 +38,25 @@ export default function CalendarHeader({ sidebarOpen, onSidebarToggle }: Calenda
3738 const activeSchedule = useActiveSchedule ( ) ;
3839 const lastCheckedText = useRelativeTime ( activeSchedule . lastCheckedAt ) ;
3940 const [ isRefreshing , setIsRefreshing ] = useState ( false ) ;
41+ const [ showLoginPrompt , setShowLoginPrompt ] = useState ( false ) ;
4042 // track per-schedule cooldowns so switching schedules allows immediate refresh
4143 const [ cooldownIds , setCooldownIds ] = useState < Set < string > > ( new Set ( ) ) ;
4244 const enableDataRefreshing = OptionsStore . useStore ( store => store . enableDataRefreshing ) ;
4345
4446 const isCooldown = cooldownIds . has ( activeSchedule . id ) ;
4547 const hasRightHandSide = enableDataRefreshing ;
46- const isRefreshingRef = useRef ( false ) ;
47- isRefreshingRef . current = isRefreshing ;
4848
4949 const handleRefresh = useCallback ( async ( ) => {
50+ if ( isRefreshing ) return ;
5051 setIsRefreshing ( true ) ;
5152 // Ensure the spinner is visible long enough to provide visual feedback
52- const minSpin = new Promise ( r => setTimeout ( r , 400 ) ) ;
53+ const minSpin = new Promise ( resolve => setTimeout ( resolve , 400 ) ) ;
5354 try {
54- await chrome . storage . session . set ( { pendingRefresh : true } ) ;
5555 const [ success ] = await Promise . all ( [ refreshCourses ( ) , minSpin ] ) ;
56- if ( success ) {
57- await chrome . storage . session . remove ( 'pendingRefresh' ) ;
58- }
56+ setShowLoginPrompt ( ! success ) ;
5957 } catch ( error ) {
6058 console . error ( 'Failed to refresh courses:' , error ) ;
61- await chrome . storage . session . remove ( 'pendingRefresh' ) ;
59+ setShowLoginPrompt ( true ) ;
6260 } finally {
6361 setIsRefreshing ( false ) ;
6462 const scheduleId = activeSchedule . id ;
@@ -71,27 +69,7 @@ export default function CalendarHeader({ sidebarOpen, onSidebarToggle }: Calenda
7169 } ) ;
7270 } , 3000 ) ;
7371 }
74- } , [ activeSchedule ] ) ;
75-
76- // Auto-retry refresh after login redirect
77- useEffect ( ( ) => {
78- const checkPendingRefresh = async ( ) => {
79- const { pendingRefresh } = await chrome . storage . session . get ( 'pendingRefresh' ) ;
80- if ( pendingRefresh && ! isRefreshingRef . current ) {
81- handleRefresh ( ) ;
82- }
83- } ;
84-
85- checkPendingRefresh ( ) ;
86-
87- const onVisibilityChange = ( ) => {
88- if ( document . visibilityState === 'visible' ) {
89- checkPendingRefresh ( ) ;
90- }
91- } ;
92- document . addEventListener ( 'visibilitychange' , onVisibilityChange ) ;
93- return ( ) => document . removeEventListener ( 'visibilitychange' , onVisibilityChange ) ;
94- } , [ handleRefresh ] ) ;
72+ } , [ activeSchedule , isRefreshing ] ) ;
9573
9674 return (
9775 < div
@@ -206,11 +184,26 @@ export default function CalendarHeader({ sidebarOpen, onSidebarToggle }: Calenda
206184 </ div >
207185 { hasRightHandSide && < Divider className = 'self-center' size = '1.75rem' orientation = 'vertical' /> }
208186 < div className = { clsx ( styles . secondaryActions , 'flex items-center gap-3 ml-auto' ) } >
209- { enableDataRefreshing && lastCheckedText && (
210- < Text variant = 'mini' className = 'whitespace-nowrap text-theme-black/50 !font-normal' >
211- Last checked: { lastCheckedText }
212- </ Text >
213- ) }
187+ { enableDataRefreshing &&
188+ ( showLoginPrompt ? (
189+ < Text variant = 'mini' className = 'whitespace-nowrap text-theme-black/50 !font-normal' >
190+ < a
191+ href = { UTRP_LOGIN_URL }
192+ target = '_blank'
193+ rel = 'noreferrer'
194+ className = 'text-ut-burntorange underline'
195+ >
196+ Log in
197+ </ a >
198+ { ' to refresh course data' }
199+ </ Text >
200+ ) : (
201+ lastCheckedText && (
202+ < Text variant = 'mini' className = 'whitespace-nowrap text-theme-black/50 !font-normal' >
203+ Last checked: { lastCheckedText }
204+ </ Text >
205+ )
206+ ) ) }
214207 { enableDataRefreshing && (
215208 < Button
216209 color = 'ut-black'
0 commit comments