@vendure/dashboard 3.3.2 → 3.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/plugin/utils/config-loader.d.ts +12 -1
  2. package/dist/plugin/utils/config-loader.js +25 -7
  3. package/dist/plugin/vite-plugin-vendure-dashboard.d.ts +8 -0
  4. package/dist/plugin/vite-plugin-vendure-dashboard.js +5 -1
  5. package/package.json +4 -4
  6. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +1 -4
  7. package/src/app/routes/_authenticated/_channels/channels.tsx +18 -0
  8. package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +1 -5
  9. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +1 -4
  10. package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +1 -4
  11. package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +1 -4
  12. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +1 -5
  13. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +56 -74
  14. package/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx +369 -0
  15. package/src/app/routes/_authenticated/_products/components/create-product-options-dialog.tsx +435 -0
  16. package/src/app/routes/_authenticated/_products/components/product-option-select.tsx +117 -0
  17. package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +4 -2
  18. package/src/app/routes/_authenticated/_products/products_.$id.tsx +17 -3
  19. package/src/app/routes/_authenticated/_profile/profile.tsx +1 -4
  20. package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +1 -4
  21. package/src/lib/components/data-table/data-table-view-options.tsx +12 -2
  22. package/src/lib/components/data-table/data-table.tsx +9 -0
  23. package/src/lib/components/layout/channel-switcher.tsx +1 -2
  24. package/src/lib/components/shared/assigned-facet-values.tsx +13 -14
  25. package/src/lib/components/shared/entity-assets.tsx +140 -70
  26. package/src/lib/components/shared/paginated-list-data-table.tsx +10 -0
  27. package/src/lib/components/ui/button.tsx +1 -1
  28. package/src/lib/framework/form-engine/use-generated-form.tsx +1 -0
  29. package/src/lib/framework/page/list-page.tsx +2 -2
  30. package/src/lib/framework/page/use-detail-page.ts +7 -0
  31. package/src/lib/graphql/api.ts +10 -1
  32. package/src/lib/hooks/use-permissions.ts +4 -4
  33. package/src/lib/providers/auth.tsx +9 -3
  34. package/src/lib/providers/channel-provider.tsx +64 -24
  35. package/src/lib/providers/server-config.tsx +2 -2
  36. package/vite/utils/config-loader.ts +48 -13
  37. package/vite/vite-plugin-vendure-dashboard.ts +14 -4
@@ -6,15 +6,16 @@ import {
6
6
  } from '@dnd-kit/modifiers';
7
7
  import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
8
8
  import { CSS } from '@dnd-kit/utilities';
9
- import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
10
9
  import { Table } from '@tanstack/react-table';
11
10
  import { GripVertical, Settings2 } from 'lucide-react';
12
11
 
13
12
  import { Button } from '@/components/ui/button.js';
14
13
  import {
14
+ DropdownMenuSeparator, DropdownMenuTrigger,
15
15
  DropdownMenu,
16
16
  DropdownMenuCheckboxItem,
17
- DropdownMenuContent
17
+ DropdownMenuContent,
18
+ DropdownMenuItem
18
19
  } from '@/components/ui/dropdown-menu.js';
19
20
  import { usePage } from '@/hooks/use-page.js';
20
21
  import { useUserSettings } from '@/hooks/use-user-settings.js';
@@ -69,6 +70,13 @@ export function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps
69
70
  }
70
71
  };
71
72
 
73
+ const handleReset = () => {
74
+ if (page?.pageId) {
75
+ setTableSettings(page.pageId, 'columnOrder', undefined);
76
+ setTableSettings(page.pageId, 'columnVisibility', undefined);
77
+ }
78
+ };
79
+
72
80
  return (
73
81
  <div className="flex items-center gap-2">
74
82
  <DropdownMenu>
@@ -95,6 +103,8 @@ export function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps
95
103
  ))}
96
104
  </SortableContext>
97
105
  </DndContext>
106
+ <DropdownMenuSeparator />
107
+ <DropdownMenuItem onClick={handleReset}>Reset</DropdownMenuItem>
98
108
  </DropdownMenuContent>
