@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.
- 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} +7 -1
- package/dist/plugin/{ui-config.js → utils/ui-config.js} +2 -3
- 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-config.js +4 -6
- 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 +16 -11
- package/src/app/app-providers.tsx +9 -9
- 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 -2
- 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/_product-variants/product-variants_.$id.tsx +8 -2
- package/src/app/routes/_authenticated/_products/products.tsx +1 -1
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -0
- package/src/app/routes/_authenticated/_system/job-queue.tsx +7 -8
- package/src/app/routes/_authenticated/_system/scheduled-tasks.tsx +241 -0
- package/src/app/routes/_authenticated.tsx +12 -1
- package/src/app/styles.css +15 -0
- 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 +73 -24
- package/src/lib/components/data-table/data-table.tsx +49 -44
- 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/data-table/refresh-button.tsx +25 -0
- package/src/lib/components/layout/nav-user.tsx +20 -15
- package/src/lib/components/layout/prerelease-popup.tsx +1 -5
- package/src/lib/components/shared/alerts.tsx +19 -1
- 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/error-page.tsx +2 -2
- package/src/lib/components/shared/navigation-confirmation.tsx +49 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +10 -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/alert/alert-extensions.tsx +31 -0
- package/src/lib/framework/alert/alert-item.tsx +47 -0
- package/src/lib/framework/alert/alerts-indicator.tsx +23 -0
- package/src/lib/framework/alert/types.ts +13 -0
- package/src/lib/framework/dashboard-widget/base-widget.tsx +1 -0
- package/src/lib/framework/defaults.ts +34 -0
- package/src/lib/framework/document-introspection/get-document-structure.spec.ts +113 -3
- package/src/lib/framework/document-introspection/get-document-structure.ts +71 -13
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +15 -5
- package/src/lib/framework/extension-api/extension-api-types.ts +81 -12
- package/src/lib/framework/form-engine/use-generated-form.tsx +8 -7
- package/src/lib/framework/layout-engine/layout-extensions.ts +3 -3
- package/src/lib/framework/layout-engine/page-layout.tsx +196 -35
- package/src/lib/framework/layout-engine/page-provider.tsx +10 -0
- package/src/lib/framework/page/detail-page.tsx +62 -9
- package/src/lib/framework/page/list-page.tsx +42 -4
- package/src/lib/framework/page/page-api.ts +1 -1
- package/src/lib/framework/page/use-detail-page.ts +82 -0
- package/src/lib/framework/registry/registry-types.ts +6 -2
- package/src/lib/graphql/fragments.tsx +8 -0
- package/src/lib/graphql/graphql-env.d.ts +25 -9
- package/src/lib/hooks/use-auth.tsx +13 -1
- package/src/lib/hooks/use-channel.ts +13 -0
- package/src/lib/hooks/use-local-format.ts +28 -1
- package/src/lib/hooks/use-page.tsx +2 -3
- package/src/lib/hooks/use-permissions.ts +13 -0
- package/src/lib/index.ts +7 -8
- package/src/lib/providers/auth.tsx +22 -9
- package/src/lib/providers/channel-provider.tsx +9 -1
- 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} +11 -6
- package/vite/{ui-config.ts → utils/ui-config.ts} +7 -3
- package/vite/vite-plugin-admin-api-schema.ts +2 -12
- package/vite/vite-plugin-config-loader.ts +25 -13
- package/vite/vite-plugin-config.ts +1 -0
- 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/src/lib/components/ui/avatar.tsx +0 -38
- package/vite/config-loader.ts +0 -181
- /package/dist/plugin/{ui-config.d.ts → utils/ui-config.d.ts} +0 -0
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { AuthContext } from '../providers/auth.js';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @description
|
|
6
|
+
* **Status: Developer Preview**
|
|
7
|
+
*
|
|
8
|
+
* Provides access to the {@link ChannelContext} which contains information
|
|
9
|
+
* about the active channel.
|
|
10
|
+
*
|
|
11
|
+
*
|
|
12
|
+
* @docsCategory hooks
|
|
13
|
+
* @docsPage useAuth
|
|
14
|
+
* @docsWeight 0
|
|
15
|
+
* @since 3.3.0
|
|
16
|
+
*/
|
|
5
17
|
export function useAuth() {
|
|
6
18
|
const context = React.useContext(AuthContext);
|
|
7
19
|
if (!context) {
|
|
@@ -3,6 +3,19 @@ import * as React from 'react';
|
|
|
3
3
|
|
|
4
4
|
// Hook to use the channel context
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @description
|
|
8
|
+
* **Status: Developer Preview**
|
|
9
|
+
*
|
|
10
|
+
* Provides access to the {@link ChannelContext} which contains information
|
|
11
|
+
* about the active channel.
|
|
12
|
+
*
|
|
13
|
+
*
|
|
14
|
+
* @docsCategory hooks
|
|
15
|
+
* @docsPage useChannel
|
|
16
|
+
* @docsWeight 0
|
|
17
|
+
* @since 3.3.0
|
|
18
|
+
*/
|
|
6
19
|
export function useChannel() {
|
|
7
20
|
const context = React.useContext(ChannelContext);
|
|
8
21
|
if (context === undefined) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useLingui } from '@lingui/react';
|
|
2
1
|
import { useCallback, useMemo } from 'react';
|
|
3
2
|
|
|
4
3
|
import { useServerConfig } from './use-server-config.js';
|
|
@@ -19,6 +18,10 @@ import { useServerConfig } from './use-server-config.js';
|
|
|
19
18
|
* toMajorUnits,
|
|
20
19
|
* } = useLocalFormat();
|
|
21
20
|
* ```
|
|
21
|
+
*
|
|
22
|
+
* @docsCategory hooks
|
|
23
|
+
* @docsPage useLocalFormat
|
|
24
|
+
* @docsWeight 0
|
|
22
25
|
*/
|
|
23
26
|
export function useLocalFormat() {
|
|
24
27
|
const { moneyStrategyPrecision } = useServerConfig() ?? { moneyStrategyPrecision: 2 };
|
|
@@ -65,6 +68,29 @@ export function useLocalFormat() {
|
|
|
65
68
|
[locale],
|
|
66
69
|
);
|
|
67
70
|
|
|
71
|
+
const formatRelativeDate = useCallback(
|
|
72
|
+
(value: string | Date, options?: Intl.RelativeTimeFormatOptions) => {
|
|
73
|
+
const now = new Date();
|
|
74
|
+
const date = new Date(value);
|
|
75
|
+
const diffSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
|
76
|
+
// if less than 1 minute, use seconds. Else use minutes, hours, days, months, years
|
|
77
|
+
if (diffSeconds < 60) {
|
|
78
|
+
return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'seconds');
|
|
79
|
+
} else if (diffSeconds < 3600) {
|
|
80
|
+
return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'minutes');
|
|
81
|
+
} else if (diffSeconds < 86400) {
|
|
82
|
+
return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'hours');
|
|
83
|
+
} else if (diffSeconds < 2592000) {
|
|
84
|
+
return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'days');
|
|
85
|
+
} else if (diffSeconds < 31536000) {
|
|
86
|
+
return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'months');
|
|
87
|
+
} else {
|
|
88
|
+
return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'years');
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
[locale],
|
|
92
|
+
);
|
|
93
|
+
|
|
68
94
|
const formatLanguageName = useCallback(
|
|
69
95
|
(value: string): string => {
|
|
70
96
|
try {
|
|
@@ -111,6 +137,7 @@ export function useLocalFormat() {
|
|
|
111
137
|
formatCurrency,
|
|
112
138
|
formatNumber,
|
|
113
139
|
formatDate,
|
|
140
|
+
formatRelativeDate,
|
|
114
141
|
formatLanguageName,
|
|
115
142
|
formatCurrencyName,
|
|
116
143
|
toMajorUnits,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { PageProvider } from "@/framework/layout-engine/page-layout.js";
|
|
2
1
|
import { useContext } from "react";
|
|
2
|
+
import { PageContext } from '@/framework/layout-engine/page-provider.js';
|
|
3
3
|
|
|
4
4
|
export function usePage() {
|
|
5
|
-
const page = useContext(
|
|
5
|
+
const page = useContext(PageContext);
|
|
6
6
|
if (!page) {
|
|
7
7
|
throw new Error('PageProvider not found');
|
|
8
8
|
}
|
|
9
9
|
return page;
|
|
10
10
|
}
|
|
11
|
-
|
|
@@ -3,6 +3,19 @@ import { Permission } from '@vendure/common/lib/generated-types';
|
|
|
3
3
|
|
|
4
4
|
import { useUserSettings } from './use-user-settings.js';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @description
|
|
8
|
+
* **Status: Developer Preview**
|
|
9
|
+
*
|
|
10
|
+
* Returns a `hasPermissions` function that can be used to determine whether the active user
|
|
11
|
+
* has the given permissions on the active channel.
|
|
12
|
+
*
|
|
13
|
+
*
|
|
14
|
+
* @docsCategory hooks
|
|
15
|
+
* @docsPage usePermissions
|
|
16
|
+
* @docsWeight 0
|
|
17
|
+
* @since 3.3.0
|
|
18
|
+
*/
|
|
6
19
|
export function usePermissions() {
|
|
7
20
|
const { channels } = useAuth();
|
|
8
21
|
const { settings } = useUserSettings();
|
package/src/lib/index.ts
CHANGED
|
@@ -28,10 +28,11 @@ export * from './components/layout/nav-user.js';
|
|
|
28
28
|
export * from './components/login/login-form.js';
|
|
29
29
|
export * from './components/shared/alerts.js';
|
|
30
30
|
export * from './components/shared/animated-number.js';
|
|
31
|
-
export * from './components/shared/asset-gallery.js';
|
|
32
|
-
export * from './components/shared/asset-picker-dialog.js';
|
|
33
|
-
export * from './components/shared/asset-preview-dialog.js';
|
|
34
|
-
export * from './components/shared/asset-preview.js';
|
|
31
|
+
export * from './components/shared/asset/asset-gallery.js';
|
|
32
|
+
export * from './components/shared/asset/asset-picker-dialog.js';
|
|
33
|
+
export * from './components/shared/asset/asset-preview-dialog.js';
|
|
34
|
+
export * from './components/shared/asset/asset-preview.js';
|
|
35
|
+
export * from './components/shared/asset/focal-point-control.js';
|
|
35
36
|
export * from './components/shared/assigned-facet-values.js';
|
|
36
37
|
export * from './components/shared/channel-code-label.js';
|
|
37
38
|
export * from './components/shared/channel-selector.js';
|
|
@@ -50,7 +51,6 @@ export * from './components/shared/entity-assets.js';
|
|
|
50
51
|
export * from './components/shared/error-page.js';
|
|
51
52
|
export * from './components/shared/facet-value-chip.js';
|
|
52
53
|
export * from './components/shared/facet-value-selector.js';
|
|
53
|
-
export * from './components/shared/focal-point-control.js';
|
|
54
54
|
export * from './components/shared/form-field-wrapper.js';
|
|
55
55
|
export * from './components/shared/history-timeline/history-entry.js';
|
|
56
56
|
export * from './components/shared/history-timeline/history-note-checkbox.js';
|
|
@@ -74,7 +74,6 @@ export * from './components/shared/zone-selector.js';
|
|
|
74
74
|
export * from './components/ui/accordion.js';
|
|
75
75
|
export * from './components/ui/alert-dialog.js';
|
|
76
76
|
export * from './components/ui/alert.js';
|
|
77
|
-
export * from './components/ui/avatar.js';
|
|
78
77
|
export * from './components/ui/badge.js';
|
|
79
78
|
export * from './components/ui/breadcrumb.js';
|
|
80
79
|
export * from './components/ui/button.js';
|
|
@@ -113,8 +112,8 @@ export * from './framework/dashboard-widget/metrics-widget/index.js';
|
|
|
113
112
|
export * from './framework/dashboard-widget/metrics-widget/metrics-widget.graphql.js';
|
|
114
113
|
export * from './framework/dashboard-widget/orders-summary/index.js';
|
|
115
114
|
export * from './framework/dashboard-widget/orders-summary/order-summary-widget.graphql.js';
|
|
116
|
-
export * from './framework/dashboard-widget/widget-extensions.js';
|
|
117
115
|
export * from './framework/dashboard-widget/types.js';
|
|
116
|
+
export * from './framework/dashboard-widget/widget-extensions.js';
|
|
118
117
|
export * from './framework/defaults.js';
|
|
119
118
|
export * from './framework/document-introspection/add-custom-fields.js';
|
|
120
119
|
export * from './framework/document-introspection/get-document-structure.js';
|
|
@@ -124,9 +123,9 @@ export * from './framework/extension-api/extension-api-types.js';
|
|
|
124
123
|
export * from './framework/extension-api/use-dashboard-extensions.js';
|
|
125
124
|
export * from './framework/form-engine/form-schema-tools.js';
|
|
126
125
|
export * from './framework/form-engine/use-generated-form.js';
|
|
126
|
+
export * from './framework/layout-engine/layout-extensions.js';
|
|
127
127
|
export * from './framework/layout-engine/location-wrapper.js';
|
|
128
128
|
export * from './framework/layout-engine/page-layout.js';
|
|
129
|
-
export * from './framework/layout-engine/layout-extensions.js';
|
|
130
129
|
export * from './framework/nav-menu/nav-menu-extensions.js';
|
|
131
130
|
export * from './framework/page/detail-page-route-loader.js';
|
|
132
131
|
export * from './framework/page/detail-page.js';
|
|
@@ -3,7 +3,17 @@ import { ResultOf, graphql } from '@/graphql/graphql.js';
|
|
|
3
3
|
import { useUserSettings } from '@/hooks/use-user-settings.js';
|
|
4
4
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
|
5
5
|
import * as React from 'react';
|
|
6
|
+
import { useAuth } from '@/hooks/use-auth.js';
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @description
|
|
10
|
+
* **Status: Developer Preview**
|
|
11
|
+
*
|
|
12
|
+
* @docsCategory hooks
|
|
13
|
+
* @docsPage useAuth
|
|
14
|
+
* @docsWeight 0
|
|
15
|
+
* @since 3.3.0
|
|
16
|
+
*/
|
|
7
17
|
export interface AuthContext {
|
|
8
18
|
status: 'authenticated' | 'verifying' | 'unauthenticated';
|
|
9
19
|
authenticationError?: string;
|
|
@@ -69,17 +79,20 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
69
79
|
const onLogoutSuccessFn = React.useRef<() => void>(() => {});
|
|
70
80
|
const isAuthenticated = status === 'authenticated';
|
|
71
81
|
|
|
72
|
-
const { data: currentUserData, isLoading } = useQuery({
|
|
73
|
-
queryKey: ['currentUser'],
|
|
82
|
+
const { data: currentUserData, isLoading , error: currentUserError} = useQuery({
|
|
83
|
+
queryKey: ['currentUser', isAuthenticated],
|
|
74
84
|
queryFn: () => api.query(CurrentUserQuery),
|
|
75
85
|
retry: false,
|
|
86
|
+
staleTime: 1000,
|
|
76
87
|
});
|
|
77
88
|
|
|
89
|
+
const currentUser = currentUserError ? undefined : currentUserData;
|
|
90
|
+
|
|
78
91
|
React.useEffect(() => {
|
|
79
|
-
if (!settings.activeChannelId &&
|
|
80
|
-
setActiveChannelId(
|
|
92
|
+
if (!settings.activeChannelId && currentUser?.me?.channels?.length) {
|
|
93
|
+
setActiveChannelId(currentUser.me.channels[0].id);
|
|
81
94
|
}
|
|
82
|
-
}, [settings.activeChannelId,
|
|
95
|
+
}, [settings.activeChannelId, currentUser?.me?.channels]);
|
|
83
96
|
|
|
84
97
|
const loginMutationFn = api.mutate(LoginMutation);
|
|
85
98
|
const loginMutation = useMutation({
|
|
@@ -123,7 +136,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
123
136
|
|
|
124
137
|
React.useEffect(() => {
|
|
125
138
|
if (!isLoading) {
|
|
126
|
-
if (
|
|
139
|
+
if (currentUser?.me?.id) {
|
|
127
140
|
setStatus('authenticated');
|
|
128
141
|
} else {
|
|
129
142
|
setStatus('unauthenticated');
|
|
@@ -131,7 +144,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
131
144
|
} else {
|
|
132
145
|
setStatus('verifying');
|
|
133
146
|
}
|
|
134
|
-
}, [isLoading,
|
|
147
|
+
}, [isLoading, currentUser]);
|
|
135
148
|
|
|
136
149
|
return (
|
|
137
150
|
<AuthContext.Provider
|
|
@@ -139,8 +152,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
139
152
|
isAuthenticated,
|
|
140
153
|
authenticationError,
|
|
141
154
|
status,
|
|
142
|
-
user:
|
|
143
|
-
channels:
|
|
155
|
+
user: currentUser?.activeAdministrator,
|
|
156
|
+
channels: currentUser?.me?.channels,
|
|
144
157
|
login,
|
|
145
158
|
logout,
|
|
146
159
|
}}
|
|
@@ -41,7 +41,14 @@ const ChannelsQuery = graphql(
|
|
|
41
41
|
type ActiveChannel = ResultOf<typeof ChannelsQuery>['activeChannel'];
|
|
42
42
|
type Channel = ResultOf<typeof channelFragment>;
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* @description
|
|
46
|
+
* **Status: Developer Preview**
|
|
47
|
+
*
|
|
48
|
+
* @docsCategory hooks
|
|
49
|
+
* @docsPage useChannel
|
|
50
|
+
* @since 3.3.0
|
|
51
|
+
*/
|
|
45
52
|
export interface ChannelContext {
|
|
46
53
|
activeChannel: ActiveChannel | undefined;
|
|
47
54
|
channels: Channel[];
|
|
@@ -73,6 +80,7 @@ export function ChannelProvider({ children }: { children: React.ReactNode }) {
|
|
|
73
80
|
const { data: channelsData, isLoading: isChannelsLoading } = useQuery({
|
|
74
81
|
queryKey: ['channels'],
|
|
75
82
|
queryFn: () => api.query(ChannelsQuery),
|
|
83
|
+
retry: false,
|
|
76
84
|
});
|
|
77
85
|
|
|
78
86
|
// Set the selected channel and update localStorage
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { api } from '@/graphql/api.js';
|
|
2
2
|
import { graphql } from '@/graphql/graphql.js';
|
|
3
|
+
import { useAuth } from '@/hooks/use-auth.js';
|
|
3
4
|
import { useQuery } from '@tanstack/react-query';
|
|
4
5
|
import { ResultOf } from 'gql.tada';
|
|
5
6
|
import React from 'react';
|
|
@@ -260,9 +261,14 @@ export interface ServerConfig {
|
|
|
260
261
|
|
|
261
262
|
// create a provider for the global settings
|
|
262
263
|
export const ServerConfigProvider = ({ children }: { children: React.ReactNode }) => {
|
|
264
|
+
const { user } = useAuth();
|
|
265
|
+
const queryKey = ['getServerConfig', user?.id];
|
|
263
266
|
const { data } = useQuery({
|
|
264
|
-
queryKey
|
|
267
|
+
queryKey,
|
|
265
268
|
queryFn: () => api.query(getServerConfigDocument),
|
|
269
|
+
retry: false,
|
|
270
|
+
enabled: !!user?.id,
|
|
271
|
+
staleTime: 1000,
|
|
266
272
|
});
|
|
267
273
|
const value: ServerConfig = {
|
|
268
274
|
availableLanguages: data?.globalSettings.availableLanguages ?? [],
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import React, { createContext, useState, useEffect } from 'react';
|
|
2
2
|
import { Theme } from './theme-provider.js';
|
|
3
|
+
import { ColumnFiltersState } from '@tanstack/react-table';
|
|
4
|
+
|
|
5
|
+
export interface TableSettings {
|
|
6
|
+
columnVisibility?: Record<string, boolean>;
|
|
7
|
+
columnOrder?: string[];
|
|
8
|
+
columnFilters?: ColumnFiltersState;
|
|
9
|
+
pageSize?: number;
|
|
10
|
+
}
|
|
3
11
|
|
|
4
12
|
export interface UserSettings {
|
|
5
13
|
displayLanguage: string;
|
|
@@ -11,6 +19,7 @@ export interface UserSettings {
|
|
|
11
19
|
activeChannelId: string;
|
|
12
20
|
devMode: boolean;
|
|
13
21
|
hasSeenOnboarding: boolean;
|
|
22
|
+
tableSettings?: Record<string, TableSettings>;
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
const defaultSettings: UserSettings = {
|
|
@@ -23,6 +32,7 @@ const defaultSettings: UserSettings = {
|
|
|
23
32
|
activeChannelId: '',
|
|
24
33
|
devMode: false,
|
|
25
34
|
hasSeenOnboarding: false,
|
|
35
|
+
tableSettings: {},
|
|
26
36
|
};
|
|
27
37
|
|
|
28
38
|
export interface UserSettingsContextType {
|
|
@@ -36,6 +46,11 @@ export interface UserSettingsContextType {
|
|
|
36
46
|
setActiveChannelId: (channelId: string) => void;
|
|
37
47
|
setDevMode: (devMode: boolean) => void;
|
|
38
48
|
setHasSeenOnboarding: (hasSeen: boolean) => void;
|
|
49
|
+
setTableSettings: <K extends keyof TableSettings>(
|
|
50
|
+
tableId: string,
|
|
51
|
+
key: K,
|
|
52
|
+
value: TableSettings[K],
|
|
53
|
+
) => void;
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
export const UserSettingsContext = createContext<UserSettingsContextType | undefined>(undefined);
|
|
@@ -83,6 +98,15 @@ export const UserSettingsProvider: React.FC<React.PropsWithChildren<{}>> = ({ ch
|
|
|
83
98
|
setActiveChannelId: channelId => updateSetting('activeChannelId', channelId),
|
|
84
99
|
setDevMode: devMode => updateSetting('devMode', devMode),
|
|
85
100
|
setHasSeenOnboarding: hasSeen => updateSetting('hasSeenOnboarding', hasSeen),
|
|
101
|
+
setTableSettings: (tableId, key, value) => {
|
|
102
|
+
setSettings(prev => ({
|
|
103
|
+
...prev,
|
|
104
|
+
tableSettings: {
|
|
105
|
+
...prev.tableSettings,
|
|
106
|
+
[tableId]: { ...(prev.tableSettings?.[tableId] || {}), [key]: value },
|
|
107
|
+
},
|
|
108
|
+
}));
|
|
109
|
+
},
|
|
86
110
|
};
|
|
87
111
|
|
|
88
112
|
return <UserSettingsContext.Provider value={contextValue}>{children}</UserSettingsContext.Provider>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { findConfigExport, getPluginInfo } from './ast-utils.js';
|
|
5
|
+
|
|
6
|
+
describe('getPluginInfo', () => {
|
|
7
|
+
it('should return undefined when no plugin class is found', () => {
|
|
8
|
+
const sourceText = `
|
|
9
|
+
class NotAPlugin {
|
|
10
|
+
constructor() {}
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
13
|
+
const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
|
|
14
|
+
const result = getPluginInfo(sourceFile);
|
|
15
|
+
expect(result).toBeUndefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return plugin info when a valid plugin class is found', () => {
|
|
19
|
+
const sourceText = `
|
|
20
|
+
@VendurePlugin({
|
|
21
|
+
imports: [],
|
|
22
|
+
providers: []
|
|
23
|
+
})
|
|
24
|
+
class TestPlugin {
|
|
25
|
+
constructor() {}
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
|
|
29
|
+
const result = getPluginInfo(sourceFile);
|
|
30
|
+
expect(result).toEqual({
|
|
31
|
+
name: 'TestPlugin',
|
|
32
|
+
pluginPath: 'path/to',
|
|
33
|
+
dashboardEntryPath: undefined,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should handle multiple classes but only return the plugin one', () => {
|
|
38
|
+
const sourceText = `
|
|
39
|
+
class NotAPlugin {
|
|
40
|
+
constructor() {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@VendurePlugin({
|
|
44
|
+
imports: [],
|
|
45
|
+
providers: []
|
|
46
|
+
})
|
|
47
|
+
class TestPlugin {
|
|
48
|
+
constructor() {}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class AnotherClass {
|
|
52
|
+
constructor() {}
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
|
|
56
|
+
const result = getPluginInfo(sourceFile);
|
|
57
|
+
expect(result).toEqual({
|
|
58
|
+
name: 'TestPlugin',
|
|
59
|
+
pluginPath: 'path/to',
|
|
60
|
+
dashboardEntryPath: undefined,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should determine the dashboard entry path when it is provided', () => {
|
|
65
|
+
const sourceText = `
|
|
66
|
+
@VendurePlugin({
|
|
67
|
+
imports: [],
|
|
68
|
+
providers: [],
|
|
69
|
+
dashboard: './dashboard/index.tsx',
|
|
70
|
+
})
|
|
71
|
+
class TestPlugin {
|
|
72
|
+
constructor() {}
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
75
|
+
const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
|
|
76
|
+
const result = getPluginInfo(sourceFile);
|
|
77
|
+
expect(result).toEqual({
|
|
78
|
+
name: 'TestPlugin',
|
|
79
|
+
pluginPath: 'path/to',
|
|
80
|
+
dashboardEntryPath: './dashboard/index.tsx',
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('findConfigExport', () => {
|
|
86
|
+
it('should return undefined when no VendureConfig export is found', () => {
|
|
87
|
+
const sourceText = `
|
|
88
|
+
export const notConfig = {
|
|
89
|
+
some: 'value'
|
|
90
|
+
};
|
|
91
|
+
`;
|
|
92
|
+
const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
|
|
93
|
+
const result = findConfigExport(sourceFile);
|
|
94
|
+
expect(result).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should find exported variable with VendureConfig type', () => {
|
|
98
|
+
const sourceText = `
|
|
99
|
+
import { VendureConfig } from '@vendure/core';
|
|
100
|
+
|
|
101
|
+
export const config: VendureConfig = {
|
|
102
|
+
authOptions: {
|
|
103
|
+
tokenMethod: 'bearer'
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
`;
|
|
107
|
+
const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
|
|
108
|
+
const result = findConfigExport(sourceFile);
|
|
109
|
+
expect(result).toBe('config');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should find exported variable with VendureConfig type among other exports', () => {
|
|
113
|
+
const sourceText = `
|
|
114
|
+
import { VendureConfig } from '@vendure/core';
|
|
115
|
+
|
|
116
|
+
export const otherExport = 'value';
|
|
117
|
+
export const config: VendureConfig = {
|
|
118
|
+
authOptions: {
|
|
119
|
+
tokenMethod: 'bearer'
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
export const anotherExport = 123;
|
|
123
|
+
`;
|
|
124
|
+
const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
|
|
125
|
+
const result = findConfigExport(sourceFile);
|
|
126
|
+
expect(result).toBe('config');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
|
|
4
|
+
import { PluginInfo } from './config-loader.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get the plugin info from the source file.
|
|
8
|
+
*/
|
|
9
|
+
export function getPluginInfo(sourceFile: ts.SourceFile): PluginInfo | undefined {
|
|
10
|
+
const classDeclaration = sourceFile.statements.find(statement => {
|
|
11
|
+
return (
|
|
12
|
+
statement.kind === ts.SyntaxKind.ClassDeclaration &&
|
|
13
|
+
statement.getText().includes('@VendurePlugin(')
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
if (classDeclaration) {
|
|
17
|
+
const identifier = classDeclaration.getChildren().find(child => {
|
|
18
|
+
return child.kind === ts.SyntaxKind.Identifier;
|
|
19
|
+
});
|
|
20
|
+
const dashboardEntryPath = classDeclaration
|
|
21
|
+
.getChildren()
|
|
22
|
+
.map(child => {
|
|
23
|
+
if (child.kind === ts.SyntaxKind.SyntaxList) {
|
|
24
|
+
const pluginDecorator = child.getChildren().find(_child => {
|
|
25
|
+
return _child.kind === ts.SyntaxKind.Decorator;
|
|
26
|
+
});
|
|
27
|
+
if (pluginDecorator) {
|
|
28
|
+
const callExpression = findFirstDescendantOfKind(
|
|
29
|
+
pluginDecorator,
|
|
30
|
+
ts.SyntaxKind.CallExpression,
|
|
31
|
+
);
|
|
32
|
+
if (callExpression) {
|
|
33
|
+
const objectLiteral = findFirstDescendantOfKind(
|
|
34
|
+
callExpression,
|
|
35
|
+
ts.SyntaxKind.ObjectLiteralExpression,
|
|
36
|
+
);
|
|
37
|
+
if (objectLiteral && ts.isObjectLiteralExpression(objectLiteral)) {
|
|
38
|
+
// Now find the specific 'dashboard' property
|
|
39
|
+
const dashboardProperty = objectLiteral.properties.find(
|
|
40
|
+
prop =>
|
|
41
|
+
ts.isPropertyAssignment(prop) && prop.name?.getText() === 'dashboard',
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
dashboardProperty &&
|
|
46
|
+
ts.isPropertyAssignment(dashboardProperty) &&
|
|
47
|
+
ts.isStringLiteral(dashboardProperty.initializer)
|
|
48
|
+
) {
|
|
49
|
+
const dashboardPath = dashboardProperty.initializer.text;
|
|
50
|
+
return dashboardPath;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
.filter(Boolean)?.[0];
|
|
58
|
+
if (identifier) {
|
|
59
|
+
return {
|
|
60
|
+
name: identifier.getText(),
|
|
61
|
+
pluginPath: path.dirname(sourceFile.fileName),
|
|
62
|
+
dashboardEntryPath,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Given the AST of a TypeScript file, finds the name of the variable exported as VendureConfig.
|
|
70
|
+
*/
|
|
71
|
+
export function findConfigExport(sourceFile: ts.SourceFile): string | undefined {
|
|
72
|
+
let exportedSymbolName: string | undefined;
|
|
73
|
+
|
|
74
|
+
function visit(node: ts.Node) {
|
|
75
|
+
if (
|
|
76
|
+
ts.isVariableStatement(node) &&
|
|
77
|
+
node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)
|
|
78
|
+
) {
|
|
79
|
+
node.declarationList.declarations.forEach(declaration => {
|
|
80
|
+
if (ts.isVariableDeclaration(declaration)) {
|
|
81
|
+
const typeNode = declaration.type;
|
|
82
|
+
if (typeNode && ts.isTypeReferenceNode(typeNode)) {
|
|
83
|
+
const typeName = typeNode.typeName;
|
|
84
|
+
if (ts.isIdentifier(typeName) && typeName.text === 'VendureConfig') {
|
|
85
|
+
if (ts.isIdentifier(declaration.name)) {
|
|
86
|
+
exportedSymbolName = declaration.name.text;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
ts.forEachChild(node, visit);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
visit(sourceFile);
|
|
97
|
+
return exportedSymbolName;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function findFirstDescendantOfKind(node: ts.Node, kind: ts.SyntaxKind): ts.Node | undefined {
|
|
101
|
+
let foundNode: ts.Node | undefined;
|
|
102
|
+
|
|
103
|
+
function visit(_node: ts.Node) {
|
|
104
|
+
if (foundNode) {
|
|
105
|
+
// Stop searching if we already found it
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (_node.kind === kind) {
|
|
109
|
+
foundNode = _node;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Recursively visit children
|
|
113
|
+
ts.forEachChild(_node, visit);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Start the traversal from the initial node's children
|
|
117
|
+
ts.forEachChild(node, visit);
|
|
118
|
+
return foundNode;
|
|
119
|
+
}
|