@vendure/dashboard 3.2.3 → 3.3.0

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 (123) hide show
  1. package/dist/plugin/utils/ast-utils.d.ts +10 -0
  2. package/dist/plugin/utils/ast-utils.js +96 -0
  3. package/dist/plugin/utils/ast-utils.spec.d.ts +1 -0
  4. package/dist/plugin/utils/ast-utils.spec.js +120 -0
  5. package/dist/plugin/{config-loader.d.ts → utils/config-loader.d.ts} +22 -8
  6. package/dist/plugin/utils/config-loader.js +325 -0
  7. package/dist/plugin/{schema-generator.d.ts → utils/schema-generator.d.ts} +5 -0
  8. package/dist/plugin/{schema-generator.js → utils/schema-generator.js} +7 -1
  9. package/dist/plugin/{ui-config.js → utils/ui-config.js} +2 -3
  10. package/dist/plugin/vite-plugin-admin-api-schema.js +2 -2
  11. package/dist/plugin/vite-plugin-config-loader.d.ts +2 -3
  12. package/dist/plugin/vite-plugin-config-loader.js +18 -9
  13. package/dist/plugin/vite-plugin-config.js +4 -6
  14. package/dist/plugin/vite-plugin-dashboard-metadata.js +12 -14
  15. package/dist/plugin/vite-plugin-gql-tada.js +2 -2
  16. package/dist/plugin/vite-plugin-ui-config.js +3 -2
  17. package/package.json +16 -11
  18. package/src/app/app-providers.tsx +9 -9
  19. package/src/app/main.tsx +1 -1
  20. package/src/app/routes/_authenticated/_assets/assets.graphql.ts +26 -0
  21. package/src/app/routes/_authenticated/_assets/assets.tsx +2 -2
  22. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +156 -0
  23. package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +104 -0
  24. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +228 -0
  25. package/src/app/routes/_authenticated/_orders/components/money-gross-net.tsx +18 -0
  26. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +2 -1
  27. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +38 -0
  28. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +53 -0
  29. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +8 -49
  30. package/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx +65 -0
  31. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +187 -2
  32. package/src/app/routes/_authenticated/_orders/orders.tsx +39 -18
  33. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +31 -9
  34. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +418 -0
  35. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +8 -2
  36. package/src/app/routes/_authenticated/_products/products.tsx +1 -1
  37. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -0
  38. package/src/app/routes/_authenticated/_system/job-queue.tsx +7 -8
  39. package/src/app/routes/_authenticated/_system/scheduled-tasks.tsx +241 -0
  40. package/src/app/routes/_authenticated.tsx +12 -1
  41. package/src/app/styles.css +15 -0
  42. package/src/lib/components/data-table/add-filter-menu.tsx +61 -0
  43. package/src/lib/components/data-table/data-table-column-header.tsx +0 -13
  44. package/src/lib/components/data-table/data-table-filter-badge.tsx +75 -0
  45. package/src/lib/components/data-table/data-table-filter-dialog.tsx +27 -28
  46. package/src/lib/components/data-table/data-table-types.ts +1 -0
  47. package/src/lib/components/data-table/data-table-view-options.tsx +73 -24
  48. package/src/lib/components/data-table/data-table.tsx +49 -44
  49. package/src/lib/components/data-table/filters/data-table-boolean-filter.tsx +57 -0
  50. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +93 -0
  51. package/src/lib/components/data-table/filters/data-table-id-filter.tsx +58 -0
  52. package/src/lib/components/data-table/filters/data-table-number-filter.tsx +119 -0
  53. package/src/lib/components/data-table/filters/data-table-string-filter.tsx +62 -0
  54. package/src/lib/components/data-table/human-readable-operator.tsx +65 -0
  55. package/src/lib/components/data-table/refresh-button.tsx +25 -0
  56. package/src/lib/components/layout/nav-user.tsx +20 -15
  57. package/src/lib/components/layout/prerelease-popup.tsx +1 -5
  58. package/src/lib/components/shared/alerts.tsx +19 -1
  59. package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +93 -0
  60. package/src/lib/components/shared/{asset-gallery.tsx → asset/asset-gallery.tsx} +51 -20
  61. package/src/lib/components/shared/{asset-picker-dialog.tsx → asset/asset-picker-dialog.tsx} +1 -1
  62. package/src/lib/components/shared/{asset-preview-dialog.tsx → asset/asset-preview-dialog.tsx} +1 -7
  63. package/src/lib/components/shared/asset/asset-preview-selector.tsx +34 -0
  64. package/src/lib/components/shared/asset/asset-preview.tsx +128 -0
  65. package/src/lib/components/shared/asset/asset-properties.tsx +46 -0
  66. package/src/lib/components/shared/{focal-point-control.tsx → asset/focal-point-control.tsx} +1 -1
  67. package/src/lib/components/shared/custom-fields-form.tsx +4 -3
  68. package/src/lib/components/shared/customer-selector.tsx +13 -14
  69. package/src/lib/components/shared/detail-page-button.tsx +2 -2
  70. package/src/lib/components/shared/entity-assets.tsx +3 -3
  71. package/src/lib/components/shared/error-page.tsx +2 -2
  72. package/src/lib/components/shared/navigation-confirmation.tsx +49 -0
  73. package/src/lib/components/shared/paginated-list-data-table.tsx +10 -1
  74. package/src/lib/components/shared/product-variant-selector.tsx +111 -0
  75. package/src/lib/components/shared/vendure-image.tsx +1 -1
  76. package/src/lib/components/ui/calendar.tsx +508 -63
  77. package/src/lib/framework/alert/alert-extensions.tsx +31 -0
  78. package/src/lib/framework/alert/alert-item.tsx +47 -0
  79. package/src/lib/framework/alert/alerts-indicator.tsx +23 -0
  80. package/src/lib/framework/alert/types.ts +13 -0
  81. package/src/lib/framework/dashboard-widget/base-widget.tsx +1 -0
  82. package/src/lib/framework/defaults.ts +34 -0
  83. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +113 -3
  84. package/src/lib/framework/document-introspection/get-document-structure.ts +71 -13
  85. package/src/lib/framework/extension-api/define-dashboard-extension.ts +15 -5
  86. package/src/lib/framework/extension-api/extension-api-types.ts +81 -12
  87. package/src/lib/framework/form-engine/use-generated-form.tsx +8 -7
  88. package/src/lib/framework/layout-engine/layout-extensions.ts +3 -3
  89. package/src/lib/framework/layout-engine/page-layout.tsx +196 -35
  90. package/src/lib/framework/layout-engine/page-provider.tsx +10 -0
  91. package/src/lib/framework/page/detail-page.tsx +62 -9
  92. package/src/lib/framework/page/list-page.tsx +42 -4
  93. package/src/lib/framework/page/page-api.ts +1 -1
  94. package/src/lib/framework/page/use-detail-page.ts +82 -0
  95. package/src/lib/framework/registry/registry-types.ts +6 -2
  96. package/src/lib/graphql/fragments.tsx +8 -0
  97. package/src/lib/graphql/graphql-env.d.ts +25 -9
  98. package/src/lib/hooks/use-auth.tsx +13 -1
  99. package/src/lib/hooks/use-channel.ts +13 -0
  100. package/src/lib/hooks/use-local-format.ts +28 -1
  101. package/src/lib/hooks/use-page.tsx +2 -3
  102. package/src/lib/hooks/use-permissions.ts +13 -0
  103. package/src/lib/index.ts +7 -8
  104. package/src/lib/providers/auth.tsx +22 -9
  105. package/src/lib/providers/channel-provider.tsx +9 -1
  106. package/src/lib/providers/server-config.tsx +7 -1
  107. package/src/lib/providers/user-settings.tsx +24 -0
  108. package/vite/utils/ast-utils.spec.ts +128 -0
  109. package/vite/utils/ast-utils.ts +119 -0
  110. package/vite/utils/config-loader.ts +410 -0
  111. package/vite/{schema-generator.ts → utils/schema-generator.ts} +11 -6
  112. package/vite/{ui-config.ts → utils/ui-config.ts} +7 -3
  113. package/vite/vite-plugin-admin-api-schema.ts +2 -12
  114. package/vite/vite-plugin-config-loader.ts +25 -13
  115. package/vite/vite-plugin-config.ts +1 -0
  116. package/vite/vite-plugin-dashboard-metadata.ts +19 -15
  117. package/vite/vite-plugin-gql-tada.ts +2 -2
  118. package/vite/vite-plugin-ui-config.ts +3 -2
  119. package/dist/plugin/config-loader.js +0 -141
  120. package/src/lib/components/shared/asset-preview.tsx +0 -345
  121. package/src/lib/components/ui/avatar.tsx +0 -38
  122. package/vite/config-loader.ts +0 -181
  123. /package/dist/plugin/{ui-config.d.ts → utils/ui-config.d.ts} +0 -0
