@vendure/dashboard 3.3.8 → 3.4.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/README.md +62 -0
- package/dist/plugin/api/api-extensions.d.ts +1 -0
- package/dist/plugin/api/api-extensions.js +38 -0
- package/dist/plugin/api/metrics.resolver.d.ts +8 -0
- package/dist/plugin/api/metrics.resolver.js +40 -0
- package/dist/plugin/config/metrics-strategies.d.ts +39 -0
- package/dist/plugin/config/metrics-strategies.js +74 -0
- package/dist/plugin/constants.d.ts +4 -3
- package/dist/plugin/constants.js +10 -277
- package/dist/plugin/dashboard.plugin.d.ts +95 -0
- package/dist/plugin/dashboard.plugin.js +168 -0
- package/dist/plugin/index.d.ts +2 -1
- package/dist/plugin/index.js +18 -1
- package/dist/plugin/package.json +3 -0
- package/dist/plugin/service/metrics.service.d.ts +15 -0
- package/dist/plugin/service/metrics.service.js +145 -0
- package/dist/plugin/types.d.ts +20 -37
- package/dist/plugin/types.js +13 -1
- package/dist/vite/constants.d.ts +5 -0
- package/dist/vite/constants.js +277 -0
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.js +1 -0
- package/dist/vite/types.d.ts +40 -0
- package/dist/vite/utils/config-loader.js +1 -0
- package/dist/{plugin → vite}/utils/plugin-discovery.js +1 -1
- package/dist/vite/utils/ui-config.d.ts +3 -0
- package/dist/vite/utils/ui-config.js +30 -0
- package/dist/vite/vite-plugin-ui-config.d.ts +123 -0
- package/dist/{plugin → vite}/vite-plugin-ui-config.js +3 -11
- package/dist/{plugin → vite}/vite-plugin-vendure-dashboard.js +1 -1
- package/index.html +1 -1
- package/package.json +16 -7
- package/src/app/app-providers.tsx +1 -1
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +20 -35
- package/src/app/routes/_authenticated/_facets/facets.graphql.ts +40 -0
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +147 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history.tsx +380 -33
- package/src/app/routes/_authenticated/_products/components/option-value-input.tsx +1 -1
- package/src/app/routes/_authenticated/_system/healthchecks.tsx +1 -1
- package/src/app/routes/_authenticated/_system/job-queue.tsx +1 -0
- package/src/app/routes/_authenticated/index.tsx +2 -2
- package/src/app/routes/_authenticated.tsx +1 -1
- package/src/lib/components/data-input/rich-text-input.tsx +14 -8
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +17 -4
- package/src/lib/components/layout/app-layout.tsx +2 -7
- package/src/lib/components/layout/channel-switcher.tsx +166 -57
- package/src/lib/components/layout/dev-mode-indicator.tsx +18 -0
- package/src/lib/components/layout/language-dialog.tsx +2 -1
- package/src/lib/components/layout/manage-languages-dialog.tsx +77 -40
- package/src/lib/components/layout/nav-item-wrapper.tsx +107 -0
- package/src/lib/components/layout/nav-main.tsx +196 -107
- package/src/lib/components/login/login-form.tsx +80 -45
- package/src/lib/components/shared/asset/asset-bulk-actions.tsx +19 -4
- package/src/lib/components/shared/asset/asset-gallery.tsx +2 -2
- package/src/lib/components/shared/detail-page-button.tsx +42 -0
- package/src/lib/components/shared/history-timeline/history-entry-date.tsx +37 -0
- package/src/lib/components/shared/history-timeline/history-entry.tsx +135 -65
- package/src/lib/components/shared/history-timeline/history-note-input.tsx +4 -4
- package/src/lib/components/shared/history-timeline/history-timeline.tsx +7 -54
- package/src/lib/components/shared/translatable-form-field.tsx +16 -2
- package/src/lib/framework/defaults.ts +4 -10
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +4 -0
- package/src/lib/framework/extension-api/extension-api-types.ts +11 -2
- package/src/lib/framework/extension-api/logic/index.ts +1 -0
- package/src/lib/framework/extension-api/logic/login.ts +17 -0
- package/src/lib/framework/extension-api/logic/navigation.ts +1 -0
- package/src/lib/framework/extension-api/types/data-table.ts +12 -3
- package/src/lib/framework/extension-api/types/detail-forms.ts +13 -0
- package/src/lib/framework/extension-api/types/form-components.ts +11 -0
- package/src/lib/framework/extension-api/types/index.ts +1 -0
- package/src/lib/framework/extension-api/types/layout.ts +3 -6
- package/src/lib/framework/extension-api/types/login.ts +96 -0
- package/src/lib/framework/extension-api/types/navigation.ts +57 -0
- package/src/lib/framework/extension-api/types/widgets.ts +0 -4
- package/src/lib/framework/extension-api/use-login-extensions.ts +26 -0
- package/src/lib/framework/layout-engine/dev-mode-button.tsx +24 -0
- package/src/lib/framework/layout-engine/location-wrapper.tsx +5 -12
- package/src/lib/framework/registry/global-registry.ts +4 -0
- package/src/lib/framework/registry/registry-types.ts +2 -0
- package/src/lib/graphql/api.ts +25 -3
- package/src/lib/graphql/graphql-env.d.ts +28 -28
- package/src/lib/graphql/settings-store-operations.ts +17 -0
- package/src/lib/hooks/use-floating-bulk-actions.ts +82 -0
- package/src/lib/hooks/use-local-format.ts +20 -5
- package/src/lib/index.ts +2 -1
- package/src/lib/providers/channel-provider.tsx +13 -11
- package/src/lib/providers/user-settings.tsx +78 -3
- package/src/lib/virtual.d.ts +26 -2
- package/src/vite-env.d.ts +2 -0
- package/vite/utils/plugin-discovery.ts +1 -1
- package/vite/utils/ui-config.ts +30 -42
- package/vite/vite-plugin-ui-config.ts +119 -17
- package/vite/vite-plugin-vendure-dashboard.ts +1 -1
- package/dist/plugin/utils/ui-config.d.ts +0 -3
- package/dist/plugin/utils/ui-config.js +0 -34
- package/dist/plugin/vite-plugin-ui-config.d.ts +0 -15
- package/src/app/routes/_authenticated/_facets/components/add-facet-value-dialog.tsx +0 -146
- package/src/lib/components/shared/rich-text-editor.tsx +0 -0
- /package/dist/{plugin/utils/ast-utils.spec.d.ts → vite/types.js} +0 -0
- /package/dist/{plugin → vite}/utils/ast-utils.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/ast-utils.js +0 -0
- /package/dist/{plugin/utils/config-loader.d.ts → vite/utils/ast-utils.spec.d.ts} +0 -0
- /package/dist/{plugin → vite}/utils/ast-utils.spec.js +0 -0
- /package/dist/{plugin → vite}/utils/compiler.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/compiler.js +0 -0
- /package/dist/{plugin/utils/config-loader.js → vite/utils/config-loader.d.ts} +0 -0
- /package/dist/{plugin → vite}/utils/logger.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/logger.js +0 -0
- /package/dist/{plugin → vite}/utils/plugin-discovery.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/schema-generator.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/schema-generator.js +0 -0
- /package/dist/{plugin → vite}/utils/tsconfig-utils.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/tsconfig-utils.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-admin-api-schema.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-admin-api-schema.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-config-loader.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-config-loader.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-config.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-config.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-dashboard-metadata.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-dashboard-metadata.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-gql-tada.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-gql-tada.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-tailwind-source.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-tailwind-source.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-theme.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-theme.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-transform-index.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-transform-index.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-vendure-dashboard.d.ts +0 -0
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
NavMenuSection,
|
|
15
15
|
NavMenuSectionPlacement,
|
|
16
16
|
} from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
|
|
17
|
-
import { Link,
|
|
17
|
+
import { Link, useRouter, useRouterState } from '@tanstack/react-router';
|
|
18
18
|
import { ChevronRight } from 'lucide-react';
|
|
19
19
|
import * as React from 'react';
|
|
20
|
+
import { NavItemWrapper } from './nav-item-wrapper.js';
|
|
20
21
|
|
|
21
22
|
// Utility to sort items & sections by the optional `order` prop (ascending) and then alphabetically by title
|
|
22
23
|
function sortByOrder<T extends { order?: number; title: string }>(a: T, b: T) {
|
|
@@ -28,10 +29,73 @@ function sortByOrder<T extends { order?: number; title: string }>(a: T, b: T) {
|
|
|
28
29
|
return orderA - orderB;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Escapes special regex characters in a string to be used as a literal pattern
|
|
34
|
+
*/
|
|
35
|
+
function escapeRegexChars(str: string): string {
|
|
36
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavMenuItem> }>) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const
|
|
40
|
+
const router = useRouter();
|
|
41
|
+
const routerState = useRouterState();
|
|
42
|
+
const currentPath = routerState.location.pathname;
|
|
43
|
+
const basePath = router.basepath || '';
|
|
44
|
+
|
|
45
|
+
// Helper to check if a path is active
|
|
46
|
+
const isPathActive = React.useCallback(
|
|
47
|
+
(itemUrl: string) => {
|
|
48
|
+
// Remove basepath prefix from current path for comparison
|
|
49
|
+
const normalizedCurrentPath = basePath ? currentPath.replace(new RegExp(`^${escapeRegexChars(basePath)}`), '') : currentPath;
|
|
50
|
+
|
|
51
|
+
// Ensure normalized path starts with /
|
|
52
|
+
const cleanPath = normalizedCurrentPath.startsWith('/') ? normalizedCurrentPath : `/${normalizedCurrentPath}`;
|
|
53
|
+
|
|
54
|
+
// Special handling for root path
|
|
55
|
+
if (itemUrl === '/') {
|
|
56
|
+
return cleanPath === '/' || cleanPath === '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// For other paths, check exact match or prefix match
|
|
60
|
+
return cleanPath === itemUrl || cleanPath.startsWith(`${itemUrl}/`);
|
|
61
|
+
},
|
|
62
|
+
[currentPath, basePath],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Helper to find sections containing active routes
|
|
66
|
+
const findActiveSections = React.useCallback(
|
|
67
|
+
(sections: Array<NavMenuSection | NavMenuItem>) => {
|
|
68
|
+
const activeTopSections = new Set<string>();
|
|
69
|
+
let activeBottomSection: string | null = null;
|
|
70
|
+
|
|
71
|
+
for (const section of sections) {
|
|
72
|
+
if ('items' in section && section.items) {
|
|
73
|
+
const hasActiveItem = section.items.some(item => isPathActive(item.url));
|
|
74
|
+
if (hasActiveItem) {
|
|
75
|
+
if (section.placement === 'top') {
|
|
76
|
+
activeTopSections.add(section.id);
|
|
77
|
+
} else if (section.placement === 'bottom' && !activeBottomSection) {
|
|
78
|
+
activeBottomSection = section.id;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { activeTopSections, activeBottomSection };
|
|
85
|
+
},
|
|
86
|
+
[isPathActive],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Initialize state with active sections on mount
|
|
90
|
+
const [openBottomSectionId, setOpenBottomSectionId] = React.useState<string | null>(() => {
|
|
91
|
+
const { activeBottomSection } = findActiveSections(items);
|
|
92
|
+
return activeBottomSection;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const [openTopSectionIds, setOpenTopSectionIds] = React.useState<Set<string>>(() => {
|
|
96
|
+
const { activeTopSections } = findActiveSections(items);
|
|
97
|
+
return activeTopSections;
|
|
98
|
+
});
|
|
35
99
|
|
|
36
100
|
// Helper to build a sorted list of sections for a given placement, memoized for stability
|
|
37
101
|
const getSortedSections = React.useCallback(
|
|
@@ -52,6 +116,17 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
|
|
|
52
116
|
const topSections = React.useMemo(() => getSortedSections('top'), [getSortedSections]);
|
|
53
117
|
const bottomSections = React.useMemo(() => getSortedSections('bottom'), [getSortedSections]);
|
|
54
118
|
|
|
119
|
+
// Handle top section open/close (only one section open at a time)
|
|
120
|
+
const handleTopSectionToggle = (sectionId: string, isOpen: boolean) => {
|
|
121
|
+
if (isOpen) {
|
|
122
|
+
// When opening a section, close all others
|
|
123
|
+
setOpenTopSectionIds(new Set([sectionId]));
|
|
124
|
+
} else {
|
|
125
|
+
// When closing a section, remove it from the set
|
|
126
|
+
setOpenTopSectionIds(new Set());
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
55
130
|
// Handle bottom section open/close
|
|
56
131
|
const handleBottomSectionToggle = (sectionId: string, isOpen: boolean) => {
|
|
57
132
|
if (isOpen) {
|
|
@@ -61,77 +136,81 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
|
|
|
61
136
|
}
|
|
62
137
|
};
|
|
63
138
|
|
|
64
|
-
//
|
|
139
|
+
// Update open sections when route changes (for client-side navigation)
|
|
65
140
|
React.useEffect(() => {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
item => currentPath === item.url || currentPath.startsWith(`${item.url}/`),
|
|
74
|
-
)
|
|
75
|
-
: null;
|
|
76
|
-
|
|
77
|
-
if (matchingItem) {
|
|
78
|
-
setOpenBottomSectionId(section.id);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
141
|
+
const { activeTopSections, activeBottomSection } = findActiveSections(items);
|
|
142
|
+
|
|
143
|
+
// Replace open sections with only the active one
|
|
144
|
+
setOpenTopSectionIds(activeTopSections);
|
|
145
|
+
|
|
146
|
+
if (activeBottomSection) {
|
|
147
|
+
setOpenBottomSectionId(activeBottomSection);
|
|
81
148
|
}
|
|
82
|
-
}, [
|
|
149
|
+
}, [currentPath, items, findActiveSections]);
|
|
83
150
|
|
|
84
151
|
// Render a top navigation section
|
|
85
152
|
const renderTopSection = (item: NavMenuSection | NavMenuItem) => {
|
|
86
153
|
if ('url' in item) {
|
|
87
154
|
return (
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
{item.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
155
|
+
<NavItemWrapper key={item.title} locationId={item.id} order={item.order} offset={true}>
|
|
156
|
+
<SidebarMenuItem>
|
|
157
|
+
<SidebarMenuButton
|
|
158
|
+
tooltip={item.title}
|
|
159
|
+
asChild
|
|
160
|
+
isActive={isPathActive(item.url)}
|
|
161
|
+
>
|
|
162
|
+
<Link to={item.url}>
|
|
163
|
+
{item.icon && <item.icon />}
|
|
164
|
+
<span>{item.title}</span>
|
|
165
|
+
</Link>
|
|
166
|
+
</SidebarMenuButton>
|
|
167
|
+
</SidebarMenuItem>
|
|
168
|
+
</NavItemWrapper>
|
|
96
169
|
);
|
|
97
170
|
}
|
|
98
171
|
|
|
99
172
|
return (
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
<
|
|
108
|
-
<
|
|
109
|
-
{item.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
173
|
+
<NavItemWrapper key={item.title} locationId={item.id} order={item.order} offset={true}>
|
|
174
|
+
<Collapsible
|
|
175
|
+
asChild
|
|
176
|
+
open={openTopSectionIds.has(item.id)}
|
|
177
|
+
onOpenChange={isOpen => handleTopSectionToggle(item.id, isOpen)}
|
|
178
|
+
className="group/collapsible"
|
|
179
|
+
>
|
|
180
|
+
<SidebarMenuItem>
|
|
181
|
+
<CollapsibleTrigger asChild>
|
|
182
|
+
<SidebarMenuButton tooltip={item.title}>
|
|
183
|
+
{item.icon && <item.icon />}
|
|
184
|
+
<span>{item.title}</span>
|
|
185
|
+
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
|
186
|
+
</SidebarMenuButton>
|
|
187
|
+
</CollapsibleTrigger>
|
|
188
|
+
<CollapsibleContent>
|
|
189
|
+
<SidebarMenuSub>
|
|
190
|
+
{item.items?.map(subItem => (
|
|
191
|
+
<NavItemWrapper
|
|
192
|
+
key={subItem.title}
|
|
193
|
+
locationId={subItem.id}
|
|
194
|
+
order={subItem.order}
|
|
195
|
+
parentLocationId={item.id}
|
|
124
196
|
>
|
|
125
|
-
<
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
197
|
+
<SidebarMenuSubItem>
|
|
198
|
+
<SidebarMenuSubButton
|
|
199
|
+
asChild
|
|
200
|
+
isActive={isPathActive(subItem.url)}
|
|
201
|
+
>
|
|
202
|
+
<Link to={subItem.url}>
|
|
203
|
+
<span>{subItem.title}</span>
|
|
204
|
+
</Link>
|
|
205
|
+
</SidebarMenuSubButton>
|
|
206
|
+
</SidebarMenuSubItem>
|
|
207
|
+
</NavItemWrapper>
|
|
208
|
+
))}
|
|
209
|
+
</SidebarMenuSub>
|
|
210
|
+
</CollapsibleContent>
|
|
211
|
+
</SidebarMenuItem>
|
|
212
|
+
</Collapsible>
|
|
213
|
+
</NavItemWrapper>
|
|
135
214
|
);
|
|
136
215
|
};
|
|
137
216
|
|
|
@@ -139,53 +218,64 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
|
|
|
139
218
|
const renderBottomSection = (item: NavMenuSection | NavMenuItem) => {
|
|
140
219
|
if ('url' in item) {
|
|
141
220
|
return (
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
<
|
|
145
|
-
{item.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
221
|
+
<NavItemWrapper key={item.title} locationId={item.id} order={item.order} offset={true}>
|
|
222
|
+
<SidebarMenuItem>
|
|
223
|
+
<SidebarMenuButton
|
|
224
|
+
tooltip={item.title}
|
|
225
|
+
asChild
|
|
226
|
+
isActive={isPathActive(item.url)}
|
|
227
|
+
>
|
|
228
|
+
<Link to={item.url}>
|
|
229
|
+
{item.icon && <item.icon />}
|
|
230
|
+
<span>{item.title}</span>
|
|
231
|
+
</Link>
|
|
232
|
+
</SidebarMenuButton>
|
|
233
|
+
</SidebarMenuItem>
|
|
234
|
+
</NavItemWrapper>
|
|
150
235
|
);
|
|
151
236
|
}
|
|
152
237
|
return (
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
location.pathname.startsWith(`${subItem.url}/`)
|
|
177
|
-
}
|
|
238
|
+
<NavItemWrapper key={item.title} locationId={item.id} order={item.order} offset={true}>
|
|
239
|
+
<Collapsible
|
|
240
|
+
asChild
|
|
241
|
+
open={openBottomSectionId === item.id}
|
|
242
|
+
onOpenChange={isOpen => handleBottomSectionToggle(item.id, isOpen)}
|
|
243
|
+
className="group/collapsible"
|
|
244
|
+
>
|
|
245
|
+
<SidebarMenuItem>
|
|
246
|
+
<CollapsibleTrigger asChild>
|
|
247
|
+
<SidebarMenuButton tooltip={item.title}>
|
|
248
|
+
{item.icon && <item.icon />}
|
|
249
|
+
<span>{item.title}</span>
|
|
250
|
+
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
|
251
|
+
</SidebarMenuButton>
|
|
252
|
+
</CollapsibleTrigger>
|
|
253
|
+
<CollapsibleContent>
|
|
254
|
+
<SidebarMenuSub>
|
|
255
|
+
{item.items?.map(subItem => (
|
|
256
|
+
<NavItemWrapper
|
|
257
|
+
key={subItem.title}
|
|
258
|
+
locationId={subItem.id}
|
|
259
|
+
order={subItem.order}
|
|
260
|
+
parentLocationId={item.id}
|
|
178
261
|
>
|
|
179
|
-
<
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
262
|
+
<SidebarMenuSubItem>
|
|
263
|
+
<SidebarMenuSubButton
|
|
264
|
+
asChild
|
|
265
|
+
isActive={isPathActive(subItem.url)}
|
|
266
|
+
>
|
|
267
|
+
<Link to={subItem.url}>
|
|
268
|
+
<span>{subItem.title}</span>
|
|
269
|
+
</Link>
|
|
270
|
+
</SidebarMenuSubButton>
|
|
271
|
+
</SidebarMenuSubItem>
|
|
272
|
+
</NavItemWrapper>
|
|
273
|
+
))}
|
|
274
|
+
</SidebarMenuSub>
|
|
275
|
+
</CollapsibleContent>
|
|
276
|
+
</SidebarMenuItem>
|
|
277
|
+
</Collapsible>
|
|
278
|
+
</NavItemWrapper>
|
|
189
279
|
);
|
|
190
280
|
};
|
|
191
281
|
|
|
@@ -193,7 +283,6 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
|
|
|
193
283
|
<>
|
|
194
284
|
{/* Top sections */}
|
|
195
285
|
<SidebarGroup>
|
|
196
|
-
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
|
197
286
|
<SidebarMenu>{topSections.map(renderTopSection)}</SidebarMenu>
|
|
198
287
|
</SidebarGroup>
|
|
199
288
|
|
|
@@ -10,8 +10,10 @@ import { useForm } from 'react-hook-form';
|
|
|
10
10
|
import { toast } from 'sonner';
|
|
11
11
|
import { uiConfig } from 'virtual:vendure-ui-config';
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
+
import { useLoginExtensions } from '../../framework/extension-api/use-login-extensions.js';
|
|
13
14
|
import { LogoMark } from '../shared/logo-mark.js';
|
|
14
15
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../ui/form.js';
|
|
16
|
+
import { Separator } from '../ui/separator.js';
|
|
15
17
|
|
|
16
18
|
export interface LoginFormProps extends React.ComponentProps<'div'> {
|
|
17
19
|
loginError?: string;
|
|
@@ -19,7 +21,7 @@ export interface LoginFormProps extends React.ComponentProps<'div'> {
|
|
|
19
21
|
onFormSubmit?: (username: string, password: string) => void;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
type RemoteLoginImage = {
|
|
24
|
+
export type RemoteLoginImage = {
|
|
23
25
|
urls: { regular: string };
|
|
24
26
|
location: { name: string };
|
|
25
27
|
user: { name: string; links: { html: string } };
|
|
@@ -32,6 +34,7 @@ const formSchema = z.object({
|
|
|
32
34
|
|
|
33
35
|
export function LoginForm({ className, onFormSubmit, isVerifying, loginError, ...props }: LoginFormProps) {
|
|
34
36
|
const [remoteLoginImage, setRemoteLoginImage] = React.useState<RemoteLoginImage | null>(null);
|
|
37
|
+
const loginExtensions = useLoginExtensions();
|
|
35
38
|
|
|
36
39
|
React.useEffect(() => {
|
|
37
40
|
if (!uiConfig.loginImageUrl) {
|
|
@@ -66,17 +69,39 @@ export function LoginForm({ className, onFormSubmit, isVerifying, loginError, ..
|
|
|
66
69
|
>
|
|
67
70
|
<div className="flex flex-col gap-6">
|
|
68
71
|
<div className="flex flex-col items-start space-y-4">
|
|
69
|
-
{
|
|
70
|
-
|
|
72
|
+
{loginExtensions.logo ? (
|
|
73
|
+
<>
|
|
74
|
+
<loginExtensions.logo.component />
|
|
75
|
+
{loginExtensions.beforeForm && (
|
|
76
|
+
<>
|
|
77
|
+
<loginExtensions.beforeForm.component />
|
|
78
|
+
<Separator className="w-full" />
|
|
79
|
+
</>
|
|
80
|
+
)}
|
|
81
|
+
</>
|
|
82
|
+
) : (
|
|
83
|
+
<>
|
|
84
|
+
{!uiConfig.hideVendureBranding && (
|
|
85
|
+
<LogoMark className="text-vendure-brand h-6 w-auto" />
|
|
86
|
+
)}
|
|
87
|
+
<div>
|
|
88
|
+
<h1 className="text-2xl font-medium">
|
|
89
|
+
<Trans>Welcome back!</Trans>
|
|
90
|
+
</h1>
|
|
91
|
+
<p className="text-muted-foreground text-balance">
|
|
92
|
+
Login to your Vendure store
|
|
93
|
+
</p>
|
|
94
|
+
</div>
|
|
95
|
+
{loginExtensions.beforeForm && (
|
|
96
|
+
<>
|
|
97
|
+
<Separator className="w-full" />
|
|
98
|
+
<div className="w-full">
|
|
99
|
+
<loginExtensions.beforeForm.component />
|
|
100
|
+
</div>
|
|
101
|
+
</>
|
|
102
|
+
)}
|
|
103
|
+
</>
|
|
71
104
|
)}
|
|
72
|
-
<div>
|
|
73
|
-
<h1 className="text-2xl font-medium">
|
|
74
|
-
<Trans>Welcome back!</Trans>
|
|
75
|
-
</h1>
|
|
76
|
-
<p className="text-muted-foreground text-balance">
|
|
77
|
-
Login to your Vendure store
|
|
78
|
-
</p>
|
|
79
|
-
</div>
|
|
80
105
|
</div>
|
|
81
106
|
<FormField
|
|
82
107
|
control={form.control}
|
|
@@ -117,7 +142,6 @@ export function LoginForm({ className, onFormSubmit, isVerifying, loginError, ..
|
|
|
117
142
|
</FormItem>
|
|
118
143
|
)}
|
|
119
144
|
/>
|
|
120
|
-
|
|
121
145
|
<Button type="submit" disabled={isVerifying}>
|
|
122
146
|
{isVerifying && (
|
|
123
147
|
<>
|
|
@@ -128,44 +152,55 @@ export function LoginForm({ className, onFormSubmit, isVerifying, loginError, ..
|
|
|
128
152
|
{!isVerifying && <span>Login</span>}
|
|
129
153
|
</Button>
|
|
130
154
|
</div>
|
|
155
|
+
{loginExtensions.afterForm && (
|
|
156
|
+
<>
|
|
157
|
+
<Separator className="w-full my-4" />
|
|
158
|
+
|
|
159
|
+
<loginExtensions.afterForm.component />
|
|
160
|
+
</>
|
|
161
|
+
)}
|
|
131
162
|
</form>
|
|
132
163
|
</Form>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
164
|
+
{loginExtensions.loginImage ? (
|
|
165
|
+
<loginExtensions.loginImage.component />
|
|
166
|
+
) : (
|
|
167
|
+
<div className="bg-muted relative hidden md:block lg:min-h-[500px]">
|
|
168
|
+
{remoteLoginImage && (
|
|
169
|
+
<>
|
|
170
|
+
<img
|
|
171
|
+
src={remoteLoginImage.urls.regular}
|
|
172
|
+
alt="Image"
|
|
173
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
174
|
+
/>
|
|
175
|
+
<div className="absolute h-full w-full top-0 left-0 flex items-end justify-start bg-gradient-to-b from-transparent to-black/80 p-4 ">
|
|
176
|
+
<div>
|
|
177
|
+
<p className="text-lg font-medium text-white">
|
|
178
|
+
{remoteLoginImage.location.name}
|
|
179
|
+
</p>
|
|
180
|
+
<p className="text-sm text-white/80">
|
|
181
|
+
By
|
|
182
|
+
<a
|
|
183
|
+
className="mx-1 underline"
|
|
184
|
+
href={remoteLoginImage.user.links.html}
|
|
185
|
+
target="_blank"
|
|
186
|
+
>
|
|
187
|
+
{remoteLoginImage.user.name}
|
|
188
|
+
</a>
|
|
189
|
+
on Unsplash
|
|
190
|
+
</p>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</>
|
|
194
|
+
)}
|
|
195
|
+
{uiConfig.loginImageUrl && (
|
|
136
196
|
<img
|
|
137
|
-
src={
|
|
138
|
-
alt="
|
|
197
|
+
src={uiConfig.loginImageUrl}
|
|
198
|
+
alt="Login image"
|
|
139
199
|
className="absolute inset-0 h-full w-full object-cover"
|
|
140
200
|
/>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
{remoteLoginImage.location.name}
|
|
145
|
-
</p>
|
|
146
|
-
<p className="text-sm text-white/80">
|
|
147
|
-
By
|
|
148
|
-
<a
|
|
149
|
-
className="mx-1 underline"
|
|
150
|
-
href={remoteLoginImage.user.links.html}
|
|
151
|
-
target="_blank"
|
|
152
|
-
>
|
|
153
|
-
{remoteLoginImage.user.name}
|
|
154
|
-
</a>
|
|
155
|
-
on Unsplash
|
|
156
|
-
</p>
|
|
157
|
-
</div>
|
|
158
|
-
</div>
|
|
159
|
-
</>
|
|
160
|
-
)}
|
|
161
|
-
{uiConfig.loginImageUrl && (
|
|
162
|
-
<img
|
|
163
|
-
src={uiConfig.loginImageUrl}
|
|
164
|
-
alt="Login image"
|
|
165
|
-
className="absolute inset-0 h-full w-full object-cover"
|
|
166
|
-
/>
|
|
167
|
-
)}
|
|
168
|
-
</div>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
)}
|
|
169
204
|
</CardContent>
|
|
170
205
|
</Card>
|
|
171
206
|
</div>
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
DropdownMenuTrigger,
|
|
9
9
|
} from '@/vdb/components/ui/dropdown-menu.js';
|
|
10
10
|
import { getBulkActions } from '@/vdb/framework/data-table/data-table-extensions.js';
|
|
11
|
+
import { useFloatingBulkActions } from '@/vdb/hooks/use-floating-bulk-actions.js';
|
|
11
12
|
import { usePageBlock } from '@/vdb/hooks/use-page-block.js';
|
|
12
13
|
import { usePage } from '@/vdb/hooks/use-page.js';
|
|
13
14
|
import { Trans } from '@/vdb/lib/trans.js';
|
|
@@ -34,9 +35,15 @@ interface AssetBulkActionsProps {
|
|
|
34
35
|
|
|
35
36
|
export function AssetBulkActions({ selection, bulkActions, refetch }: Readonly<AssetBulkActionsProps>) {
|
|
36
37
|
const { pageId } = usePage();
|
|
37
|
-
const
|
|
38
|
+
const pageBlock = usePageBlock();
|
|
39
|
+
const blockId = pageBlock?.blockId;
|
|
40
|
+
|
|
41
|
+
const { position, shouldShow } = useFloatingBulkActions({
|
|
42
|
+
selectionCount: selection.length,
|
|
43
|
+
containerSelector: '[data-asset-gallery]'
|
|
44
|
+
});
|
|
38
45
|
|
|
39
|
-
if (
|
|
46
|
+
if (!shouldShow) {
|
|
40
47
|
return null;
|
|
41
48
|
}
|
|
42
49
|
|
|
@@ -62,13 +69,21 @@ export function AssetBulkActions({ selection, bulkActions, refetch }: Readonly<A
|
|
|
62
69
|
allBulkActions.sort((a, b) => (a.order ?? 10_000) - (b.order ?? 10_000));
|
|
63
70
|
|
|
64
71
|
return (
|
|
65
|
-
<div
|
|
72
|
+
<div
|
|
73
|
+
className="flex items-center gap-4 px-8 py-2 animate-in fade-in duration-200 fixed transform -translate-x-1/2 bg-white shadow-2xl rounded-md border z-50"
|
|
74
|
+
style={{
|
|
75
|
+
height: 'auto',
|
|
76
|
+
maxHeight: '60px',
|
|
77
|
+
bottom: position.bottom,
|
|
78
|
+
left: position.left
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
66
81
|
<span className="text-sm text-muted-foreground">
|
|
67
82
|
<Trans>{selection.length} selected</Trans>
|
|
68
83
|
</span>
|
|
69
84
|
<DropdownMenu>
|
|
70
85
|
<DropdownMenuTrigger asChild>
|
|
71
|
-
<Button variant="outline" size="sm" className="h-8">
|
|
86
|
+
<Button variant="outline" size="sm" className="h-8 shadow-none">
|
|
72
87
|
<Trans>With selected...</Trans>
|
|
73
88
|
<ChevronDown className="ml-2 h-4 w-4" />
|
|
74
89
|
</Button>
|
|
@@ -232,7 +232,7 @@ export function AssetGallery({
|
|
|
232
232
|
};
|
|
233
233
|
|
|
234
234
|
return (
|
|
235
|
-
<div className={`flex flex-col w-full ${fixedHeight ? 'h-[600px]' : ''} ${className}`}>
|
|
235
|
+
<div className={`relative flex flex-col w-full ${fixedHeight ? 'h-[600px]' : ''} ${className}`}>
|
|
236
236
|
{showHeader && (
|
|
237
237
|
<div className="flex flex-col md:flex-row gap-2 mb-4 flex-shrink-0">
|
|
238
238
|
<div className="relative flex-grow flex items-center gap-2">
|
|
@@ -291,7 +291,7 @@ export function AssetGallery({
|
|
|
291
291
|
</div>
|
|
292
292
|
)}
|
|
293
293
|
|
|
294
|
-
<div className="grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-3 p-1">
|
|
294
|
+
<div data-asset-gallery className="grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-3 p-1">
|
|
295
295
|
{isLoading ? (
|
|
296
296
|
<div className="col-span-full flex justify-center py-12">
|
|
297
297
|
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|