From bf5c67fc7e779be3143f88ba5885174d577f7d2b Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 31 Mar 2026 16:16:12 +0700 Subject: [PATCH] 690331:1616 Correspondence Page Refactor by GPT-5.3-Codex Medium #03 --- frontend/components/auth/auth-sync.tsx | 3 + .../components/correspondences/detail.tsx | 15 +---- frontend/lib/api/client.ts | 67 ++++++++++++++----- frontend/proxy.ts | 3 +- 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/frontend/components/auth/auth-sync.tsx b/frontend/components/auth/auth-sync.tsx index e363eb9..194e74b 100644 --- a/frontend/components/auth/auth-sync.tsx +++ b/frontend/components/auth/auth-sync.tsx @@ -3,6 +3,7 @@ import { useSession, signOut } from 'next-auth/react'; import { useEffect } from 'react'; import { useAuthStore } from '@/lib/stores/auth-store'; +import { clearAuthTokenCache } from '@/lib/api/client'; export function AuthSync() { const { data: session, status } = useSession(); @@ -10,6 +11,7 @@ export function AuthSync() { useEffect(() => { if (session?.error === 'RefreshAccessTokenError') { + clearAuthTokenCache(); // Clear cached token on auth error signOut({ callbackUrl: '/login' }); } else if (status === 'authenticated' && session?.user) { // Map NextAuth session to AuthStore user @@ -40,6 +42,7 @@ export function AuthSync() { (session as { accessToken?: string }).accessToken || '' ); } else if (status === 'unauthenticated') { + clearAuthTokenCache(); // Clear cached token on logout logout(); } }, [session, status, setAuth, logout]); diff --git a/frontend/components/correspondences/detail.tsx b/frontend/components/correspondences/detail.tsx index 23ebb13..aa36561 100644 --- a/frontend/components/correspondences/detail.tsx +++ b/frontend/components/correspondences/detail.tsx @@ -25,15 +25,6 @@ interface CorrespondenceDetailProps { selectedRevisionId?: string; } -const normalizeUuid = (value?: string): string | undefined => { - if (typeof value !== 'string') { - return undefined; - } - - const normalized = value.trim().toLowerCase(); - return normalized.length > 0 ? normalized : undefined; -}; - const normalizeRecipientType = (value?: string): string | undefined => { if (typeof value !== 'string') { return undefined; @@ -54,9 +45,9 @@ export function CorrespondenceDetail({ data, selectedRevisionId }: Correspondenc if (!data) return
No data found
; - const normalizedSelectedRevisionId = normalizeUuid(selectedRevisionId); - const selectedRevision = normalizedSelectedRevisionId - ? data.revisions?.find((r) => normalizeUuid(r.publicId) === normalizedSelectedRevisionId) + // เลือก revision ตาม selectedRevisionId ถ้ามี ถ้าไม่มีใช้ revision ปัจจุบัน + const selectedRevision = selectedRevisionId + ? data.revisions?.find((r) => r.publicId === selectedRevisionId) : undefined; const currentRevision = selectedRevision || data.revisions?.find((r) => r.isCurrent) || data.revisions?.[0]; const subject = currentRevision?.subject || '-'; diff --git a/frontend/lib/api/client.ts b/frontend/lib/api/client.ts index 9d9d88f..964612a 100644 --- a/frontend/lib/api/client.ts +++ b/frontend/lib/api/client.ts @@ -5,6 +5,49 @@ import { v4 as uuidv4 } from 'uuid'; // อ่านค่า Base URL จาก Environment Variable const baseURL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api'; +// Token cache for API calls outside React components +let cachedToken: string | null = null; +let tokenPromise: Promise | null = null; + +// Async function to get token +async function getAuthToken(): Promise { + if (cachedToken) return cachedToken; + + if (tokenPromise) return tokenPromise; + + tokenPromise = (async () => { + try { + if (typeof window !== 'undefined') { + const { getSession } = await import('next-auth/react'); + const session = await getSession(); + cachedToken = session?.accessToken || null; + return cachedToken; + } + } catch (_error) { + // Fallback to localStorage + try { + const authStorage = localStorage.getItem('auth-storage'); + if (authStorage) { + const parsed = JSON.parse(authStorage); + cachedToken = parsed?.state?.token || null; + return cachedToken; + } + } catch (__error) { + // All methods failed + } + } + return null; + })(); + + return tokenPromise; +} + +// Function to clear token cache (call on logout) +export function clearAuthTokenCache(): void { + cachedToken = null; + tokenPromise = null; +} + // สร้าง Axios Instance หลัก const apiClient: AxiosInstance = axios.create({ baseURL, @@ -28,20 +71,11 @@ apiClient.interceptors.request.use( } // 2. Authentication Token Injection - // ดึง Token จาก Zustand persist store (localStorage) + // ดึง Token จาก NextAuth session ผ่าน getSession() if (typeof window !== 'undefined') { - try { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - const parsed = JSON.parse(authStorage); - const token = parsed?.state?.token; - - if (token) { - config.headers['Authorization'] = `Bearer ${token}`; - } - } - } catch (_error) { - // Auth token retrieval failed - request will proceed without token + const token = await getAuthToken(); + if (token) { + config.headers['Authorization'] = `Bearer ${token}`; } } @@ -66,8 +100,11 @@ apiClient.interceptors.response.use( // กรณี Token หมดอายุ หรือ ไม่มีสิทธิ์ if (status === 401) { - // Unauthorized: redirect handled by auth interceptor - // สามารถเพิ่ม Logic Redirect ไปหน้า Login ได้ถ้าต้องการ + // Clear cached token and redirect to login + clearAuthTokenCache(); + if (typeof window !== 'undefined') { + window.location.href = '/login'; + } } } return Promise.reject(error); diff --git a/frontend/proxy.ts b/frontend/proxy.ts index 45143a3..96ca4aa 100644 --- a/frontend/proxy.ts +++ b/frontend/proxy.ts @@ -55,8 +55,9 @@ export const config = { * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) + * - robots.txt (robots file) * - images (public images) */ - '/((?!api|_next/static|_next/image|favicon.ico|images).*)', + '/((?!api|_next/static|_next/image|favicon.ico|robots.txt|images).*)', ], };