99
109
  </DropdownMenu>
100
110
  </div>
@@ -86,6 +86,15 @@ export function DataTable<TData>({
86
86
  defaultColumnVisibility ?? {},
87
87
  );
88
88
 
89
+ useEffect(() => {
90
+ // If the defaultColumnVisibility changes externally (e.g. the user reset the table settings),
91
+ // we want to reset the column visibility to the default.
92
+ if (defaultColumnVisibility && JSON.stringify(defaultColumnVisibility) !== JSON.stringify(columnVisibility)) {
93
+ setColumnVisibility(defaultColumnVisibility);
94
+ }
95
+ // We intentionally do not include `columnVisibility` in the dependency array
96
+ }, [defaultColumnVisibility]);
97
+
89
98
  let tableOptions: TableOptions<TData> = {
90
99
  data,
91
100
  columns,
@@ -61,13 +61,12 @@ export function ChannelSwitcher() {
61
61
  onClick={() => setSelectedChannel(channel.id)}
62
62
  className="gap-2 p-2"
63
63
  >
64
- <div className="flex size-6 items-center justify-center rounded-xs border">
64
+ <div className="flex size-8 items-center justify-center rounded border">
65
65
  <span className="truncate font-semibold text-xs">
66
66
  {channel.defaultCurrencyCode}
67
67
  </span>
68
68
  </div>
69
69
  <ChannelCodeLabel code={channel.code} />
70
- <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
71
70
  </DropdownMenuItem>
72
71
  ))}
73
72
  <DropdownMenuSeparator />
@@ -23,19 +23,19 @@ interface AssignedFacetValuesProps {
23
23
  }
24
24
 