@@ -5,6 +5,7 @@ import { api } from '@/graphql/api.js';
5
5
  import { graphql } from '@/graphql/graphql.js';
6
6
  import { Trans } from '@/lib/trans.js';
7
7
  import { useQuery } from '@tanstack/react-query';
8
+ import { useDebounce } from '@uidotdev/usehooks';
8
9
  import { Plus, Search } from 'lucide-react';
9
10
  import { useState } from 'react';
10
11
 
@@ -38,19 +39,20 @@ export interface CustomerSelectorProps {
38
39
  export function CustomerSelector(props: CustomerSelectorProps) {
39
40
  const [open, setOpen] = useState(false);
40
41
  const [searchTerm, setSearchTerm] = useState('');
42
+ const debouncedSearchTerm = useDebounce(searchTerm, 300);
41
43
 
42
44
  const { data, isLoading } = useQuery({
43
- queryKey: ['customers', searchTerm],
45
+ queryKey: ['customers', debouncedSearchTerm],
44
46
  queryFn: () =>
45
47
  api.query(customersDocument, {
46
48
  options: {
47
49
  sort: { lastName: 'ASC' },
48
- filter: searchTerm ? {
49
- firstName: { contains: searchTerm },
50
- lastName: { contains: searchTerm },
51
- emailAddress: { contains: searchTerm },
50
+ filter: debouncedSearchTerm ? {
51
+ firstName: { contains: debouncedSearchTerm },
52
+ lastName: { contains: debouncedSearchTerm },
53
+ emailAddress: { contains: debouncedSearchTerm },
52
54
  } : undefined,
53
- filterOperator: searchTerm ? 'OR' : undefined,
55
+ filterOperator: debouncedSearchTerm ? 'OR' : undefined,
54
56
  },
55
57
  }),
56
58
  staleTime: 1000 * 60, // 1 minute
@@ -70,14 +72,11 @@ export function CustomerSelector(props: CustomerSelectorProps) {
70
72
  </PopoverTrigger>
71
73
  <PopoverContent className="p-0 w-[350px]" align="start">
72
74
  <Command shouldFilter={false}>
73
- <div className="flex items-center border-b px-3">
74
- <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
75
- <CommandInput
76
- placeholder="Search customers..."
77
- onValueChange={handleSearch}
78
- className="h-10 flex-1 bg-transparent outline-none placeholder:text-muted-foreground"
79
- />
80
- </div>
75
+ <CommandInput
76
+ placeholder="Search customers..."
77
+ onValueChange={handleSearch}
78
+ className="h-10 flex-1 bg-transparent outline-none placeholder:text-muted-foreground"
79
+ />
81
80
  <CommandList>
82
81
  <CommandEmpty>
83
82
  {isLoading ? (
@@ -1,6 +1,6 @@
1
1
  import { Link } from '@tanstack/react-router';
2
+ import { ChevronRight } from 'lucide-react';
2
3
  import { Button } from '../ui/button.js';
3
- import { SquareArrowOutUpRightIcon } from 'lucide-react';
4
4
 
5
5
  export function DetailPageButton({
6
6
  id,
@@ -15,7 +15,7 @@ export function DetailPageButton({
15
15
  <Button asChild variant="ghost" disabled={disabled}>
16
16
  <Link to={`./${id}`}>
17
17
  {label}
18
- {!disabled && <SquareArrowOutUpRightIcon className="h-3 w-3 text-muted-foreground" />}
18
+ {!disabled && <ChevronRight className="h-3 w-3 text-muted-foreground" />}
19
19
  </Link>
20
20
  </Button>
21
21
  );
@@ -24,9 +24,9 @@ import {
24
24
  } from '@dnd-kit/sortable';
25
25
  import { CSS } from '@dnd-kit/utilities';
26
26
  import { EllipsisIcon, ImageIcon, PaperclipIcon } from 'lucide-react';
27
- import React, { useCallback, useEffect, useState } from 'react';
28
- import { AssetPickerDialog } from './asset-picker-dialog.js';
29
- import { AssetPreviewDialog } from './asset-preview-dialog.js';
27
+ import { useCallback, useEffect, useState } from 'react';
28
+ import { AssetPickerDialog } from './asset/asset-picker-dialog.js';
29
+ import { AssetPreviewDialog } from './asset/asset-preview-dialog.js';
30
30
  import { VendureImage } from './vendure-image.js';
31
31
 
32
32
  type Asset = AssetFragment;
@@ -13,12 +13,12 @@ export interface ErrorPageProps {
13
13
  */
14
14
  export function ErrorPage({ message }: ErrorPageProps) {
15
15
  return (
16
- <Page>
16
+ <Page pageId='error-page'>
17
17
  <PageTitle>
18
18
  <Trans>Error</Trans>
19
19
  </PageTitle>
20
20
  <PageLayout>
21
- <PageBlock column="main">
21
+ <PageBlock column="main" blockId='error-message'>
22
22
  <Alert variant="destructive">
23
23
  <AlertCircle className="h-4 w-4" />
24
24
  <AlertTitle>Error</AlertTitle>
@@ -0,0 +1,49 @@
1
+ import { Trans } from '@/lib/trans.js';
2
+ import { useBlocker } from '@tanstack/react-router';
3
+ import { UseFormReturn } from 'react-hook-form';
4
+
5
+ import { Button } from '../ui/button.js';
6
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog.js';
7
+
8
+ export interface NavigationConfirmationProps {
9
+ form: UseFormReturn<any>;
10
+ }
11
+
12
+ /**
13
+ * Navigation confirmation dialog that blocks navigation when the form is dirty.
14
+ */
15
+ export function NavigationConfirmation(props: Readonly<NavigationConfirmationProps>) {
16
+ const { proceed, reset, status } = useBlocker({
17
+ shouldBlockFn: (args) => {
18
+ // When a new entity is being created, we don't want to block navigation
19
+ // to the newly-created entity page.
20
+ const isNavigatingToNewlyCreatedEntity = args.current.fullPath === args.next.fullPath
21
+ && args.current.params.id === 'new' && args.next.params.id !== 'new'
22
+ if (isNavigatingToNewlyCreatedEntity) {
23
+ return false;
24
+ }
25
+ return props.form.formState.isDirty;
26
+ },
27
+ withResolver: true,
28
+ enableBeforeUnload: true,
29
+ });
30
+ return (
31
+ <Dialog open={status === 'blocked'} onOpenChange={reset}>
32
+ <DialogContent className="sm:max-w-[425px]">
33
+ <DialogHeader>
34
+ <DialogTitle><Trans>Confirm navigation</Trans></DialogTitle>
35
+ <DialogDescription>
36
+ <Trans>Are you sure you want to navigate away from this page? Any unsaved changes will be
37
+ lost.</Trans>
38
+ </DialogDescription>
39
+ </DialogHeader>
40
+ <DialogFooter>
41
+ <Button variant="outline" onClick={reset}><Trans>Cancel</Trans></Button>
42
+ <Button type="button" onClick={proceed}>
43
+ <Trans>Confirm</Trans>
44
+ </Button>
45
+ </DialogFooter>
46
+ </DialogContent>
47
+ </Dialog>
48
+ );
49
+ }
@@ -28,7 +28,7 @@ import {
28
28
  SortingState,
29
29
  Table,
30
30
  } from '@tanstack/react-table';
31
- import { AccessorKeyColumnDef, ColumnDef, Row, TableOptions } from '@tanstack/table-core';
31
+ import { AccessorKeyColumnDef, ColumnDef, Row, TableOptions, VisibilityState } from '@tanstack/table-core';
32
32
  import { EllipsisIcon, TrashIcon } from 'lucide-react';
33
33
  import React, { useMemo } from 'react';
34
34
  import { toast } from 'sonner';
@@ -196,6 +196,7 @@ export interface PaginatedListDataTableProps<
196
196
  onPageChange: (table: Table<any>, page: number, perPage: number) => void;
197
197
  onSortChange: (table: Table<any>, sorting: SortingState) => void;
198
198
  onFilterChange: (table: Table<any>, filters: ColumnFiltersState) => void;
199
+ onColumnVisibilityChange?: (table: Table<any>, columnVisibility: VisibilityState) => void;
199
200
  facetedFilters?: FacetedFilterConfig<T>;
200
201
  rowActions?: RowAction<PaginatedListItemFields<T>>[];
201
202
  disableViewOptions?: boolean;
@@ -227,6 +228,7 @@ export function PaginatedListDataTable<
227
228
  onPageChange,
228
229
  onSortChange,
229
230
  onFilterChange,
231
+ onColumnVisibilityChange,
230
232
  facetedFilters,
231
233
  rowActions,
232
234
  disableViewOptions,
@@ -331,6 +333,10 @@ export function PaginatedListDataTable<
331
333
  meta: { fieldInfo, isCustomField },
332
334
  enableColumnFilter,
333
335
  enableSorting: fieldInfo.isScalar,
336
+ // Filtering is done on the server side, but we set this to 'equalsString' because
337
+ // otherwise the TanStack Table with apply an "auto" function which somehow
338
+ // prevents certain filters from working.
339
+ filterFn: 'equalsString',
334
340
  cell: ({ cell, row }) => {
335
341
  const value = !isCustomField
336
342
  ? cell.getValue()
@@ -416,11 +422,13 @@ export function PaginatedListDataTable<
416
422
  onPageChange={onPageChange}
417
423
  onSortChange={onSortChange}
418
424
  onFilterChange={onFilterChange}
425
+ onColumnVisibilityChange={onColumnVisibilityChange}
419
426
  onSearchTermChange={onSearchTermChange ? term => setSearchTerm(term) : undefined}
420
427
  defaultColumnVisibility={columnVisibility}
421
428
  facetedFilters={facetedFilters}
422
429
  disableViewOptions={disableViewOptions}
423
430
  setTableOptions={setTableOptions}
431
+ onRefresh={refetchPaginatedList}
424
432
  />
425
433
  </PaginatedListContext.Provider>
426
434
  );
@@ -434,6 +442,7 @@ function getRowActions(
434
442
  id: 'actions',
435
443
  accessorKey: 'actions',
436
444
  header: 'Actions',
445
+ enableColumnFilter: false,
437
446
  cell: ({ row }) => {
438
447
  return (
439
448
  <DropdownMenu>
@@ -0,0 +1,111 @@
1
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command.js';
2
+ import {
3
+ Popover,
4
+ PopoverContent,
5
+ PopoverTrigger,
6
+ } from "@/components/ui/popover.js";
7
+ import { api } from '@/graphql/api.js';
8
+ import { graphql } from '@/graphql/graphql.js';
9
+ import { cn } from '@/lib/utils.js';
10
+ import { useQuery } from '@tanstack/react-query';
11
+ import { useDebounce } from '@uidotdev/usehooks';
12
+ import { ChevronsUpDown, Plus } from 'lucide-react';
13
+ import { useState } from 'react';
14
+ import { Button } from '../ui/button.js';
15
+ import { assetFragment } from '@/graphql/fragments.js';
16
+ import { VendureImage } from './vendure-image.js';
17
+
18
+ const productVariantListDocument = graphql(`
19
+ query ProductVariantList($options: ProductVariantListOptions) {
20
+ productVariants(options: $options) {
21
+ items {
22
+ id
23
+ name
24
+ sku
25
+ featuredAsset {
26
+ ...Asset
27
+ }
28
+ }
29
+ totalItems
30
+ }
31
+ }
32
+ `, [assetFragment]);
33
+
34
+ export interface ProductVariantSelectorProps {
35
+ onProductVariantIdChange: (productVariantId: string) => void;
36
+ }
37
+
38
+ export function ProductVariantSelector({ onProductVariantIdChange }: ProductVariantSelectorProps) {
39
+ const [search, setSearch] = useState('');
40
+ const [open, setOpen] = useState(false);
41
+ const debouncedSearch = useDebounce(search, 500);
42
+
43
+ const { data, isLoading } = useQuery({
44
+ queryKey: ['productVariants', debouncedSearch],
45
+ staleTime: 1000 * 60 * 5,
46
+ enabled: debouncedSearch.length > 0,
47
+ queryFn: () =>
48
+ api.query(productVariantListDocument, {
49
+ options: {
50
+ take: 10,
51
+ filter: {
52
+ name: { contains: debouncedSearch },
53
+ sku: { contains: debouncedSearch },
54
+ },
55
+ filterOperator: 'OR',
56
+ },
57
+ }),
58
+ });
59
+
60
+ return (
61
+ <Popover open={open} onOpenChange={setOpen}>
62
+ <PopoverTrigger asChild>
63
+ <Button
64
+ variant="outline"
65
+ role="combobox"
66
+ className="w-full"
67
+ >
68
+ Add item to order
69
+ <Plus className="opacity-50" />
70
+ </Button>
71
+ </PopoverTrigger>
72
+ <PopoverContent className="p-0">
73
+ <Command shouldFilter={false}>
74
+ <CommandInput
75
+ placeholder="Add item to order..."
76
+ className="h-9"
77
+ onValueChange={(value) => setSearch(value)}
78
+ />
79
+ <CommandList>
80
+ <CommandEmpty>No products found.</CommandEmpty>
81
+ <CommandGroup>
82
+ {data?.productVariants.items.map((variant) => (
83
+ <CommandItem
84
+ key={variant.id}
85
+ value={variant.id}
86
+ onSelect={() => {
87
+ onProductVariantIdChange(variant.id);
88
+ setOpen(false);
89
+ }}
90
+ className="flex items-center gap-2 p-2"
91
+ >
92
+ {variant.featuredAsset && (
93
+ <VendureImage
94
+ asset={variant.featuredAsset}
95
+ preset="tiny"
96
+ className="size-8 rounded-md object-cover"
97
+ />
98
+ )}
99
+ <div className="flex flex-col">
100
+ <span className="text-sm font-medium">{variant.name}</span>
101
+ <span className="text-xs text-muted-foreground">{variant.sku}</span>
102
+ </div>
103
+ </CommandItem>
104
+ ))}
105
+ </CommandGroup>
106
+ </CommandList>
107
+ </Command>
108
+ </PopoverContent>
109
+ </Popover>
110
+ );
111
+ }
@@ -70,7 +70,7 @@ export function VendureImage({
70
70
  }
71
71
 
72
72
  // Apply focal point if available and requested
73
- if (useFocalPoint && asset.focalPoint && mode === 'crop') {
73
+ if (useFocalPoint && asset.focalPoint) {
74
74
  url.searchParams.set('fpx', asset.focalPoint.x.toString());
75
75
  url.searchParams.set('fpy', asset.focalPoint.y.toString());
76
76
  }