diff --git a/components/ExpenseDetailsModal.vue b/components/ExpenseDetailsModal.vue index c77b1b7..a7a53a5 100644 --- a/components/ExpenseDetailsModal.vue +++ b/components/ExpenseDetailsModal.vue @@ -28,7 +28,7 @@ {{ expense.DisplayPrice || expense.Price }}
- + Rate: {{ expense.ConversionRate }} | USD: {{ expense.DisplayPriceUSD }}
diff --git a/components/ExpenseList.vue b/components/ExpenseList.vue index 93392bf..d2a06d6 100644 --- a/components/ExpenseList.vue +++ b/components/ExpenseList.vue @@ -64,12 +64,12 @@ - +{{ expense.Receipt.length - 1 }} + {{ expense.Category || 'Other' }} diff --git a/middleware/authentication.ts b/middleware/authentication.ts index 2c73914..2c4bda0 100644 --- a/middleware/authentication.ts +++ b/middleware/authentication.ts @@ -20,14 +20,14 @@ export default defineNuxtRouteMiddleware(async (to) => { // Use a cached auth state to avoid excessive API calls const nuxtApp = useNuxtApp(); const cacheKey = 'auth:session:cache'; - const cacheExpiry = 5 * 60 * 1000; // 5 minutes cache (increased from 30 seconds) + const cacheExpiry = 15 * 60 * 1000; // 15 minutes cache (increased for better UX) // Check if we have a cached session const cachedSession = nuxtApp.payload.data?.[cacheKey]; const now = Date.now(); if (cachedSession && cachedSession.timestamp && (now - cachedSession.timestamp) < cacheExpiry) { - console.log('[MIDDLEWARE] Using cached session'); + console.log('[MIDDLEWARE] Using cached session (age:', Math.round((now - cachedSession.timestamp) / 1000), 'seconds)'); if (cachedSession.authenticated && cachedSession.user) { // Store auth state for components if (!nuxtApp.payload.data) { diff --git a/pages/dashboard/expenses.vue b/pages/dashboard/expenses.vue index 1299082..c33f7ed 100644 --- a/pages/dashboard/expenses.vue +++ b/pages/dashboard/expenses.vue @@ -24,7 +24,7 @@ - + - + - + - + + + Apply + + + + Export Options: -
+
-
+
${{ formatPrice(berth.Price) }} - - {{ getInterestedCount(berth) }} interested - + + +
+
Interested Parties:
+
+ {{ party['Full Name'] }} +
+
+
diff --git a/plugins/01.auth-refresh.client.ts b/plugins/01.auth-refresh.client.ts index 33a9f3b..4f81503 100644 --- a/plugins/01.auth-refresh.client.ts +++ b/plugins/01.auth-refresh.client.ts @@ -14,8 +14,8 @@ export default defineNuxtPlugin(() => { refreshTimer = null } - // Calculate time until refresh (refresh 5 minutes before expiry) - const refreshBuffer = 5 * 60 * 1000 // 5 minutes in milliseconds + // Calculate time until refresh (refresh 10 minutes before expiry for better safety margin) + const refreshBuffer = 10 * 60 * 1000 // 10 minutes in milliseconds (increased from 5) const timeUntilRefresh = expiresAt - Date.now() - refreshBuffer console.log('[AUTH_REFRESH] Scheduling token refresh in:', Math.max(0, timeUntilRefresh), 'ms') diff --git a/server/api/expenses/generate-pdf.ts b/server/api/expenses/generate-pdf.ts index 3c9e93e..187f2cb 100644 --- a/server/api/expenses/generate-pdf.ts +++ b/server/api/expenses/generate-pdf.ts @@ -748,56 +748,22 @@ async function fetchReceiptImage(receipt: any): Promise { console.log('[expenses/generate-pdf] Detected S3 URL, fetching directly...'); try { - // Ensure URL is properly encoded - let encodedUrl = rawPath; - try { - // Parse and reconstruct URL to ensure proper encoding - const url = new URL(rawPath); - // Re-encode the pathname to handle special characters - url.pathname = url.pathname.split('/').map(segment => encodeURIComponent(decodeURIComponent(segment))).join('/'); - encodedUrl = url.toString(); - console.log('[expenses/generate-pdf] URL encoded:', encodedUrl); - } catch (urlError) { - console.log('[expenses/generate-pdf] Using original URL (encoding failed):', rawPath); - encodedUrl = rawPath; - } + // Use the signed URL directly without modification to preserve AWS signature + console.log('[expenses/generate-pdf] Fetching from S3 URL (preserving signature):', rawPath); - // Fetch image directly from S3 URL with proper headers - const response = await fetch(encodedUrl, { + // Fetch image directly from S3 URL with minimal headers to avoid signature issues + const response = await fetch(rawPath, { method: 'GET', headers: { - 'Accept': 'image/*', - 'User-Agent': 'PortNimara-Client-Portal/1.0', - 'Cache-Control': 'no-cache' + 'Accept': 'image/*' }, // Add timeout to prevent hanging - signal: AbortSignal.timeout(45000) // 45 second timeout + signal: AbortSignal.timeout(30000) // 30 second timeout }); if (!response.ok) { console.error(`[expenses/generate-pdf] Failed to fetch image from S3: ${response.status} ${response.statusText}`); console.error('[expenses/generate-pdf] Response headers:', Object.fromEntries(response.headers.entries())); - - // Try with the original URL if encoding failed - if (encodedUrl !== rawPath) { - console.log('[expenses/generate-pdf] Retrying with original URL...'); - const originalResponse = await fetch(rawPath, { - method: 'GET', - headers: { - 'Accept': 'image/*', - 'User-Agent': 'PortNimara-Client-Portal/1.0' - }, - signal: AbortSignal.timeout(30000) - }); - - if (originalResponse.ok) { - const arrayBuffer = await originalResponse.arrayBuffer(); - const imageBuffer = Buffer.from(arrayBuffer); - console.log('[expenses/generate-pdf] Successfully fetched with original URL, Size:', imageBuffer.length); - return imageBuffer; - } - } - return null; } @@ -810,27 +776,13 @@ async function fetchReceiptImage(receipt: any): Promise { } catch (fetchError: any) { console.error('[expenses/generate-pdf] Error fetching from S3 URL:', fetchError.message); + console.error('[expenses/generate-pdf] Error details:', { + name: fetchError.name, + code: fetchError.code, + message: fetchError.message + }); - // If it's a timeout or network error, try one more time with simpler approach - if (fetchError.name === 'TimeoutError' || fetchError.name === 'AbortError' || fetchError.code === 'ECONNRESET') { - console.log('[expenses/generate-pdf] Network error, trying simplified approach...'); - try { - const simpleResponse = await fetch(rawPath, { - method: 'GET', - signal: AbortSignal.timeout(90000) // Extended timeout for final attempt - }); - - if (simpleResponse.ok) { - const arrayBuffer = await simpleResponse.arrayBuffer(); - const imageBuffer = Buffer.from(arrayBuffer); - console.log('[expenses/generate-pdf] Successfully fetched image with simplified approach, Size:', imageBuffer.length); - return imageBuffer; - } - } catch (finalError) { - console.error('[expenses/generate-pdf] Final attempt also failed:', finalError); - } - } - + // Don't try multiple attempts for signed URLs as they may expire return null; } }