@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.
- package/dist/plugin/utils/ast-utils.d.ts +10 -0
- package/dist/plugin/utils/ast-utils.js +96 -0
- package/dist/plugin/utils/ast-utils.spec.d.ts +1 -0
- package/dist/plugin/utils/ast-utils.spec.js +120 -0
- package/dist/plugin/{config-loader.d.ts → utils/config-loader.d.ts} +22 -8
- package/dist/plugin/utils/config-loader.js +325 -0
- package/dist/plugin/{schema-generator.d.ts → utils/schema-generator.d.ts} +5 -0
- package/dist/plugin/{schema-generator.js → utils/schema-generator.js} +6 -0
- package/dist/plugin/{ui-config.js → utils/ui-config.js} +2 -2
- package/dist/plugin/vite-plugin-admin-api-schema.js +2 -2
- package/dist/plugin/vite-plugin-config-loader.d.ts +2 -3
- package/dist/plugin/vite-plugin-config-loader.js +18 -9
- package/dist/plugin/vite-plugin-dashboard-metadata.js +12 -14
- package/dist/plugin/vite-plugin-gql-tada.js +2 -2
- package/dist/plugin/vite-plugin-ui-config.js +3 -2
- package/package.json +8 -6
- package/src/app/app-providers.tsx +8 -8
- package/src/app/main.tsx +1 -1
- package/src/app/routes/_authenticated/_assets/assets.graphql.ts +26 -0
- package/src/app/routes/_authenticated/_assets/assets.tsx +2 -2
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +156 -0
- package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +104 -0
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +228 -0
- package/src/app/routes/_authenticated/_orders/components/money-gross-net.tsx +18 -0
- package/src/app/routes/_authenticated/_orders/components/order-address.tsx +2 -1
- package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +38 -0
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +53 -0
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +8 -49
- package/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx +65 -0
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +187 -1
- package/src/app/routes/_authenticated/_orders/orders.tsx +39 -18
- package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +31 -9
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +418 -0
- package/src/app/routes/_authenticated/_products/products.tsx +1 -1
- package/src/app/routes/_authenticated.tsx +12 -1
- package/src/lib/components/data-table/add-filter-menu.tsx +61 -0
- package/src/lib/components/data-table/data-table-column-header.tsx +0 -13
- package/src/lib/components/data-table/data-table-filter-badge.tsx +75 -0
- package/src/lib/components/data-table/data-table-filter-dialog.tsx +27 -28
- package/src/lib/components/data-table/data-table-types.ts +1 -0
- package/src/lib/components/data-table/data-table-view-options.tsx +72 -23
- package/src/lib/components/data-table/data-table.tsx +23 -24
- package/src/lib/components/data-table/filters/data-table-boolean-filter.tsx +57 -0
- package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +93 -0
- package/src/lib/components/data-table/filters/data-table-id-filter.tsx +58 -0
- package/src/lib/components/data-table/filters/data-table-number-filter.tsx +119 -0
- package/src/lib/components/data-table/filters/data-table-string-filter.tsx +62 -0
- package/src/lib/components/data-table/human-readable-operator.tsx +65 -0
- package/src/lib/components/layout/nav-user.tsx +4 -4
- package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +93 -0
- package/src/lib/components/shared/{asset-gallery.tsx → asset/asset-gallery.tsx} +51 -20
- package/src/lib/components/shared/{asset-picker-dialog.tsx → asset/asset-picker-dialog.tsx} +1 -1
- package/src/lib/components/shared/{asset-preview-dialog.tsx → asset/asset-preview-dialog.tsx} +1 -7
- package/src/lib/components/shared/asset/asset-preview-selector.tsx +34 -0
- package/src/lib/components/shared/asset/asset-preview.tsx +128 -0
- package/src/lib/components/shared/asset/asset-properties.tsx +46 -0
- package/src/lib/components/shared/{focal-point-control.tsx → asset/focal-point-control.tsx} +1 -1
- package/src/lib/components/shared/custom-fields-form.tsx +4 -3
- package/src/lib/components/shared/customer-selector.tsx +13 -14
- package/src/lib/components/shared/detail-page-button.tsx +2 -2
- package/src/lib/components/shared/entity-assets.tsx +3 -3
- package/src/lib/components/shared/navigation-confirmation.tsx +39 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +9 -1
- package/src/lib/components/shared/product-variant-selector.tsx +111 -0
- package/src/lib/components/shared/vendure-image.tsx +1 -1
- package/src/lib/components/ui/calendar.tsx +508 -63
- package/src/lib/framework/document-introspection/get-document-structure.spec.ts +113 -3
- package/src/lib/framework/document-introspection/get-document-structure.ts +70 -11
- package/src/lib/framework/form-engine/use-generated-form.tsx +8 -7
- package/src/lib/framework/layout-engine/page-layout.tsx +4 -0
- package/src/lib/framework/page/list-page.tsx +23 -4
- package/src/lib/framework/page/use-detail-page.ts +1 -0
- package/src/lib/graphql/fragments.tsx +8 -0
- package/src/lib/index.ts +5 -5
- package/src/lib/providers/auth.tsx +12 -9
- package/src/lib/providers/channel-provider.tsx +1 -0
- package/src/lib/providers/server-config.tsx +7 -1
- package/src/lib/providers/user-settings.tsx +24 -0
- package/vite/utils/ast-utils.spec.ts +128 -0
- package/vite/utils/ast-utils.ts +119 -0
- package/vite/utils/config-loader.ts +410 -0
- package/vite/{schema-generator.ts → utils/schema-generator.ts} +7 -1
- package/vite/{ui-config.ts → utils/ui-config.ts} +2 -2
- package/vite/vite-plugin-admin-api-schema.ts +2 -2
- package/vite/vite-plugin-config-loader.ts +25 -13
- package/vite/vite-plugin-dashboard-metadata.ts +19 -15
- package/vite/vite-plugin-gql-tada.ts +2 -2
- package/vite/vite-plugin-ui-config.ts +3 -2
- package/dist/plugin/config-loader.js +0 -141
- package/src/lib/components/shared/asset-preview.tsx +0 -345
- package/vite/config-loader.ts +0 -181
- /package/dist/plugin/{ui-config.d.ts → utils/ui-config.d.ts} +0 -0
|
@@ -1,24 +1,33 @@
|
|
|
1
|
-
import { loadVendureConfig } from './config-loader.js';
|
|
1
|
+
import { loadVendureConfig } from './utils/config-loader.js';
|
|
2
2
|
export const configLoaderName = 'vendure:config-loader';
|
|
3
3
|
/**
|
|
4
4
|
* This Vite plugin loads the VendureConfig from the specified file path, and
|
|
5
5
|
* makes it available to other plugins via the `ConfigLoaderApi`.
|
|
6
6
|
*/
|
|
7
7
|
export function configLoaderPlugin(options) {
|
|
8
|
-
let
|
|
8
|
+
let result;
|
|
9
9
|
const onConfigLoaded = [];
|
|
10
10
|
return {
|
|
11
11
|
name: configLoaderName,
|
|
12
12
|
async buildStart() {
|
|
13
|
-
this.info(`Loading Vendure config...`);
|
|
13
|
+
this.info(`Loading Vendure config. This can take a short while depending on the size of your project...`);
|
|
14
14
|
try {
|
|
15
|
-
const
|
|
15
|
+
const startTime = Date.now();
|
|
16
|
+
result = await loadVendureConfig({
|
|
16
17
|
tempDir: options.tempDir,
|
|
17
18
|
vendureConfigPath: options.vendureConfigPath,
|
|
18
19
|
vendureConfigExport: options.vendureConfigExport,
|
|
20
|
+
logger: {
|
|
21
|
+
info: (message) => this.info(message),
|
|
22
|
+
warn: (message) => this.warn(message),
|
|
23
|
+
debug: (message) => this.debug(message),
|
|
24
|
+
},
|
|
19
25
|
});
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
const endTime = Date.now();
|
|
27
|
+
const duration = endTime - startTime;
|
|
28
|
+
const pluginNames = result.pluginInfo.map(p => p.name).join(', ');
|
|
29
|
+
this.info(`Found ${result.pluginInfo.length} plugins: ${pluginNames}`);
|
|
30
|
+
this.info(`Vendure config loaded (using export "${result.exportedSymbolName}") in ${duration}ms`);
|
|
22
31
|
}
|
|
23
32
|
catch (e) {
|
|
24
33
|
if (e instanceof Error) {
|
|
@@ -29,13 +38,13 @@ export function configLoaderPlugin(options) {
|
|
|
29
38
|
},
|
|
30
39
|
api: {
|
|
31
40
|
getVendureConfig() {
|
|
32
|
-
if (
|
|
33
|
-
return Promise.resolve(
|
|
41
|
+
if (result) {
|
|
42
|
+
return Promise.resolve(result);
|
|
34
43
|
}
|
|
35
44
|
else {
|
|
36
45
|
return new Promise(resolve => {
|
|
37
46
|
onConfigLoaded.push(() => {
|
|
38
|
-
resolve(
|
|
47
|
+
resolve(result);
|
|
39
48
|
});
|
|
40
49
|
});
|
|
41
50
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getPluginDashboardExtensions } from '@vendure/core';
|
|
2
1
|
import path from 'path';
|
|
3
2
|
import { getConfigLoaderApi } from './vite-plugin-config-loader.js';
|
|
4
3
|
const virtualModuleId = 'virtual:dashboard-extensions';
|
|
@@ -10,7 +9,7 @@ const resolvedVirtualModuleId = `\0${virtualModuleId}`;
|
|
|
10
9
|
*/
|
|
11
10
|
export function dashboardMetadataPlugin(options) {
|
|
12
11
|
let configLoaderApi;
|
|
13
|
-
let
|
|
12
|
+
let loadVendureConfigResult;
|
|
14
13
|
return {
|
|
15
14
|
name: 'vendure:dashboard-extensions-metadata',
|
|
16
15
|
configResolved({ plugins }) {
|
|
@@ -24,21 +23,20 @@ export function dashboardMetadataPlugin(options) {
|
|
|
24
23
|
async load(id) {
|
|
25
24
|
var _a;
|
|
26
25
|
if (id === resolvedVirtualModuleId) {
|
|
27
|
-
if (!
|
|
28
|
-
|
|
26
|
+
if (!loadVendureConfigResult) {
|
|
27
|
+
loadVendureConfigResult = await configLoaderApi.getVendureConfig();
|
|
29
28
|
}
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const jsPath = normalizeImportPath(options.rootDir, providedPath);
|
|
34
|
-
return { importPath: `./${jsPath}` };
|
|
35
|
-
});
|
|
36
|
-
this.info(`Found ${extensionData.length} Dashboard extensions`);
|
|
29
|
+
const { pluginInfo } = loadVendureConfigResult;
|
|
30
|
+
const pluginsWithExtensions = (_a = pluginInfo === null || pluginInfo === void 0 ? void 0 : pluginInfo.map(({ dashboardEntryPath, pluginPath }) => dashboardEntryPath && path.join(pluginPath, dashboardEntryPath)).filter(x => x != null)) !== null && _a !== void 0 ? _a : [];
|
|
31
|
+
this.info(`Found ${pluginsWithExtensions.length} Dashboard extensions`);
|
|
37
32
|
return `
|
|
38
33
|
export async function runDashboardExtensions() {
|
|
39
|
-
${
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
${pluginsWithExtensions
|
|
35
|
+
.map(extension => {
|
|
36
|
+
return `await import('${extension}');`;
|
|
37
|
+
})
|
|
38
|
+
.join('\n')}
|
|
39
|
+
}`;
|
|
42
40
|
}
|
|
43
41
|
},
|
|
44
42
|
};
|
|
@@ -2,7 +2,7 @@ import { generateOutput } from '@gql.tada/cli-utils';
|
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
3
|
import { printSchema } from 'graphql';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import { generateSchema } from './schema-generator.js';
|
|
5
|
+
import { generateSchema } from './utils/schema-generator.js';
|
|
6
6
|
import { getConfigLoaderApi } from './vite-plugin-config-loader.js';
|
|
7
7
|
export function gqlTadaPlugin(options) {
|
|
8
8
|
let configLoaderApi;
|
|
@@ -12,7 +12,7 @@ export function gqlTadaPlugin(options) {
|
|
|
12
12
|
configLoaderApi = getConfigLoaderApi(plugins);
|
|
13
13
|
},
|
|
14
14
|
async buildStart() {
|
|
15
|
-
const vendureConfig = await configLoaderApi.getVendureConfig();
|
|
15
|
+
const { vendureConfig } = await configLoaderApi.getVendureConfig();
|
|
16
16
|
const safeSchema = await generateSchema({ vendureConfig });
|
|
17
17
|
const tsConfigContent = {
|
|
18
18
|
compilerOptions: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import { getAdminUiConfig } from './ui-config.js';
|
|
2
|
+
import { getAdminUiConfig } from './utils/ui-config.js';
|
|
3
3
|
import { getConfigLoaderApi } from './vite-plugin-config-loader.js';
|
|
4
4
|
const virtualModuleId = 'virtual:vendure-ui-config';
|
|
5
5
|
const resolvedVirtualModuleId = `\0${virtualModuleId}`;
|
|
@@ -24,7 +24,8 @@ export function uiConfigPlugin({ adminUiConfig }) {
|
|
|
24
24
|
async load(id) {
|
|
25
25
|
if (id === resolvedVirtualModuleId) {
|
|
26
26
|
if (!vendureConfig) {
|
|
27
|
-
|
|
27
|
+
const result = await configLoaderApi.getVendureConfig();
|
|
28
|
+
vendureConfig = result.vendureConfig;
|
|
28
29
|
}
|
|
29
30
|
const config = getAdminUiConfig(vendureConfig, adminUiConfig);
|
|
30
31
|
return `
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.2.
|
|
4
|
+
"version": "3.2.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"dev": "vite",
|
|
17
|
+
"build:standalone": "vite build",
|
|
17
18
|
"build": "tsc --project tsconfig.plugin.json",
|
|
18
19
|
"watch": "tsc --project tsconfig.plugin.json --watch",
|
|
19
20
|
"test": "vitest run",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
],
|
|
46
47
|
"dependencies": {
|
|
47
48
|
"@dnd-kit/core": "^6.3.1",
|
|
49
|
+
"@dnd-kit/modifiers": "^9.0.0",
|
|
48
50
|
"@dnd-kit/sortable": "^10.0.0",
|
|
49
51
|
"@hookform/resolvers": "^4.1.3",
|
|
50
52
|
"@lingui/babel-plugin-lingui-macro": "^5.2.0",
|
|
@@ -84,8 +86,8 @@
|
|
|
84
86
|
"@types/react-dom": "^19.0.4",
|
|
85
87
|
"@types/react-grid-layout": "^1.3.5",
|
|
86
88
|
"@uidotdev/usehooks": "^2.4.1",
|
|
87
|
-
"@vendure/common": "3.2.
|
|
88
|
-
"@vendure/core": "3.2.
|
|
89
|
+
"@vendure/common": "3.2.4",
|
|
90
|
+
"@vendure/core": "3.2.4",
|
|
89
91
|
"@vitejs/plugin-react": "^4.3.4",
|
|
90
92
|
"awesome-graphql-client": "^2.1.0",
|
|
91
93
|
"class-variance-authority": "^0.7.1",
|
|
@@ -99,7 +101,7 @@
|
|
|
99
101
|
"motion": "^12.6.2",
|
|
100
102
|
"next-themes": "^0.4.6",
|
|
101
103
|
"react": "^19.0.0",
|
|
102
|
-
"react-day-picker": "^
|
|
104
|
+
"react-day-picker": "^9.6.7",
|
|
103
105
|
"react-dom": "^19.0.0",
|
|
104
106
|
"react-dropzone": "^14.3.8",
|
|
105
107
|
"react-grid-layout": "^1.5.1",
|
|
@@ -109,8 +111,8 @@
|
|
|
109
111
|
"tailwind-merge": "^3.0.1",
|
|
110
112
|
"tailwindcss": "^4.0.6",
|
|
111
113
|
"tailwindcss-animate": "^1.0.7",
|
|
114
|
+
"tsconfig-paths": "^4.2.0",
|
|
112
115
|
"tw-animate-css": "^1.2.4",
|
|
113
|
-
"unplugin-swc": "^1.5.1",
|
|
114
116
|
"vite": "^6.1.0",
|
|
115
117
|
"zod": "^3.24.2"
|
|
116
118
|
},
|
|
@@ -124,5 +126,5 @@
|
|
|
124
126
|
"globals": "^15.14.0",
|
|
125
127
|
"vite-plugin-dts": "^4.5.3"
|
|
126
128
|
},
|
|
127
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "5434f3eca355aaf6ae3bf14f479f27d2387f3c3a"
|
|
128
130
|
}
|
|
@@ -14,15 +14,15 @@ export function AppProviders({ children }: { children: React.ReactNode }) {
|
|
|
14
14
|
return (
|
|
15
15
|
<I18nProvider>
|
|
16
16
|
<QueryClientProvider client={queryClient}>
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
<
|
|
17
|
+
<UserSettingsProvider>
|
|
18
|
+
<ThemeProvider defaultTheme="system">
|
|
19
|
+
<AuthProvider>
|
|
20
|
+
<ServerConfigProvider>
|
|
21
21
|
<ChannelProvider>{children}</ChannelProvider>
|
|
22
|
-
</
|
|
23
|
-
</
|
|
24
|
-
</
|
|
25
|
-
</
|
|
22
|
+
</ServerConfigProvider>
|
|
23
|
+
</AuthProvider>
|
|
24
|
+
</ThemeProvider>
|
|
25
|
+
</UserSettingsProvider>
|
|
26
26
|
<ReactQueryDevtools initialIsOpen={false} buttonPosition="bottom-left" />
|
|
27
27
|
</QueryClientProvider>
|
|
28
28
|
</I18nProvider>
|
package/src/app/main.tsx
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { assetFragment } from '@/graphql/fragments.js';
|
|
2
|
+
import { graphql } from '@/graphql/graphql.js';
|
|
3
|
+
|
|
4
|
+
export const assetDetailDocument = graphql(
|
|
5
|
+
`
|
|
6
|
+
query AssetDetail($id: ID!) {
|
|
7
|
+
asset(id: $id) {
|
|
8
|
+
...Asset
|
|
9
|
+
tags {
|
|
10
|
+
id
|
|
11
|
+
value
|
|
12
|
+
}
|
|
13
|
+
customFields
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
`,
|
|
17
|
+
[assetFragment],
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export const assetUpdateDocument = graphql(`
|
|
21
|
+
mutation AssetUpdate($input: UpdateAssetInput!) {
|
|
22
|
+
updateAsset(input: $input) {
|
|
23
|
+
id
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AssetGallery } from '@/components/shared/asset-gallery.js';
|
|
1
|
+
import { AssetGallery } from '@/components/shared/asset/asset-gallery.js';
|
|
2
2
|
import { Page, PageTitle, PageActionBar } from '@/framework/layout-engine/page-layout.js';
|
|
3
3
|
import { Trans } from '@/lib/trans.js';
|
|
4
4
|
import { createFileRoute } from '@tanstack/react-router';
|
|
@@ -13,7 +13,7 @@ function RouteComponent() {
|
|
|
13
13
|
<PageTitle>
|
|
14
14
|
<Trans>Assets</Trans>
|
|
15
15
|
</PageTitle>
|
|
16
|
-
<AssetGallery selectable={
|
|
16
|
+
<AssetGallery selectable={true} multiSelect='manual' />
|
|
17
17
|
</Page>
|
|
18
18
|
);
|
|
19
19
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { AssetPreview } from '@/components/shared/asset/asset-preview.js'
|
|
2
|
+
import { detailPageRouteLoader } from '@/framework/page/detail-page-route-loader.js';
|
|
3
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
4
|
+
import { assetDetailDocument, assetUpdateDocument } from './assets.graphql.js';
|
|
5
|
+
import { Trans, useLingui } from '@/lib/trans.js';
|
|
6
|
+
import { ErrorPage } from '@/components/shared/error-page.js';
|
|
7
|
+
import { toast } from 'sonner';
|
|
8
|
+
import { Page, PageTitle, PageActionBar, PageActionBarRight, PageBlock, PageLayout, CustomFieldsPageBlock } from '@/framework/layout-engine/page-layout.js'
|
|
9
|
+
import { useDetailPage } from '@/framework/page/use-detail-page.js';
|
|
10
|
+
import { PermissionGuard } from '@/components/shared/permission-guard.js';
|
|
11
|
+
import { Button } from '@/components/ui/button.js';
|
|
12
|
+
import { VendureImage } from '@/components/shared/vendure-image.js';
|
|
13
|
+
import { useState, useRef } from 'react';
|
|
14
|
+
import { PreviewPreset } from '@/components/shared/asset/asset-preview.js';
|
|
15
|
+
import { AssetPreviewSelector } from '@/components/shared/asset/asset-preview-selector.js';
|
|
16
|
+
import { AssetProperties } from '@/components/shared/asset/asset-properties.js';
|
|
17
|
+
import { AssetFocalPointEditor } from '@/components/shared/asset/asset-focal-point-editor.js';
|
|
18
|
+
import { FocusIcon } from 'lucide-react';
|
|
19
|
+
import { Point } from '@/components/shared/asset/focal-point-control.js';
|
|
20
|
+
import { Label } from '@/components/ui/label.js';
|
|
21
|
+
export const Route = createFileRoute('/_authenticated/_assets/assets_/$id')({
|
|
22
|
+
component: AssetDetailPage,
|
|
23
|
+
loader: detailPageRouteLoader({
|
|
24
|
+
queryDocument: assetDetailDocument,
|
|
25
|
+
breadcrumb(isNew, entity) {
|
|
26
|
+
return [
|
|
27
|
+
{ path: '/assets', label: 'Assets' },
|
|
28
|
+
isNew ? <Trans>New asset</Trans> : entity?.name ?? '',
|
|
29
|
+
];
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function AssetDetailPage() {
|
|
36
|
+
const params = Route.useParams();
|
|
37
|
+
const { i18n } = useLingui();
|
|
38
|
+
|
|
39
|
+
const imageRef = useRef<HTMLImageElement>(null);
|
|
40
|
+
const [size, setSize] = useState<PreviewPreset>('medium');
|
|
41
|
+
const [width, setWidth] = useState(0);
|
|
42
|
+
const [height, setHeight] = useState(0);
|
|
43
|
+
const [focalPoint, setFocalPoint] = useState<Point | undefined>(undefined);
|
|
44
|
+
const [settingFocalPoint, setSettingFocalPoint] = useState(false);
|
|
45
|
+
const { form, submitHandler, entity, isPending } = useDetailPage({
|
|
46
|
+
queryDocument: assetDetailDocument,
|
|
47
|
+
updateDocument: assetUpdateDocument,
|
|
48
|
+
setValuesForUpdate: entity => {
|
|
49
|
+
return {
|
|
50
|
+
id: entity.id,
|
|
51
|
+
focalPoint: entity.focalPoint,
|
|
52
|
+
name: entity.name,
|
|
53
|
+
tags: entity.tags?.map(tag => tag.value) ?? [],
|
|
54
|
+
customFields: entity.customFields,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
params: { id: params.id },
|
|
58
|
+
onSuccess: async () => {
|
|
59
|
+
toast(i18n.t('Successfully updated asset'));
|
|
60
|
+
form.reset(form.getValues());
|
|
61
|
+
},
|
|
62
|
+
onError: err => {
|
|
63
|
+
toast(i18n.t('Failed to update asset'), {
|
|
64
|
+
description: err instanceof Error ? err.message : 'Unknown error',
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const updateDimensions = () => {
|
|
70
|
+
if (!imageRef.current) return;
|
|
71
|
+
const img = imageRef.current;
|
|
72
|
+
const imgWidth = img.naturalWidth;
|
|
73
|
+
const imgHeight = img.naturalHeight;
|
|
74
|
+
setWidth(imgWidth);
|
|
75
|
+
setHeight(imgHeight);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (!entity) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return (
|
|
82
|
+
<Page pageId="asset-detail" form={form} submitHandler={submitHandler}>
|
|
83
|
+
<PageTitle>
|
|
84
|
+
<Trans>Edit asset</Trans>
|
|
85
|
+
</PageTitle>
|
|
86
|
+
<PageActionBar>
|
|
87
|
+
<PageActionBarRight>
|
|
88
|
+
<PermissionGuard requires={['UpdateChannel']}>
|
|
89
|
+
<Button
|
|
90
|
+
type="submit"
|
|
91
|
+
disabled={!form.formState.isDirty || isPending}
|
|
92
|
+
>
|
|
93
|
+
<Trans>Update</Trans>
|
|
94
|
+
</Button>
|
|
95
|
+
</PermissionGuard>
|
|
96
|
+
</PageActionBarRight>
|
|
97
|
+
</PageActionBar>
|
|
98
|
+
<PageLayout>
|
|
99
|
+
<PageBlock column="main" blockId="asset-preview">
|
|
100
|
+
<div className="relative flex items-center justify-center bg-muted/30 rounded-lg min-h-[300px] overflow-auto">
|
|
101
|
+
<AssetFocalPointEditor
|
|
102
|
+
width={width}
|
|
103
|
+
height={height}
|
|
104
|
+
settingFocalPoint={settingFocalPoint}
|
|
105
|
+
focalPoint={form.getValues().focalPoint ?? { x: 0.5, y: 0.5 }}
|
|
106
|
+
onFocalPointChange={(point) => {
|
|
107
|
+
form.setValue('focalPoint.x', point.x, { shouldDirty: true });
|
|
108
|
+
form.setValue('focalPoint.y', point.y, { shouldDirty: true });
|
|
109
|
+
setSettingFocalPoint(false);
|
|
110
|
+
}}
|
|
111
|
+
onCancel={() => {
|
|
112
|
+
setSettingFocalPoint(false);
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<VendureImage
|
|
116
|
+
ref={imageRef}
|
|
117
|
+
asset={entity}
|
|
118
|
+
preset={size || undefined}
|
|
119
|
+
mode="resize"
|
|
120
|
+
useFocalPoint={true}
|
|
121
|
+
onLoad={updateDimensions}
|
|
122
|
+
className="max-w-full max-h-full object-contain"
|
|
123
|
+
/>
|
|
124
|
+
</AssetFocalPointEditor>
|
|
125
|
+
</div>
|
|
126
|
+
</PageBlock>
|
|
127
|
+
<CustomFieldsPageBlock
|
|
128
|
+
column="main"
|
|
129
|
+
entityType={'Asset'}
|
|
130
|
+
control={form.control}
|
|
131
|
+
/>
|
|
132
|
+
<PageBlock column="side" blockId="asset-properties">
|
|
133
|
+
<AssetProperties asset={entity} />
|
|
134
|
+
</PageBlock>
|
|
135
|
+
<PageBlock column="side" blockId="asset-size">
|
|
136
|
+
<div className="flex flex-col gap-2">
|
|
137
|
+
<AssetPreviewSelector size={size} setSize={setSize} width={width} height={height} />
|
|
138
|
+
<div className="flex items-center gap-2">
|
|
139
|
+
<Button type='button' variant="outline" size="icon" onClick={() => setSettingFocalPoint(true)}>
|
|
140
|
+
<FocusIcon className="h-4 w-4" />
|
|
141
|
+
</Button>
|
|
142
|
+
<div className="text-sm text-muted-foreground">
|
|
143
|
+
<Label><Trans>Focal Point</Trans></Label>
|
|
144
|
+
<div className="text-sm text-muted-foreground">
|
|
145
|
+
{form.getValues().focalPoint?.x && form.getValues().focalPoint?.y
|
|
146
|
+
? `${form.getValues().focalPoint?.x.toFixed(2)}, ${form.getValues().focalPoint?.y.toFixed(2)}`
|
|
147
|
+
: <Trans>Not set</Trans>}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</PageBlock>
|
|
153
|
+
</PageLayout>
|
|
154
|
+
</Page>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Button } from '@/components/ui/button.js';
|
|
2
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover.js';
|
|
3
|
+
import { api } from '@/graphql/api.js';
|
|
4
|
+
import { graphql, ResultOf } from '@/graphql/graphql.js';
|
|
5
|
+
import { useQuery } from '@tanstack/react-query';
|
|
6
|
+
import { Trans } from '@/lib/trans.js';
|
|
7
|
+
import { useLingui } from '@/lib/trans.js';
|
|
8
|
+
import { addressFragment } from '../../_customers/customers.graphql.js';
|
|
9
|
+
import { Card } from '@/components/ui/card.js';
|
|
10
|
+
import { cn } from '@/lib/utils.js';
|
|
11
|
+
import { useState } from 'react';
|
|
12
|
+
import { Plus } from 'lucide-react';
|
|
13
|
+
|
|
14
|
+
const getCustomerAddressesDocument = graphql(
|
|
15
|
+
`
|
|
16
|
+
query GetCustomerAddresses($customerId: ID!) {
|
|
17
|
+
customer(id: $customerId) {
|
|
18
|
+
id
|
|
19
|
+
addresses {
|
|
20
|
+
...Address
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
`,
|
|
25
|
+
[addressFragment],
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
type CustomerAddressesQuery = ResultOf<typeof getCustomerAddressesDocument>;
|
|
29
|
+
|
|
30
|
+
interface CustomerAddressSelectorProps {
|
|
31
|
+
customerId: string | undefined;
|
|
32
|
+
onSelect: (address: ResultOf<typeof addressFragment>) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function CustomerAddressSelector({ customerId, onSelect }: CustomerAddressSelectorProps) {
|
|
36
|
+
const { i18n } = useLingui();
|
|
37
|
+
const [open, setOpen] = useState(false);
|
|
38
|
+
|
|
39
|
+
const { data, isLoading } = useQuery<CustomerAddressesQuery>({
|
|
40
|
+
queryKey: ['customerAddresses', customerId],
|
|
41
|
+
queryFn: () => api.query(getCustomerAddressesDocument, { customerId: customerId ?? '' }),
|
|
42
|
+
enabled: !!customerId,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const addresses: ResultOf<typeof addressFragment>[] = data?.customer?.addresses || [];
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
49
|
+
<PopoverTrigger asChild>
|
|
50
|
+
<div className="flex items-center gap-2">
|
|
51
|
+
<Button variant="outline" size="sm" type="button" className="" disabled={!customerId}>
|
|
52
|
+
<Plus className="h-4 w-4" />
|
|
53
|
+
<Trans>Select address</Trans>
|
|
54
|
+
</Button>
|
|
55
|
+
</div>
|
|
56
|
+
</PopoverTrigger>
|
|
57
|
+
<PopoverContent className="w-[400px] p-0" align="start">
|
|
58
|
+
<div className="p-4">
|
|
59
|
+
<h4 className="mb-4">
|
|
60
|
+
<Trans>Select an address</Trans>
|
|
61
|
+
</h4>
|
|
62
|
+
<div className="space-y-2">
|
|
63
|
+
{isLoading ? (
|
|
64
|
+
<div className="text-sm text-muted-foreground">
|
|
65
|
+
<Trans>Loading addresses...</Trans>
|
|
66
|
+
</div>
|
|
67
|
+
) : addresses.length === 0 ? (
|
|
68
|
+
<div className="text-sm text-muted-foreground">
|
|
69
|
+
<Trans>No addresses found</Trans>
|
|
70
|
+
</div>
|
|
71
|
+
) : (
|
|
72
|
+
addresses.map(address => (
|
|
73
|
+
<Card
|
|
74
|
+
key={address.id}
|
|
75
|
+
className={cn(
|
|
76
|
+
'p-4 cursor-pointer hover:bg-accent transition-colors',
|
|
77
|
+
)}
|
|
78
|
+
onClick={() => {
|
|
79
|
+
onSelect(address);
|
|
80
|
+
setOpen(false);
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<div className="flex flex-col gap-1 text-sm">
|
|
84
|
+
<div className="font-semibold">{address.fullName}</div>
|
|
85
|
+
{address.company && <div>{address.company}</div>}
|
|
86
|
+
<div>{address.streetLine1}</div>
|
|
87
|
+
{address.streetLine2 && <div>{address.streetLine2}</div>}
|
|
88
|
+
<div>
|
|
89
|
+
{address.city}
|
|
90
|
+
{address.province && `, ${address.province}`}
|
|
91
|
+
</div>
|
|
92
|
+
<div>{address.postalCode}</div>
|
|
93
|
+
<div>{address.country.name}</div>
|
|
94
|
+
{address.phoneNumber && <div>{address.phoneNumber}</div>}
|
|
95
|
+
</div>
|
|
96
|
+
</Card>
|
|
97
|
+
))
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</PopoverContent>
|
|
102
|
+
</Popover>
|
|
103
|
+
);
|
|
104
|
+
}
|