@@ -18,7 +18,16 @@ type StaffAuthState = {
1818 isRestoring : boolean ;
1919} ;
2020
21- const StaffAuthContext = createContext < any > ( null ) ;
21+ type StaffAuthContextType = {
22+ auth : StaffAuthState ;
23+ loginSuccess : ( data : {
24+ accessToken : string ;
25+ staff : Staff ;
26+ } ) => void ;
27+ logout : ( ) => Promise < void > ;
28+ } ;
29+
30+ const StaffAuthContext = createContext < StaffAuthContextType | null > ( null ) ;
2231
2332export function StaffAuthProvider ( { children } : { children : ReactNode } ) {
2433 const [ auth , setAuth ] = useState < StaffAuthState > ( {
@@ -28,11 +37,15 @@ export function StaffAuthProvider({ children }: { children: ReactNode }) {
2837 } ) ;
2938
3039 const tokenRef = useRef < string | null > ( null ) ;
40+ const refreshingRef = useRef < Promise < string > | null > ( null ) ;
3141
3242 useEffect ( ( ) => {
3343 tokenRef . current = auth . accessToken ;
3444 } , [ auth . accessToken ] ) ;
3545
46+ /**
47+ * ✅ Axios interceptor
48+ */
3649 useEffect ( ( ) => {
3750 const reqId = api . interceptors . request . use ( config => {
3851 if ( tokenRef . current ) {
@@ -41,52 +54,131 @@ export function StaffAuthProvider({ children }: { children: ReactNode }) {
4154 return config ;
4255 } ) ;
4356
57+ const resId = api . interceptors . response . use (
58+ res => res ,
59+ async error => {
60+ const original = error . config ;
61+
62+ if (
63+ error . response ?. status !== 401 ||
64+ original ?. _retry ||
65+ original ?. url ?. includes ( '/staff/public/auth/login' ) ||
66+ original ?. url ?. includes ( '/staff/public/auth/refresh' )
67+ ) {
68+ return Promise . reject ( error ) ;
69+ }
70+
71+ original . _retry = true ;
72+
73+ try {
74+ if ( ! refreshingRef . current ) {
75+ refreshingRef . current = refreshStaff ( ) . then ( res => {
76+ tokenRef . current = res . accessToken ;
77+ setAuth ( prev => ( {
78+ ...prev ,
79+ accessToken : res . accessToken ,
80+ } ) ) ;
81+ refreshingRef . current = null ;
82+ return res . accessToken ;
83+ } ) ;
84+ }
85+
86+ const newToken = await refreshingRef . current ;
87+ original . headers . Authorization = `Bearer ${ newToken } ` ;
88+ return api ( original ) ;
89+ } catch {
90+ refreshingRef . current = null ;
91+ setAuth ( {
92+ accessToken : null ,
93+ staff : null ,
94+ isRestoring : false ,
95+ } ) ;
96+ return Promise . reject ( error ) ;
97+ }
98+ }
99+ ) ;
100+
44101 return ( ) => {
45102 api . interceptors . request . eject ( reqId ) ;
103+ api . interceptors . response . eject ( resId ) ;
46104 } ;
47105 } , [ ] ) ;
48106
107+ /**
108+ * ✅ Restore session ONCE on mount
109+ */
49110 useEffect ( ( ) => {
50- async function restore ( ) {
111+ let cancelled = false ;
112+
113+ ( async ( ) => {
51114 try {
52115 const refreshRes = await refreshStaff ( ) ;
53116
54117 tokenRef . current = refreshRes . accessToken ;
55118
56119 const profile = await api . get ( '/staff/public/auth/me' ) ;
57120
121+ if ( cancelled ) return ;
122+
58123 setAuth ( {
59124 accessToken : refreshRes . accessToken ,
60125 staff : profile . data ,
61126 isRestoring : false ,
62127 } ) ;
63128 } catch {
129+ if ( cancelled ) return ;
130+
64131 setAuth ( {
65132 accessToken : null ,
66133 staff : null ,
67134 isRestoring : false ,
68135 } ) ;
69136 }
70- }
137+ } ) ( ) ;
71138
72- restore ( ) ;
139+ return ( ) => {
140+ cancelled = true ;
141+ } ;
73142 } , [ ] ) ;
74143
75- function loginSuccess ( data : any ) {
144+ function loginSuccess ( data : {
145+ accessToken : string ;
146+ staff : Staff ;
147+ } ) {
148+ tokenRef . current = data . accessToken ;
149+
76150 setAuth ( {
77151 accessToken : data . accessToken ,
78152 staff : data . staff ,
79153 isRestoring : false ,
80154 } ) ;
81155 }
82156
157+ async function logout ( ) {
158+ try {
159+ await api . post ( '/staff/public/auth/logout' ) ;
160+ } catch { }
161+
162+ tokenRef . current = null ;
163+
164+ setAuth ( {
165+ accessToken : null ,
166+ staff : null ,
167+ isRestoring : false ,
168+ } ) ;
169+ }
170+
83171 return (
84- < StaffAuthContext . Provider value = { { auth, loginSuccess } } >
172+ < StaffAuthContext . Provider value = { { auth, loginSuccess, logout } } >
85173 { children }
86174 </ StaffAuthContext . Provider >
87175 ) ;
88176}
89177
90178export function useStaffAuth ( ) {
91- return useContext ( StaffAuthContext ) ;
179+ const ctx = useContext ( StaffAuthContext ) ;
180+ if ( ! ctx ) {
181+ throw new Error ( 'useStaffAuth must be used inside StaffAuthProvider' ) ;
182+ }
183+ return ctx ;
92184}
0 commit comments