Quellcode durchsuchen

fix(dashboard): Fix admin auth & channel selection bugs (#3562)

Manish vor 7 Monaten
Ursprung
Commit
3b9c0909cf

+ 10 - 1
packages/dashboard/src/lib/graphql/api.ts

@@ -1,6 +1,6 @@
 import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
 import { AwesomeGraphQLClient } from 'awesome-graphql-client';
-import { DocumentNode, parse, print } from 'graphql';
+import { DocumentNode, print } from 'graphql';
 import { uiConfig } from 'virtual:vendure-ui-config';
 
 const API_URL = uiConfig.apiHost + (uiConfig.apiPort !== 'auto' ? `:${uiConfig.apiPort}` : '') + '/admin-api';
@@ -11,8 +11,17 @@ export type RequestDocument = string | DocumentNode;
 const awesomeClient = new AwesomeGraphQLClient({
     endpoint: API_URL,
     fetch: async (url: string, options: RequestInit = {}) => {
+        // Get the active channel token from localStorage
+        const channelToken = localStorage.getItem('vendure-selected-channel-token');
+        const headers = new Headers(options.headers);
+
+        if (channelToken) {
+            headers.set('vendure-token', channelToken);
+        }
+
         return fetch(url, {
             ...options,
+            headers,
             credentials: 'include',
             mode: 'cors',
         });

+ 4 - 4
packages/dashboard/src/lib/hooks/use-permissions.ts

@@ -1,8 +1,7 @@
 import { useAuth } from '@/hooks/use-auth.js';
+import { useChannel } from '@/hooks/use-channel.js';
 import { Permission } from '@vendure/common/lib/generated-types';
 
-import { useUserSettings } from './use-user-settings.js';
-
 /**
  * @description
  * **Status: Developer Preview**
@@ -18,13 +17,14 @@ import { useUserSettings } from './use-user-settings.js';
  */
 export function usePermissions() {
     const { channels } = useAuth();
-    const { settings } = useUserSettings();
+    const { selectedChannelId } = useChannel();
 
     function hasPermissions(permissions: string[]) {
         if (permissions.length === 0) {
             return true;
         }
-        const activeChannel = (channels ?? []).find(channel => channel.id === settings.activeChannelId);
+        // Use the selected channel instead of settings.activeChannelId
+        const activeChannel = (channels ?? []).find(channel => channel.id === selectedChannelId);
         if (!activeChannel) {
             return false;
         }

+ 9 - 3
packages/dashboard/src/lib/providers/auth.tsx

@@ -103,6 +103,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
                     if (data.login.__typename === 'CurrentUser') {
                         setAuthenticationError(undefined);
                         await refetchCurrentUser();
+                        // Invalidate all queries to ensure fresh data after login
+                        await queryClient.invalidateQueries();
                         setStatus('authenticated');
                         onLoginSuccess?.();
                     } else {
@@ -115,7 +117,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
                     setStatus('unauthenticated');
                 });
         },
-        [refetchCurrentUser],
+        [refetchCurrentUser, queryClient],
     );
 
     const logout = React.useCallback(
@@ -123,13 +125,17 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
             setStatus('verifying');
             api.mutate(LogOutMutation)({}).then(async data => {
                 if (data?.logout.success) {
-                    queryClient.removeQueries({ queryKey: ['currentUser'] });
+                    // Clear all cached queries to prevent stale data
+                    queryClient.clear();
+                    // Clear selected channel from localStorage
+                    localStorage.removeItem('vendure-selected-channel');
+                    localStorage.removeItem('vendure-selected-channel-token');
                     setStatus('unauthenticated');
                     onLogoutSuccess?.();
                 }
             });
         },
-        [refetchCurrentUser],
+        [queryClient],
     );
 
     // Determine isAuthenticated from currentUserData

+ 64 - 24
packages/dashboard/src/lib/providers/channel-provider.tsx

@@ -1,7 +1,7 @@
 import { api } from '@/graphql/api.js';
 import { ResultOf, graphql } from '@/graphql/graphql.js';
-import { useQuery, useQueryClient } from '@tanstack/react-query';
 import { useAuth } from '@/hooks/use-auth.js';
+import { useQuery, useQueryClient } from '@tanstack/react-query';
 import * as React from 'react';
 
 // Define the channel fragment for reuse
@@ -63,10 +63,11 @@ export const ChannelContext = React.createContext<ChannelContext | undefined>(un
 
 // Local storage key for the selected channel
 const SELECTED_CHANNEL_KEY = 'vendure-selected-channel';
+const SELECTED_CHANNEL_TOKEN_KEY = 'vendure-selected-channel-token';
 
 export function ChannelProvider({ children }: { children: React.ReactNode }) {
-    const { isAuthenticated } = useAuth();
     const queryClient = useQueryClient();
+    const { channels: userChannels, isAuthenticated } = useAuth();
     const [selectedChannelId, setSelectedChannelId] = React.useState<string | undefined>(() => {
         // Initialize from localStorage if available
         try {
@@ -79,44 +80,83 @@ export function ChannelProvider({ children }: { children: React.ReactNode }) {
     });
 
     // Fetch all available channels
-    const { data: channelsData, isLoading: isChannelsLoading, refetch: refetchChannels } = useQuery({
-        queryKey: ['channels'],
+    const { data: channelsData, isLoading: isChannelsLoading } = useQuery({
+        queryKey: ['channels', isAuthenticated],
         queryFn: () => api.query(ChannelsQuery),
         retry: false,
+        enabled: isAuthenticated,
     });
 
-    React.useEffect(() => {
-        if (isAuthenticated) {
-            // Refetch channels when authenticated
-            refetchChannels();
+    // Filter channels based on user permissions
+    const channels = React.useMemo(() => {
+        // If user has specific channels assigned (non-superadmin), use those
+        if (userChannels && userChannels.length > 0) {
+            // Map user channels to match the Channel type structure
+            return userChannels.map(ch => ({
+                id: ch.id,
+                code: ch.code,
+                token: ch.token,
+                defaultLanguageCode:
+                    channelsData?.channels.items.find(c => c.id === ch.id)?.defaultLanguageCode || 'en',
+                defaultCurrencyCode:
+                    channelsData?.channels.items.find(c => c.id === ch.id)?.defaultCurrencyCode || 'USD',
+                pricesIncludeTax:
+                    channelsData?.channels.items.find(c => c.id === ch.id)?.pricesIncludeTax || false,
+            }));
         }
-    }, [isAuthenticated, refetchChannels])
+        // Otherwise use all channels (superadmin)
+        return channelsData?.channels.items || [];
+    }, [userChannels, channelsData?.channels.items]);
 
     // Set the selected channel and update localStorage
-    const setSelectedChannel = React.useCallback((channelId: string) => {
-        try {
-            // Store in localStorage
-            localStorage.setItem(SELECTED_CHANNEL_KEY, channelId);
-            setSelectedChannelId(channelId);
-            queryClient.invalidateQueries();
-        } catch (e) {
-            console.error('Failed to set selected channel', e);
-        }
-    }, []);
+    const setSelectedChannel = React.useCallback(
+        (channelId: string) => {
+            try {
+                // Find the channel to get its token
+                const channel = channels.find(c => c.id === channelId);
+                if (channel) {
+                    // Store channel ID and token in localStorage
+                    localStorage.setItem(SELECTED_CHANNEL_KEY, channelId);
+                    localStorage.setItem(SELECTED_CHANNEL_TOKEN_KEY, channel.token);
+                    setSelectedChannelId(channelId);
+                    queryClient.invalidateQueries();
+                }
+            } catch (e) {
+                console.error('Failed to set selected channel', e);
+            }
+        },
+        [queryClient, channels],
+    );
 
     // If no selected channel is set but we have an active channel, use that
+    // Also validate that the selected channel is accessible to the user
     React.useEffect(() => {
-        if (!selectedChannelId && channelsData?.activeChannel?.id) {
-            setSelectedChannelId(channelsData.activeChannel.id);
+        const validChannelIds = channels.map(c => c.id);
+
+        // If selected channel is not valid for this user, reset it
+        if (selectedChannelId && !validChannelIds.includes(selectedChannelId)) {
+            setSelectedChannelId(undefined);
+            try {
+                localStorage.removeItem(SELECTED_CHANNEL_KEY);
+                localStorage.removeItem(SELECTED_CHANNEL_TOKEN_KEY);
+            } catch (e) {
+                console.error('Failed to remove selected channel from localStorage', e);
+            }
+        }
+
+        // If no selected channel is set, use the first available channel
+        if (!selectedChannelId && channels.length > 0) {
+            const defaultChannel = channels[0];
+            setSelectedChannelId(defaultChannel.id);
             try {
-                localStorage.setItem(SELECTED_CHANNEL_KEY, channelsData.activeChannel.id);
+                localStorage.setItem(SELECTED_CHANNEL_KEY, defaultChannel.id);
+                localStorage.setItem(SELECTED_CHANNEL_TOKEN_KEY, defaultChannel.token);
             } catch (e) {
                 console.error('Failed to store selected channel in localStorage', e);
             }
         }
-    }, [selectedChannelId, channelsData]);
+    }, [selectedChannelId, channels]);
 
-    const channels = channelsData?.channels.items || [];
     const activeChannel = channelsData?.activeChannel;
     const isLoading = isChannelsLoading;