From b585daddde5c25933ed5d0f01782663f2598e031 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 17 Jun 2025 18:05:22 +0200 Subject: [PATCH] FEAT: Enhance berth color handling in dashboard components and improve authentication middleware with caching --- components/BerthDetailsModal.vue | 122 +++++++++++++++++----- middleware/authentication.ts | 56 +++++++++- pages/dashboard/berth-list.vue | 13 ++- pages/dashboard/interest-berth-status.vue | 11 +- utils/berthColors.ts | 32 ++++++ 5 files changed, 202 insertions(+), 32 deletions(-) create mode 100644 utils/berthColors.ts diff --git a/components/BerthDetailsModal.vue b/components/BerthDetailsModal.vue index ddf71e7..041c300 100644 --- a/components/BerthDetailsModal.vue +++ b/components/BerthDetailsModal.vue @@ -2,10 +2,10 @@ @@ -688,48 +688,122 @@ onUnmounted(() => { diff --git a/middleware/authentication.ts b/middleware/authentication.ts index 6160f87..a7323b0 100644 --- a/middleware/authentication.ts +++ b/middleware/authentication.ts @@ -10,11 +10,48 @@ export default defineNuxtRouteMiddleware(async (to) => { return; } + // Skip auth check if we're already on the login page to prevent redirect loops + if (to.path === '/login' || to.path.startsWith('/auth')) { + return; + } + console.log('[MIDDLEWARE] Checking authentication for route:', to.path); + // Use a cached auth state to avoid excessive API calls + const nuxtApp = useNuxtApp(); + const cacheKey = 'auth:session:cache'; + const cacheExpiry = 30000; // 30 seconds cache + + // 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'); + if (cachedSession.authenticated && cachedSession.user) { + return; + } + return navigateTo('/login'); + } + try { - // Check Keycloak authentication via session API - const sessionData = await $fetch('/api/auth/session') as any; + // Check Keycloak authentication via session API with timeout + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); // 5 second timeout + + const sessionData = await $fetch('/api/auth/session', { + signal: controller.signal, + retry: 1, + retryDelay: 500 + }) as any; + + clearTimeout(timeout); + + // Cache the session data + nuxtApp.payload.data[cacheKey] = { + ...sessionData, + timestamp: now + }; console.log('[MIDDLEWARE] Session check result:', { authenticated: sessionData.authenticated, @@ -30,8 +67,21 @@ export default defineNuxtRouteMiddleware(async (to) => { console.log('[MIDDLEWARE] No valid authentication found, redirecting to login'); return navigateTo('/login'); - } catch (error) { + } catch (error: any) { console.error('[MIDDLEWARE] Auth check failed:', error); + + // If it's a network error or timeout, check if we have a recent cached session + if (error.name === 'AbortError' || error.code === 'ECONNREFUSED') { + console.log('[MIDDLEWARE] Network error, checking for recent cache'); + const recentCache = nuxtApp.payload.data[cacheKey]; + if (recentCache && recentCache.timestamp && (now - recentCache.timestamp) < 300000) { // 5 minutes + console.log('[MIDDLEWARE] Using recent cache despite network error'); + if (recentCache.authenticated && recentCache.user) { + return; + } + } + } + return navigateTo('/login'); } }); diff --git a/pages/dashboard/berth-list.vue b/pages/dashboard/berth-list.vue index df3fa68..ac300d3 100644 --- a/pages/dashboard/berth-list.vue +++ b/pages/dashboard/berth-list.vue @@ -188,7 +188,11 @@
- + {{ berth['Mooring Number'] }} @@ -272,7 +276,11 @@ >
- + {{ berth['Mooring Number'] }} @@ -326,6 +334,7 @@ import { ref, computed, onMounted } from 'vue'; import { useFetch } from '#app'; import type { Berth, BerthsResponse } from '@/utils/types'; import { BerthArea } from '@/utils/types'; +import { getBerthColorFromMooringNumber } from '@/utils/berthColors'; import BerthStatusBadge from '@/components/BerthStatusBadge.vue'; import BerthDetailsModal from '@/components/BerthDetailsModal.vue'; diff --git a/pages/dashboard/interest-berth-status.vue b/pages/dashboard/interest-berth-status.vue index e55f082..4540bc4 100644 --- a/pages/dashboard/interest-berth-status.vue +++ b/pages/dashboard/interest-berth-status.vue @@ -115,8 +115,8 @@ - -
+ +
- + {{ berth['Mooring Number'] }} @@ -261,6 +265,7 @@ import { ref, computed } from 'vue'; import { useFetch } from '#app'; import type { Berth, BerthsResponse } from '@/utils/types'; import { BerthArea, BerthStatus } from '@/utils/types'; +import { getBerthColorFromMooringNumber } from '@/utils/berthColors'; import BerthStatusBadge from '@/components/BerthStatusBadge.vue'; import BerthDetailsModal from '@/components/BerthDetailsModal.vue'; diff --git a/utils/berthColors.ts b/utils/berthColors.ts new file mode 100644 index 0000000..3ad6b58 --- /dev/null +++ b/utils/berthColors.ts @@ -0,0 +1,32 @@ +/** + * Berth area color mapping based on the Port Nimara layout + */ +export const berthAreaColors: Record = { + 'A': '#E67E22', // Orange/Brown + 'B': '#F1C40F', // Yellow + 'C': '#27AE60', // Green + 'D': '#3498DB', // Blue + 'E': '#E91E63', // Pink + 'F': '#9B59B6', // Purple (if exists) + 'G': '#1ABC9C', // Teal (if exists) +}; + +/** + * Get the color for a berth area + * @param area - The area letter + * @returns The hex color code for the area + */ +export function getBerthAreaColor(area: string): string { + return berthAreaColors[area?.toUpperCase()] || '#757575'; // Grey fallback +} + +/** + * Get the color from a mooring number (e.g., "A1" -> orange) + * @param mooringNumber - The full mooring number + * @returns The hex color code for the area + */ +export function getBerthColorFromMooringNumber(mooringNumber: string): string { + if (!mooringNumber) return '#757575'; // Grey fallback + const area = mooringNumber.charAt(0).toUpperCase(); + return getBerthAreaColor(area); +}