25
25
  export function AssignedFacetValues({
26
- value = [],
27
- facetValues,
28
- canUpdate = true,
29
- onBlur,
30
- onChange,
31
- }: AssignedFacetValuesProps) {
26
+ value = [],
27
+ facetValues,
28
+ canUpdate = true,
29
+ onBlur,
30
+ onChange,
31
+ }: AssignedFacetValuesProps) {
32
32
  const [knownFacetValues, setKnownFacetValues] = useState<FacetValue[]>(facetValues);
33
-
33
+
34
34
  function onSelectHandler(facetValue: FacetValue) {
35
35
  setKnownFacetValues(prev => [...prev, facetValue]);
36
36
  onChange?.([...new Set([...(value ?? []), facetValue.id])]);
37
37
  }
38
-
38
+
39
39
  function onRemoveHandler(id: string) {
40
40
  onChange?.(value?.filter(fvId => fvId !== id) ?? []);
41
41
  }
@@ -47,12 +47,11 @@ export function AssignedFacetValues({
47
47
  const facetValue = knownFacetValues.find(fv => fv.id === id);
48
48
  if (!facetValue) return null;
49
49
  return (
50
- <div className="mb-2 mr-1">
51
- <FacetValueChip
52
- key={facetValue.id}
53
- facetValue={facetValue}
54
- removable={canUpdate}
55
- onRemove={onRemoveHandler}
50
+ <div className="mb-2 mr-1" key={facetValue.id}>
51
+ <FacetValueChip
52
+ facetValue={facetValue}
53
+ removable={canUpdate}
54
+ onRemove={onRemoveHandler}
56
55
  />
57
56
  </div>
58
57
  );
@@ -47,16 +47,55 @@ interface EntityAssetsProps {
47
47
  onChange?: (change: EntityAssetValue) => void;
48
48
  }
49
49
 
50
+ // FeaturedAsset component
51
+ interface FeaturedAssetProps {
52
+ featuredAsset?: Asset | null;
53
+ compact?: boolean;
54
+ onSelectAssets: () => void;
55
+ onPreviewAsset: (asset: Asset) => void;
56
+ }
57
+
58
+ function FeaturedAsset({
59
+ featuredAsset,
60
+ compact = false,
61
+ onSelectAssets,
62
+ onPreviewAsset,
63
+ }: FeaturedAssetProps) {
64
+ return (
65
+ <div
66
+ className={`flex items-center justify-center ${compact ? 'h-40' : 'h-64'} border border-dashed rounded-md`}
67
+ >
68
+ {featuredAsset ? (
69
+ <VendureImage
70
+ asset={featuredAsset}
71
+ mode="crop"
72
+ preset="small"
73
+ onClick={() => onPreviewAsset(featuredAsset)}
74
+ className="max-w-full max-h-full object-contain cursor-pointer"
75
+ />
76
+ ) : (
77
+ <div
78
+ className="flex flex-col items-center justify-center text-muted-foreground cursor-pointer"
79
+ onClick={onSelectAssets}
80
+ >
81
+ <ImageIcon className={compact ? 'h-10 w-10' : 'h-16 w-16'} />
82
+ {!compact && <div className="mt-2">No featured asset</div>}
83
+ </div>
84
+ )}
85
+ </div>
86
+ );
87
+ }
88
+
50
89
  // Sortable asset item component
51
90
  function SortableAsset({
52
- asset,
53
- compact,
54
- isFeatured,
55
- updatePermissions,
56
- onPreview,
57
- onSetAsFeatured,
58
- onRemove,
59
- }: {
91
+ asset,
92
+ compact,
93
+ isFeatured,
94
+ updatePermissions,
95
+ onPreview,
96
+ onSetAsFeatured,
97
+ onRemove,
98
+ }: {
60
99
  asset: Asset;
61
100
  compact: boolean;
62
101
  isFeatured: boolean;
@@ -122,13 +161,13 @@ function SortableAsset({
122
161
  }
123
162
 
124
163
  export function EntityAssets({
125
- assets: initialAssets = [],
126
- featuredAsset: initialFeaturedAsset,
127
- compact = false,
128
- updatePermissions = true,
129
- multiSelect = true,
130
- onChange,
131
- }: EntityAssetsProps) {
164
+ assets: initialAssets = [],
165
+ featuredAsset: initialFeaturedAsset,
166
+ compact = false,
167
+ updatePermissions = true,
168
+ multiSelect = true,
169
+ onChange,
170
+ }: EntityAssetsProps) {
132
171
  const [assets, setAssets] = useState<Asset[]>([...initialAssets]);
133
172
  const [featuredAsset, setFeaturedAsset] = useState<Asset | undefined | null>(initialFeaturedAsset);
134
173
  const [isPickerOpen, setIsPickerOpen] = useState(false);
@@ -234,57 +273,6 @@ export function EntityAssets({
234
273
  },
235
274
  [featuredAsset],
236
275
  );
237
-
238
- const renderAssetList = () => (
239
- <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
240
- <div className={`${compact ? 'max-h-32' : ''} overflow-auto p-1`}>
241
- <SortableContext
242
- items={assets.map(asset => asset.id)}
243
- strategy={horizontalListSortingStrategy}
244
- >
245
- <div className="flex flex-wrap gap-2">
246
- {assets.map(asset => (
247
- <SortableAsset
248
- key={asset.id}
249
- asset={asset}
250
- compact={compact}
251
- isFeatured={isFeatured(asset)}
252
- updatePermissions={updatePermissions}
253
- onPreview={setPreviewAsset}
254
- onSetAsFeatured={handleSetAsFeatured}
255
- onRemove={handleRemoveAsset}
256
- />
257
- ))}
258
- </div>
259
- </SortableContext>
260
- </div>
261
- </DndContext>
262
- );
263
-
264
- const FeaturedAsset = () => (
265
- <div
266
- className={`flex items-center justify-center ${compact ? 'h-40' : 'h-64'} border border-dashed rounded-md`}
267
- >
268
- {featuredAsset ? (
269
- <VendureImage
270
- asset={featuredAsset}
271
- mode="crop"
272
- preset="small"
273
- onClick={() => setPreviewAsset(featuredAsset)}
274
- className="max-w-full max-h-full object-contain cursor-pointer"
275
- />
276
- ) : (
277
- <div
278
- className="flex flex-col items-center justify-center text-muted-foreground cursor-pointer"
279
- onClick={handleSelectAssets}
280
- >
281
- <ImageIcon className={compact ? 'h-10 w-10' : 'h-16 w-16'} />
282
- {!compact && <div className="mt-2">No featured asset</div>}
283
- </div>
284
- )}
285
- </div>
286
- );
287
-
288
276
  // AddAssetButton component
289
277
  const AddAssetButton = () =>
290
278
  updatePermissions && (
@@ -303,15 +291,45 @@ export function EntityAssets({
303
291
  <>
304
292
  {compact ? (
305
293
  <div className="flex flex-col gap-3">
306
- <FeaturedAsset />
307
- {renderAssetList()}
294
+ <FeaturedAsset
295
+ featuredAsset={featuredAsset}
296
+ compact={compact}
297
+ onSelectAssets={handleSelectAssets}
298
+ onPreviewAsset={setPreviewAsset}
299
+ />
300
+ <AssetList
301
+ assets={assets}
302
+ compact={compact}
303
+ sensors={sensors}
304
+ updatePermissions={updatePermissions}
305
+ isFeatured={isFeatured}
306
+ onPreview={setPreviewAsset}
307
+ onSetAsFeatured={handleSetAsFeatured}
308
+ onRemove={handleRemoveAsset}
309
+ onDragEnd={handleDragEnd}
310
+ />
308
311
  <AddAssetButton />
309
312
  </div>
310
313
  ) : (
311
314
  <div className="grid grid-cols-1 md:grid-cols-[256px_1fr] gap-4">
312
- <FeaturedAsset />
315
+ <FeaturedAsset
316
+ featuredAsset={featuredAsset}
317
+ compact={compact}
318
+ onSelectAssets={handleSelectAssets}
319
+ onPreviewAsset={setPreviewAsset}
320
+ />
313
321
  <div className="flex flex-col gap-4">
314
- {renderAssetList()}
322
+ <AssetList
323
+ assets={assets}
324
+ compact={compact}
325
+ sensors={sensors}
326
+ updatePermissions={updatePermissions}
327
+ isFeatured={isFeatured}
328
+ onPreview={setPreviewAsset}
329
+ onSetAsFeatured={handleSetAsFeatured}
330
+ onRemove={handleRemoveAsset}
331
+ onDragEnd={handleDragEnd}
332
+ />
315
333
  <AddAssetButton />
316
334
  </div>
317
335
  </div>
@@ -338,3 +356,55 @@ export function EntityAssets({
338
356
  </>
339
357
  );
340
358
  }
359
+
360
+ // AssetList component
361
+ interface AssetListProps {
362
+ assets: Asset[];
363
+ compact: boolean;
364
+ sensors: ReturnType<typeof useSensors>;
365
+ updatePermissions: boolean;
366
+ isFeatured: (asset: Asset) => boolean;
367
+ onPreview: (asset: Asset) => void;
368
+ onSetAsFeatured: (asset: Asset) => void;
369
+ onRemove: (asset: Asset) => void;
370
+ onDragEnd: (event: DragEndEvent) => void;
371
+ }
372
+
373
+
374
+ function AssetList({
375
+ assets,
376
+ compact,
377
+ sensors,
378
+ updatePermissions,
379
+ isFeatured,
380
+ onPreview,
381
+ onSetAsFeatured,
382
+ onRemove,
383
+ onDragEnd,
384
+ }: AssetListProps) {
385
+ return (
386
+ <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
387
+ <div className={`${compact ? 'max-h-32' : ''} overflow-auto p-1`}>
388
+ <SortableContext
389
+ items={assets.map(asset => asset.id)}
390
+ strategy={horizontalListSortingStrategy}
391
+ >
392
+ <div className="flex flex-wrap gap-2">
393
+ {assets.map(asset => (
394
+ <SortableAsset
395
+ key={asset.id}
396
+ asset={asset}
397
+ compact={compact}
398
+ isFeatured={isFeatured(asset)}
399
+ updatePermissions={updatePermissions}
400
+ onPreview={onPreview}
401
+ onSetAsFeatured={onSetAsFeatured}
402
+ onRemove={onRemove}
403
+ />
404
+ ))}
405
+ </div>
406
+ </SortableContext>
407
+ </div>
408
+ </DndContext>
409
+ );
410
+ }
@@ -174,6 +174,8 @@ export interface RowAction<T> {
174
174
  onClick?: (row: Row<T>) => void;
175
175
  }
176
176
 
177
+ export type PaginatedListRefresherRegisterFn = (refreshFn: () => void) => void;
178
+
177
179
  export interface PaginatedListDataTableProps<
178
180
  T extends TypedDocumentNode<U, V>,
179
181
  U extends any,
@@ -202,6 +204,12 @@ export interface PaginatedListDataTableProps<
202
204
  disableViewOptions?: boolean;
203
205
  transformData?: (data: PaginatedListItemFields<T>[]) => PaginatedListItemFields<T>[];
204
206
  setTableOptions?: (table: TableOptions<any>) => TableOptions<any>;
207
+ /**
208
+ * Register a function that allows you to assign a refresh function for
209
+ * this list. The function can be assigned to a ref and then called when
210
+ * the list needs to be refreshed.
211
+ */
212
+ registerRefresher?: PaginatedListRefresherRegisterFn;
205
213
  }
206
214
 
207
215
  export const PaginatedListDataTableKey = 'PaginatedListDataTable';
@@ -234,6 +242,7 @@ export function PaginatedListDataTable<
234
242
  disableViewOptions,
235
243
  setTableOptions,
236
244
  transformData,
245
+ registerRefresher,
237
246
  }: PaginatedListDataTableProps<T, U, V, AC>) {
238
247
  const [searchTerm, setSearchTerm] = React.useState<string>('');
239
248
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
@@ -274,6 +283,7 @@ export function PaginatedListDataTable<
274
283
  function refetchPaginatedList() {
275
284
  queryClient.invalidateQueries({ queryKey });
276
285
  }
286
+ registerRefresher?.(refetchPaginatedList);
277
287
 
278
288
  const { data } = useQuery({
279
289
  queryFn: () => {
@@ -43,7 +43,7 @@ export interface ButtonProps
43
43
  const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
44
  ({ className, variant, size, asChild = false, ...props }, ref) => {
45
45
  const Comp = asChild ? Slot : 'button';
46
- return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
46
+ return <Comp className={cn(buttonVariants({ variant, size, className }))} type={props.type ?? 'button'} ref={ref} {...props} />;
47
47
  },
48
48
  );
49
49
  Button.displayName = 'Button';
@@ -51,6 +51,7 @@ export function useGeneratedForm<
51
51
  }
52
52
  return result;
53
53
  },
54
+ mode: 'onChange',
54
55
  defaultValues,
55
56
  values: processedEntity ? setValues(processedEntity) : defaultValues,
56
57
  });
@@ -110,8 +110,8 @@ export function ListPage<
110
110
  itemsPerPage: routeSearch.perPage ? parseInt(routeSearch.perPage) : tableSettings?.pageSize ?? 10,
111
111
  };
112
112
 
113
- const columnVisibility = pageId ? tableSettings?.columnVisibility : defaultVisibility;
114
- const columnOrder = pageId ? tableSettings?.columnOrder : defaultColumnOrder;
113
+ const columnVisibility = pageId ? tableSettings?.columnVisibility ?? defaultVisibility : defaultVisibility;
114
+ const columnOrder = pageId ? tableSettings?.columnOrder ?? defaultColumnOrder : defaultColumnOrder;
115
115
  const columnFilters = pageId ? tableSettings?.columnFilters : routeSearch.filters;
116
116
 
117
117
  const sorting: SortingState = (routeSearch.sort ?? '')
@@ -288,6 +288,13 @@ export function useDetailPage<
288
288
  },
289
289
  });
290
290
 
291
+ // A kind of ridiculous workaround to ensure that the `isDirty` and `isValid` properties
292
+ // are always up-to-date when used by the consuming component. This seems to be necessary
293
+ // due to the way that `react-hook-form` uses a Proxy object for the form state.
294
+ // See https://react-hook-form.com/docs/useform/formstate
295
+ // noinspection JSUnusedLocalSymbols
296
+ const { isDirty, isValid } = form.formState;
297
+
291
298
  return {
292
299
  form: form as any,
293
300
  submitHandler,
@@ -1,6 +1,6 @@
1
1
  import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
2
2
  import { AwesomeGraphQLClient } from 'awesome-graphql-client';
3
- import { DocumentNode, parse, print } from 'graphql';
3
+ import { DocumentNode, print } from 'graphql';
4
4
  import { uiConfig } from 'virtual:vendure-ui-config';
5
5
 
6
6
  const API_URL = uiConfig.apiHost + (uiConfig.apiPort !== 'auto' ? `:${uiConfig.apiPort}` : '') + '/admin-api';
@@ -11,8 +11,17 @@ export type RequestDocument = string | DocumentNode;
11
11
  const awesomeClient = new AwesomeGraphQLClient({
12
12
  endpoint: API_URL,
13
13
  fetch: async (url: string, options: RequestInit = {}) => {
14
+ // Get the active channel token from localStorage
15
+ const channelToken = localStorage.getItem('vendure-selected-channel-token');
16
+ const headers = new Headers(options.headers);
17
+
18
+ if (channelToken) {
19
+ headers.set('vendure-token', channelToken);
20
+ }
21
+
14
22
  return fetch(url, {
15
23
  ...options,
24
+ headers,
16
25
  credentials: 'include',
17
26
  mode: 'cors',
18
27
  });
@@ -1,8 +1,7 @@
1
1
  import { useAuth } from '@/hooks/use-auth.js';
2
+ import { useChannel } from '@/hooks/use-channel.js';
2
3
  import { Permission } from '@vendure/common/lib/generated-types';
3
4
 
4
- import { useUserSettings } from './use-user-settings.js';
5
-
6
5
  /**
7
6
  * @description
8
7
  * **Status: Developer Preview**
@@ -18,13 +17,14 @@ import { useUserSettings } from './use-user-settings.js';
18
17
  */
19
18
  export function usePermissions() {
20
19
  const { channels } = useAuth();
21
- const { settings } = useUserSettings();
20
+ const { selectedChannelId } = useChannel();
22
21
 
23
22
  function hasPermissions(permissions: string[]) {
24
23
  if (permissions.length === 0) {
25
24
  return true;
26
25
  }
27
- const activeChannel = (channels ?? []).find(channel => channel.id === settings.activeChannelId);
26
+ // Use the selected channel instead of settings.activeChannelId
27
+ const activeChannel = (channels ?? []).find(channel => channel.id === selectedChannelId);
28
28
  if (!activeChannel) {
29
29
  return false;
30
30
  }
@@ -103,6 +103,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
103
103
  if (data.login.__typename === 'CurrentUser') {
104
104
  setAuthenticationError(undefined);
105
105
  await refetchCurrentUser();
106
+ // Invalidate all queries to ensure fresh data after login
107
+ await queryClient.invalidateQueries();
106
108
  setStatus('authenticated');
107
109
  onLoginSuccess?.();
108
110
  } else {
@@ -115,7 +117,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
115
117
  setStatus('unauthenticated');
116
118
  });
117
119
  },
118
- [refetchCurrentUser],
120
+ [refetchCurrentUser, queryClient],
119
121
  );
120
122
 
121
123
  const logout = React.useCallback(
@@ -123,13 +125,17 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
123
125
  setStatus('verifying');
124
126
  api.mutate(LogOutMutation)({}).then(async data => {
125
127
  if (data?.logout.success) {
126
- queryClient.removeQueries({ queryKey: ['currentUser'] });
128
+ // Clear all cached queries to prevent stale data
129
+ queryClient.clear();
130
+ // Clear selected channel from localStorage
131
+ localStorage.removeItem('vendure-selected-channel');
132
+ localStorage.removeItem('vendure-selected-channel-token');
127
133
  setStatus('unauthenticated');
128
134
  onLogoutSuccess?.();
129
135
  }
130
136
  });
131
137
  },
132
- [refetchCurrentUser],
138
+ [queryClient],
133
139
  );
134
140
 
135
141
  // Determine isAuthenticated from currentUserData