@vendure/dashboard 3.2.2 → 3.2.4

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 (92) 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} +6 -0
  9. package/dist/plugin/{ui-config.js → utils/ui-config.js} +2 -2
  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-dashboard-metadata.js +12 -14
  14. package/dist/plugin/vite-plugin-gql-tada.js +2 -2
  15. package/dist/plugin/vite-plugin-ui-config.js +3 -2
  16. package/package.json +8 -6
  17. package/src/app/app-providers.tsx +8 -8
  18. package/src/app/main.tsx +1 -1
  19. package/src/app/routes/_authenticated/_assets/assets.graphql.ts +26 -0
  20. package/src/app/routes/_authenticated/_assets/assets.tsx +2 -2
  21. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +156 -0
  22. package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +104 -0
  23. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +228 -0
  24. package/src/app/routes/_authenticated/_orders/components/money-gross-net.tsx +18 -0
  25. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +2 -1
  26. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +38 -0
  27. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +53 -0
  28. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +8 -49
  29. package/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx +65 -0
  30. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +187 -1
  31. package/src/app/routes/_authenticated/_orders/orders.tsx +39 -18
  32. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +31 -9
  33. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +418 -0
  34. package/src/app/routes/_authenticated/_products/products.tsx +1 -1
  35. package/src/app/routes/_authenticated.tsx +12 -1
  36. package/src/lib/components/data-table/add-filter-menu.tsx +61 -0
  37. package/src/lib/components/data-table/data-table-column-header.tsx +0 -13
  38. package/src/lib/components/data-table/data-table-filter-badge.tsx +75 -0
  39. package/src/lib/components/data-table/data-table-filter-dialog.tsx +27 -28
  40. package/src/lib/components/data-table/data-table-types.ts +1 -0
  41. package/src/lib/components/data-table/data-table-view-options.tsx +72 -23
  42. package/src/lib/components/data-table/data-table.tsx +23 -24
  43. package/src/lib/components/data-table/filters/data-table-boolean-filter.tsx +57 -0
  44. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +93 -0
  45. package/src/lib/components/data-table/filters/data-table-id-filter.tsx +58 -0
  46. package/src/lib/components/data-table/filters/data-table-number-filter.tsx +119 -0
  47. package/src/lib/components/data-table/filters/data-table-string-filter.tsx +62 -0
  48. package/src/lib/components/data-table/human-readable-operator.tsx +65 -0
  49. package/src/lib/components/layout/nav-user.tsx +4 -4
  50. package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +93 -0
  51. package/src/lib/components/shared/{asset-gallery.tsx → asset/asset-gallery.tsx} +51 -20
  52. package/src/lib/components/shared/{asset-picker-dialog.tsx → asset/asset-picker-dialog.tsx} +1 -1
  53. package/src/lib/components/shared/{asset-preview-dialog.tsx → asset/asset-preview-dialog.tsx} +1 -7
  54. package/src/lib/components/shared/asset/asset-preview-selector.tsx +34 -0
  55. package/src/lib/components/shared/asset/asset-preview.tsx +128 -0
  56. package/src/lib/components/shared/asset/asset-properties.tsx +46 -0
  57. package/src/lib/components/shared/{focal-point-control.tsx → asset/focal-point-control.tsx} +1 -1
  58. package/src/lib/components/shared/custom-fields-form.tsx +4 -3
  59. package/src/lib/components/shared/customer-selector.tsx +13 -14
  60. package/src/lib/components/shared/detail-page-button.tsx +2 -2
  61. package/src/lib/components/shared/entity-assets.tsx +3 -3
  62. package/src/lib/components/shared/navigation-confirmation.tsx +39 -0
  63. package/src/lib/components/shared/paginated-list-data-table.tsx +9 -1
  64. package/src/lib/components/shared/product-variant-selector.tsx +111 -0
  65. package/src/lib/components/shared/vendure-image.tsx +1 -1
  66. package/src/lib/components/ui/calendar.tsx +508 -63
  67. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +113 -3
  68. package/src/lib/framework/document-introspection/get-document-structure.ts +70 -11
  69. package/src/lib/framework/form-engine/use-generated-form.tsx +8 -7
  70. package/src/lib/framework/layout-engine/page-layout.tsx +4 -0
  71. package/src/lib/framework/page/list-page.tsx +23 -4
  72. package/src/lib/framework/page/use-detail-page.ts +1 -0
  73. package/src/lib/graphql/fragments.tsx +8 -0
  74. package/src/lib/index.ts +5 -5
  75. package/src/lib/providers/auth.tsx +12 -9
  76. package/src/lib/providers/channel-provider.tsx +1 -0
  77. package/src/lib/providers/server-config.tsx +7 -1
  78. package/src/lib/providers/user-settings.tsx +24 -0
  79. package/vite/utils/ast-utils.spec.ts +128 -0
  80. package/vite/utils/ast-utils.ts +119 -0
  81. package/vite/utils/config-loader.ts +410 -0
  82. package/vite/{schema-generator.ts → utils/schema-generator.ts} +7 -1
  83. package/vite/{ui-config.ts → utils/ui-config.ts} +2 -2
  84. package/vite/vite-plugin-admin-api-schema.ts +2 -2
  85. package/vite/vite-plugin-config-loader.ts +25 -13
  86. package/vite/vite-plugin-dashboard-metadata.ts +19 -15
  87. package/vite/vite-plugin-gql-tada.ts +2 -2
  88. package/vite/vite-plugin-ui-config.ts +3 -2
  89. package/dist/plugin/config-loader.js +0 -141
  90. package/src/lib/components/shared/asset-preview.tsx +0 -345
  91. package/vite/config-loader.ts +0 -181
  92. /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;
@@ -0,0 +1,39 @@
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: NavigationConfirmationProps) {
16
+ const { proceed, reset, status } = useBlocker({
17
+ shouldBlockFn: () => props.form.formState.isDirty,
18
+ withResolver: true,
19
+ enableBeforeUnload: true,
20
+ })
21
+ return (
22
+ <Dialog open={status === 'blocked'} onOpenChange={reset}>
23
+ <DialogContent className="sm:max-w-[425px]">
24
+ <DialogHeader>
25
+ <DialogTitle><Trans>Confirm navigation</Trans></DialogTitle>
26
+ <DialogDescription>
27
+ <Trans>Are you sure you want to navigate away from this page? Any unsaved changes will be lost.</Trans>
28
+ </DialogDescription>
29
+ </DialogHeader>
30
+ <DialogFooter>
31
+ <Button variant="outline" onClick={reset}><Trans>Cancel</Trans></Button>
32
+ <Button type="button" onClick={proceed}>
33
+ <Trans>Confirm</Trans>
34
+ </Button>
35
+ </DialogFooter>
36
+ </DialogContent>
37
+ </Dialog>
38
+ )
39
+ }
@@ -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,6 +422,7 @@ 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}
@@ -434,6 +441,7 @@ function getRowActions(
434
441
  id: 'actions',
435
442
  accessorKey: 'actions',
436
443
  header: 'Actions',
444
+ enableColumnFilter: false,
437
445
  cell: ({ row }) => {
438
446
  return (
439
447
  <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
  }