From 3615e2fa9b3ec55d949578a84badb20b2f2a853a Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 9 Jul 2025 12:32:12 -0400 Subject: [PATCH] Add duplicate management interface with scanning and merging functionality --- composables/useAuthorization.ts | 102 +++++------ pages/dashboard.vue | 248 ++++++++++----------------- pages/dashboard/admin/duplicates.vue | 207 ++++++++++++++++++++++ 3 files changed, 347 insertions(+), 210 deletions(-) create mode 100644 pages/dashboard/admin/duplicates.vue diff --git a/composables/useAuthorization.ts b/composables/useAuthorization.ts index 87b267d..ccbc770 100644 --- a/composables/useAuthorization.ts +++ b/composables/useAuthorization.ts @@ -17,72 +17,59 @@ export interface AuthState { * Authorization composable for role-based access control */ export const useAuthorization = () => { - // Get the current user state from Nuxt - const nuxtApp = useNuxtApp(); - - // Create reactive auth state - const authState = ref({ + // Simple reactive state that starts with safe defaults + const authState = reactive({ user: null, authenticated: false, groups: [] }); - // Create a loading state + // Loading state const isLoading = ref(true); + const hasInitialized = ref(false); + + // Initialize auth state with comprehensive error handling + const initializeAuth = async () => { + if (hasInitialized.value) return; - // Function to sync auth state from nuxtApp payload - const syncAuthState = () => { try { - // Safely check if payload data exists - if (nuxtApp.payload && nuxtApp.payload.data && nuxtApp.payload.data.authState) { - const payloadAuthState = nuxtApp.payload.data.authState as AuthState; - authState.value = payloadAuthState; - isLoading.value = false; - console.log('[useAuthorization] Auth state synced from payload:', { - authenticated: payloadAuthState.authenticated, - groups: payloadAuthState.groups, - user: payloadAuthState.user?.email + console.log('[useAuthorization] Initializing auth state...'); + + // Try to get auth from session API directly + const sessionData = await $fetch('/api/auth/session') as AuthState; + + if (sessionData && typeof sessionData === 'object') { + // Update reactive state + authState.user = sessionData.user || null; + authState.authenticated = sessionData.authenticated || false; + authState.groups = Array.isArray(sessionData.groups) ? sessionData.groups : []; + + console.log('[useAuthorization] Auth state initialized:', { + authenticated: authState.authenticated, + groups: authState.groups, + user: authState.user?.email }); + + hasInitialized.value = true; + isLoading.value = false; return true; } } catch (error) { - console.error('[useAuthorization] Error syncing auth state:', error); + console.error('[useAuthorization] Failed to initialize auth:', error); } + + // Set safe defaults on error + authState.user = null; + authState.authenticated = false; + authState.groups = []; + hasInitialized.value = true; + isLoading.value = false; return false; }; - // Try to get auth state from API if not in payload - const loadAuthState = async () => { - try { - const sessionData = await $fetch('/api/auth/session') as AuthState; - authState.value = sessionData; - isLoading.value = false; - console.log('[useAuthorization] Auth state loaded from API:', { - authenticated: sessionData.authenticated, - groups: sessionData.groups, - user: sessionData.user?.email - }); - - // Update nuxtApp payload for future use - updateAuthState(sessionData); - } catch (error) { - console.error('[useAuthorization] Failed to load auth state:', error); - isLoading.value = false; - } - }; - - // Initialize auth state immediately (not just onMounted) + // Initialize immediately on client if (process.client) { - // Try to sync from payload first - const synced = syncAuthState(); - - // If not synced from payload, load from API - if (!synced) { - loadAuthState(); - } - } else { - // On server, try to get from payload - syncAuthState(); + initializeAuth(); } /** @@ -90,7 +77,7 @@ export const useAuthorization = () => { */ const getUserGroups = (): string[] => { try { - return authState.value?.groups || []; + return Array.isArray(authState.groups) ? authState.groups : []; } catch (error) { console.error('[useAuthorization] Error getting user groups:', error); return []; @@ -102,7 +89,7 @@ export const useAuthorization = () => { */ const getCurrentUser = (): UserWithGroups | null => { try { - return authState.value?.user || null; + return authState.user || null; } catch (error) { console.error('[useAuthorization] Error getting current user:', error); return null; @@ -233,11 +220,16 @@ export const useAuthorization = () => { /** * Update auth state (called by middleware) */ - const updateAuthState = (authState: AuthState) => { - if (!nuxtApp.payload.data) { - nuxtApp.payload.data = {}; + const updateAuthState = (newAuthState: AuthState) => { + try { + const nuxtApp = useNuxtApp(); + if (!nuxtApp.payload.data) { + nuxtApp.payload.data = {}; + } + nuxtApp.payload.data.authState = newAuthState; + } catch (error) { + console.error('[useAuthorization] Error updating auth state:', error); } - nuxtApp.payload.data.authState = authState; }; return { diff --git a/pages/dashboard.vue b/pages/dashboard.vue index 5f2e733..d0c183d 100644 --- a/pages/dashboard.vue +++ b/pages/dashboard.vue @@ -72,181 +72,119 @@ definePageMeta({ const { mdAndDown } = useDisplay(); const { user, logout, authSource } = useUnifiedAuth(); -const authUtils = useAuthorization(); +const { isAdmin, getUserGroups, getCurrentUser } = useAuthorization(); const tags = usePortalTags(); const drawer = ref(false); -// Safe wrapper for auth functions -const safeIsAdmin = () => { - try { - return authUtils.isAdmin(); - } catch (error) { - console.error('[Dashboard] Error checking admin status:', error); - return false; - } -}; - -const safeGetUserGroups = () => { - try { - return authUtils.getUserGroups(); - } catch (error) { - console.error('[Dashboard] Error getting user groups:', error); - return []; - } -}; - -const safeGetCurrentUser = () => { - try { - return authUtils.getCurrentUser(); - } catch (error) { - console.error('[Dashboard] Error getting current user:', error); - return null; - } -}; - // Debug auth state onMounted(() => { nextTick(() => { console.log('[Dashboard] Auth state on mount:', { - isAdmin: safeIsAdmin(), - userGroups: safeGetUserGroups(), - currentUser: safeGetCurrentUser() + isAdmin: isAdmin(), + userGroups: getUserGroups(), + currentUser: getCurrentUser() }); }); }); const interestMenu = computed(() => { - try { - const userIsAdmin = safeIsAdmin(); - const userGroups = safeGetUserGroups(); - - console.log('[Dashboard] Computing interest menu - isAdmin:', userIsAdmin, 'groups:', userGroups); - - const baseMenu = [ - //{ - // to: "/dashboard/interest-eoi-queue", - // icon: "mdi-tray-full", - // title: "EOI Queue", - //}, - { - to: "/dashboard/interest-analytics", - icon: "mdi-view-dashboard", - title: "Analytics", - }, - { - to: "/dashboard/interest-berth-list", - icon: "mdi-table", - title: "Berth List", - }, - { - to: "/dashboard/interest-berth-status", - icon: "mdi-sail-boat", - title: "Berth Status", - }, - { - to: "/dashboard/interest-list", - icon: "mdi-view-list", - title: "Interest List", - }, - { - to: "/dashboard/interest-status", - icon: "mdi-account-check", - title: "Interest Status", - }, - { - to: "/dashboard/expenses", - icon: "mdi-receipt", - title: "Expenses", - }, - { - to: "/dashboard/file-browser", - icon: "mdi-folder", - title: "File Browser", - }, - ]; + const userIsAdmin = isAdmin(); + const userGroups = getUserGroups(); + + console.log('[Dashboard] Computing interest menu - isAdmin:', userIsAdmin, 'groups:', userGroups); + + const baseMenu = [ + //{ + // to: "/dashboard/interest-eoi-queue", + // icon: "mdi-tray-full", + // title: "EOI Queue", + //}, + { + to: "/dashboard/interest-analytics", + icon: "mdi-view-dashboard", + title: "Analytics", + }, + { + to: "/dashboard/interest-berth-list", + icon: "mdi-table", + title: "Berth List", + }, + { + to: "/dashboard/interest-berth-status", + icon: "mdi-sail-boat", + title: "Berth Status", + }, + { + to: "/dashboard/interest-list", + icon: "mdi-view-list", + title: "Interest List", + }, + { + to: "/dashboard/interest-status", + icon: "mdi-account-check", + title: "Interest Status", + }, + { + to: "/dashboard/expenses", + icon: "mdi-receipt", + title: "Expenses", + }, + { + to: "/dashboard/file-browser", + icon: "mdi-folder", + title: "File Browser", + }, + ]; - // Add admin menu items if user is admin - if (userIsAdmin) { - console.log('[Dashboard] Adding admin console to interest menu'); - baseMenu.push({ - to: "/dashboard/admin", - icon: "mdi-shield-crown", - title: "Admin Console", - }); - } - - return baseMenu; - } catch (error) { - console.error('[Dashboard] Error computing interest menu:', error); - // Return basic menu without admin items on error - return [ - { - to: "/dashboard/interest-analytics", - icon: "mdi-view-dashboard", - title: "Analytics", - }, - { - to: "/dashboard/interest-list", - icon: "mdi-view-list", - title: "Interest List", - }, - ]; + // Add admin menu items if user is admin + if (userIsAdmin) { + console.log('[Dashboard] Adding admin console to interest menu'); + baseMenu.push({ + to: "/dashboard/admin", + icon: "mdi-shield-crown", + title: "Admin Console", + }); } + + return baseMenu; }); const defaultMenu = computed(() => { - try { - const userIsAdmin = safeIsAdmin(); - const userGroups = safeGetUserGroups(); - - console.log('[Dashboard] Computing default menu - isAdmin:', userIsAdmin, 'groups:', userGroups); - - const baseMenu = [ - { - to: "/dashboard/site", - icon: "mdi-view-dashboard", - title: "Site Analytics", - }, - { - to: "/dashboard/data", - icon: "mdi-finance", - title: "Data Analytics", - }, - { - to: "/dashboard/file-browser", - icon: "mdi-folder", - title: "File Browser", - }, - ]; + const userIsAdmin = isAdmin(); + const userGroups = getUserGroups(); + + console.log('[Dashboard] Computing default menu - isAdmin:', userIsAdmin, 'groups:', userGroups); + + const baseMenu = [ + { + to: "/dashboard/site", + icon: "mdi-view-dashboard", + title: "Site Analytics", + }, + { + to: "/dashboard/data", + icon: "mdi-finance", + title: "Data Analytics", + }, + { + to: "/dashboard/file-browser", + icon: "mdi-folder", + title: "File Browser", + }, + ]; - // Add admin menu items if user is admin - if (userIsAdmin) { - console.log('[Dashboard] Adding admin console to default menu'); - baseMenu.push({ - to: "/dashboard/admin", - icon: "mdi-shield-crown", - title: "Admin Console", - }); - } - - return baseMenu; - } catch (error) { - console.error('[Dashboard] Error computing default menu:', error); - // Return basic menu without admin items on error - return [ - { - to: "/dashboard/site", - icon: "mdi-view-dashboard", - title: "Site Analytics", - }, - { - to: "/dashboard/data", - icon: "mdi-finance", - title: "Data Analytics", - }, - ]; + // Add admin menu items if user is admin + if (userIsAdmin) { + console.log('[Dashboard] Adding admin console to default menu'); + baseMenu.push({ + to: "/dashboard/admin", + icon: "mdi-shield-crown", + title: "Admin Console", + }); } + + return baseMenu; }); const menu = computed(() => diff --git a/pages/dashboard/admin/duplicates.vue b/pages/dashboard/admin/duplicates.vue new file mode 100644 index 0000000..ff2b6f4 --- /dev/null +++ b/pages/dashboard/admin/duplicates.vue @@ -0,0 +1,207 @@ + + +