@@ -12,11 +12,14 @@ import React, { useCallback, useEffect, useState } from "react";
1212import { browser } from "wxt/browser" ;
1313import Button from "@/components/ui/button" ;
1414import logo from "@/public/logo.png" ;
15- import { isLoggedIn } from "@/features/audit-scraping/audit-history-sync" ;
15+ import {
16+ getCachedLoginState ,
17+ openLoginTab ,
18+ refreshLoginState ,
19+ watchLoginState ,
20+ } from "@/features/audit-scraping/login-state" ;
1621import PopupAuditCard from "./popup-audit-card" ;
1722
18- const AUDIT_HOME_URL = "https://utdirect.utexas.edu/apps/degree/audits/" ;
19-
2023export default function App ( ) {
2124 const [ audits , setAudits ] = useState < AuditHistoryEntry [ ] > ( [ ] ) ;
2225 const [ loading , setLoading ] = useState ( true ) ;
@@ -46,8 +49,12 @@ export default function App() {
4649 . finally ( ( ) => setLoading ( false ) ) ;
4750 } , [ applyAuditHistory ] ) ;
4851
52+ // chache login state.
4953 useEffect ( ( ) => {
50- void isLoggedIn ( ) . then ( setLoggedIn ) ;
54+ void getCachedLoginState ( ) . then ( ( cached ) => {
55+ setLoggedIn ( ( current ) => current ?? cached ?? false ) ;
56+ } ) ;
57+ void refreshLoginState ( ) . then ( setLoggedIn ) ;
5158 } , [ ] ) ;
5259
5360 useEffect ( ( ) => {
@@ -74,10 +81,16 @@ export default function App() {
7481 applyAuditHistory ( data ) ;
7582 } ) ;
7683
84+ // Follow login-state writes from other contexts (content script, background)
85+ const unwatchLoginState = watchLoginState ( ( value ) => {
86+ if ( value !== null ) setLoggedIn ( value ) ;
87+ } ) ;
88+
7789 browser . runtime . onMessage . addListener ( listener ) ;
7890 return ( ) => {
7991 browser . runtime . onMessage . removeListener ( listener ) ;
8092 unwatchAuditHistory ( ) ;
93+ unwatchLoginState ( ) ;
8194 } ;
8295 } , [ applyAuditHistory ] ) ;
8396
@@ -91,22 +104,29 @@ export default function App() {
91104 // Send message to background script to run audit (background has access to tabs/scripting APIs)
92105 const handleRerunAudit = async ( ) => {
93106 setRunningAudit ( true ) ;
94- sendRuntimeMessage ( { type : "RUN_NEW_AUDIT" } ) ;
107+ const stillLoggedIn = await refreshLoginState ( ) ;
108+ setLoggedIn ( stillLoggedIn ) ;
109+ if ( ! stillLoggedIn ) {
110+ setRunningAudit ( false ) ;
111+ handleLogin ( ) ;
112+ return ;
113+ }
114+ const response = await sendRuntimeMessage ( { type : "RUN_NEW_AUDIT" } ) ;
115+ // Background refuses when the session is dead (it opens the login page
116+ // instead) — don't leave the spinner running.
117+ if ( response && ! response . success ) setRunningAudit ( false ) ;
95118 } ;
96119
97120 const handleLogin = ( ) => {
98- void browser . tabs . create ( { url : AUDIT_HOME_URL , active : true } ) ;
121+ void openLoginTab ( ) ;
99122 } ;
100123
101124 // Determine which audits to display
102125 const displayedAudits = showAll ? audits : audits . slice ( 0 , 3 ) ;
103126 const hasMoreAudits = audits . length > 3 ;
104- const needsLogin = loggedIn === false && audits . length === 0 ;
105- // An authenticated empty history is a valid state, even if an older sync cached an error.
106- const hasAuthenticatedEmptyHistory = loggedIn === true && audits . length === 0 ;
107- // Only the empty state depends on auth (Login vs. empty); with cached audits we
108- // can render immediately. So wait for auth ONLY when there's nothing to show yet.
109- const resolvingLogin = loggedIn === null && audits . length === 0 ;
127+ // Login button shows whenever we're not logged in, even with cached audits.
128+ // null only lasts until the cache read resolves (milliseconds).
129+ const needsLogin = loggedIn === false ;
110130
111131 return (
112132 < div className = "w-[438px] h-full min-h-[300px] max-h-[600px] bg-background font-sans overflow-hidden flex flex-col border border-gray-100" >
@@ -129,7 +149,7 @@ export default function App() {
129149 className = "rounded-md"
130150 onClick = { needsLogin ? handleLogin : handleRerunAudit }
131151 >
132- { resolvingLogin ? (
152+ { loggedIn === null ? (
133153 < div className = "flex items-center space-x-2" >
134154 < SpinnerIcon size = { 24 } className = "animate-spin-slow" />
135155 </ div >
@@ -176,7 +196,7 @@ export default function App() {
176196 < div className = "flex flex-col gap-2 items-center justify-center text-center mb-6 py-8" >
177197 < p className = "text-base text-dap-gray-light" > Syncing...</ p >
178198 </ div >
179- ) : needsLogin ? (
199+ ) : needsLogin && audits . length === 0 ? (
180200 < div className = "flex flex-col gap-2 items-center justify-center text-center mb-6 py-8" >
181201 < p className = "text-base text-dap-gray-light tracking-[0.32px] max-w-[300px]" >
182202 Log in to UT Direct, then visit the Degree Audit page to load your
0 commit comments