@vendure/dashboard 3.6.0-minor-202511061555 → 3.6.0-minor-202512161252
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/constants.js +2 -2
- package/dist/vite/constants.js +1 -0
- package/dist/vite/utils/compiler.d.ts +1 -0
- package/dist/vite/utils/compiler.js +5 -4
- package/dist/vite/utils/get-dashboard-paths.d.ts +5 -0
- package/dist/vite/utils/get-dashboard-paths.js +20 -0
- package/dist/vite/vite-plugin-dashboard-metadata.js +2 -1
- package/dist/vite/vite-plugin-tailwind-source.js +2 -15
- package/dist/vite/vite-plugin-translations.d.ts +10 -1
- package/dist/vite/vite-plugin-translations.js +156 -45
- package/dist/vite/vite-plugin-vendure-dashboard.d.ts +12 -0
- package/dist/vite/vite-plugin-vendure-dashboard.js +1 -0
- package/lingui.config.js +1 -0
- package/package.json +7 -7
- package/src/app/routeTree.gen.ts +1221 -0
- package/src/app/routes/_authenticated/_administrators/administrators.tsx +9 -12
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +6 -9
- package/src/app/routes/_authenticated/_channels/channels.tsx +9 -12
- package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_collections/collections.tsx +9 -12
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_countries/countries.tsx +9 -12
- package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx +9 -12
- package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +0 -1
- package/src/app/routes/_authenticated/_customers/customers.tsx +9 -12
- package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_facets/facets.tsx +9 -12
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +10 -13
- package/src/app/routes/_authenticated/_orders/components/add-surcharge-form.tsx +139 -0
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +3 -0
- package/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx +3 -1
- package/src/app/routes/_authenticated/_orders/components/order-address.tsx +3 -3
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +41 -41
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +49 -11
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +4 -1
- package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +2 -3
- package/src/app/routes/_authenticated/_orders/orders.tsx +3 -3
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +12 -3
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +27 -30
- package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +23 -0
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +9 -12
- package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_product-variants/components/add-currency-dropdown.tsx +3 -3
- package/src/app/routes/_authenticated/_product-variants/components/add-stock-location-dropdown.tsx +2 -2
- package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +10 -12
- package/src/app/routes/_authenticated/_products/products.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_products/products.tsx +15 -18
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_profile/profile.tsx +3 -3
- package/src/app/routes/_authenticated/_promotions/promotions.tsx +9 -12
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_roles/roles.tsx +9 -12
- package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_sellers/sellers.tsx +9 -12
- package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +11 -12
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +19 -20
- package/src/app/routes/_authenticated/_stock-locations/stock-locations.tsx +9 -12
- package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_system/healthchecks.tsx +2 -3
- package/src/app/routes/_authenticated/_system/job-queue.tsx +3 -3
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.tsx +9 -12
- package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -12
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_zones/components/zone-bulk-actions.tsx +49 -1
- package/src/app/routes/_authenticated/_zones/components/zone-countries-table.tsx +34 -16
- package/src/app/routes/_authenticated/_zones/zones.tsx +9 -12
- package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/index.tsx +5 -3
- package/src/i18n/locales/bg.po +3436 -0
- package/src/lib/components/data-input/datetime-input.tsx +1 -1
- package/src/lib/components/data-input/default-relation-input.tsx +1 -1
- package/src/lib/components/data-input/relation-selector.tsx +1 -1
- package/src/lib/components/data-input/string-list-input.tsx +188 -26
- package/src/lib/components/data-input/struct-form-input.tsx +175 -174
- package/src/lib/components/data-table/column-header-wrapper.tsx +1 -1
- package/src/lib/components/data-table/data-table-filter-badge.tsx +2 -2
- package/src/lib/components/data-table/data-table.tsx +1 -1
- package/src/lib/components/data-table/use-generated-columns.tsx +1 -1
- package/src/lib/components/layout/channel-switcher.tsx +6 -2
- package/src/lib/components/layout/content-language-selector.tsx +6 -7
- package/src/lib/components/layout/dev-mode-indicator.tsx +7 -3
- package/src/lib/components/layout/language-dialog.tsx +26 -13
- package/src/lib/components/layout/manage-languages-dialog.tsx +10 -29
- package/src/lib/components/layout/nav-item-wrapper.tsx +1 -1
- package/src/lib/components/shared/asset/asset-gallery.tsx +8 -3
- package/src/lib/components/shared/configurable-operation-multi-selector.tsx +14 -16
- package/src/lib/components/shared/custom-fields-form.tsx +14 -9
- package/src/lib/components/shared/language-selector.tsx +14 -6
- package/src/lib/components/shared/multi-select.tsx +1 -1
- package/src/lib/components/shared/navigation-confirmation.tsx +1 -1
- package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +4 -4
- package/src/lib/components/ui/carousel.tsx +2 -2
- package/src/lib/components/ui/chart.tsx +1 -1
- package/src/lib/components/ui/context-menu.tsx +1 -1
- package/src/lib/components/ui/drawer.tsx +1 -1
- package/src/lib/components/ui/grid-layout.tsx +1 -1
- package/src/lib/components/ui/input-group.tsx +1 -0
- package/src/lib/components/ui/input-otp.tsx +1 -1
- package/src/lib/components/ui/menubar.tsx +1 -1
- package/src/lib/components/ui/navigation-menu.tsx +1 -1
- package/src/lib/components/ui/progress.tsx +1 -1
- package/src/lib/components/ui/radio-group.tsx +1 -1
- package/src/lib/components/ui/resizable.tsx +1 -1
- package/src/lib/components/ui/select.tsx +1 -1
- package/src/lib/components/ui/slider.tsx +1 -1
- package/src/lib/components/ui/toggle-group.tsx +2 -2
- package/src/lib/components/ui/toggle.tsx +1 -1
- package/src/lib/framework/component-registry/component-registry.tsx +2 -6
- package/src/lib/framework/document-introspection/add-custom-fields.spec.ts +907 -1
- package/src/lib/framework/document-introspection/add-custom-fields.ts +248 -119
- package/src/lib/framework/extension-api/display-component-extensions.tsx +4 -3
- package/src/lib/framework/extension-api/logic/detail-forms.ts +0 -13
- package/src/lib/framework/extension-api/logic/navigation.ts +1 -1
- package/src/lib/framework/extension-api/types/data-table.ts +4 -2
- package/src/lib/framework/extension-api/types/layout.ts +34 -1
- package/src/lib/framework/extension-api/types/navigation.ts +7 -2
- package/src/lib/framework/form-engine/use-generated-form.tsx +7 -1
- package/src/lib/framework/history-entry/history-entry.tsx +1 -1
- package/src/lib/framework/layout-engine/action-bar-item-wrapper.tsx +185 -0
- package/src/lib/framework/layout-engine/dev-mode-button.tsx +15 -13
- package/src/lib/framework/layout-engine/location-wrapper.tsx +3 -1
- package/src/lib/framework/layout-engine/page-layout.spec.tsx +138 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +294 -69
- package/src/lib/framework/nav-menu/nav-menu-extensions.ts +1 -1
- package/src/lib/framework/page/detail-page-route-loader.tsx +1 -1
- package/src/lib/framework/page/page-api.ts +1 -1
- package/src/lib/framework/page/use-detail-page.ts +4 -2
- package/src/lib/framework/page/use-extended-router.tsx +20 -16
- package/src/lib/framework/registry/registry-types.ts +2 -1
- package/src/lib/graphql/api.ts +3 -8
- package/src/lib/graphql/graphql-env.d.ts +29 -10
- package/src/lib/hooks/use-permissions.ts +3 -3
- package/src/lib/hooks/use-sorted-languages.ts +41 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/lib/load-i18n-messages.ts +4 -1
- package/src/lib/providers/channel-provider.tsx +11 -7
- package/src/lib/utils/config-utils.ts +19 -0
- package/src/lib/virtual.d.ts +3 -0
- package/LICENSE.md +0 -42
- package/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx +0 -129
- /package/src/{app/routes/_authenticated/_global-settings → lib}/utils/global-languages.ts +0 -0
|
@@ -20,7 +20,7 @@ export function DataTableFilterBadge({
|
|
|
20
20
|
const [operator, value] = Object.entries(filter.value as Record<string, unknown>)[0];
|
|
21
21
|
return (
|
|
22
22
|
<Badge key={filter.id} className="flex gap-2 flex-wrap items-center" variant="outline">
|
|
23
|
-
<
|
|
23
|
+
<button
|
|
24
24
|
className="flex gap-1 flex-wrap items-center cursor-pointer flex-1"
|
|
25
25
|
onClick={() => onClick?.(filter)}
|
|
26
26
|
>
|
|
@@ -37,7 +37,7 @@ export function DataTableFilterBadge({
|
|
|
37
37
|
<div className="@xs:overflow-hidden @xs:text-ellipsis @xs:whitespace-nowrap flex flex-col @xl:flex-row @2xl:gap-1">
|
|
38
38
|
<FilterValue value={value} dataType={dataType} currencyCode={currencyCode} />
|
|
39
39
|
</div>
|
|
40
|
-
</
|
|
40
|
+
</button>
|
|
41
41
|
<button className="border-l -mr-2" onClick={() => onRemove(filter)}>
|
|
42
42
|
<XIcon className="h-4 flex-shrink-0 cursor-pointer" />
|
|
43
43
|
</button>
|
|
@@ -209,7 +209,6 @@ export function DataTable<TData>({
|
|
|
209
209
|
...pagination,
|
|
210
210
|
pageIndex: 0,
|
|
211
211
|
});
|
|
212
|
-
pagination.pageIndex;
|
|
213
212
|
}
|
|
214
213
|
prevColumnFiltersRef.current = columnFilters;
|
|
215
214
|
}, [columnFilters]);
|
|
@@ -255,6 +254,7 @@ export function DataTable<TData>({
|
|
|
255
254
|
title={filter?.title}
|
|
256
255
|
options={filter?.options}
|
|
257
256
|
optionsFn={filter?.optionsFn}
|
|
257
|
+
icon={filter?.icon}
|
|
258
258
|
/>
|
|
259
259
|
))}
|
|
260
260
|
</Suspense>
|
|
@@ -163,7 +163,7 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
|
|
|
163
163
|
if (!id) {
|
|
164
164
|
throw new Error('Column id is required');
|
|
165
165
|
}
|
|
166
|
-
finalColumns.push(columnHelper.accessor(id as any, { ...column, id
|
|
166
|
+
finalColumns.push(columnHelper.accessor(id as any, { enableColumnFilter: false, ...column, id }));
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
if (defaultColumnOrder) {
|
|
@@ -17,6 +17,7 @@ import { DEFAULT_CHANNEL_CODE } from '@/vdb/constants.js';
|
|
|
17
17
|
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
18
18
|
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
19
19
|
import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
|
|
20
|
+
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
20
21
|
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
21
22
|
import { cn } from '@/vdb/lib/utils.js';
|
|
22
23
|
import { Trans } from '@lingui/react/macro';
|
|
@@ -65,6 +66,9 @@ export function ChannelSwitcher() {
|
|
|
65
66
|
? [displayChannel, ...channels.filter(ch => ch.id !== displayChannel.id)]
|
|
66
67
|
: channels;
|
|
67
68
|
|
|
69
|
+
// Sort language codes by their formatted names and map to code and label
|
|
70
|
+
const sortedLanguages = useSortedLanguages(displayChannel?.availableLanguageCodes);
|
|
71
|
+
|
|
68
72
|
useEffect(() => {
|
|
69
73
|
if (activeChannel?.availableLanguageCodes) {
|
|
70
74
|
// Ensure the current content language is a valid one for the active
|
|
@@ -150,7 +154,7 @@ export function ChannelSwitcher() {
|
|
|
150
154
|
</div>
|
|
151
155
|
</DropdownMenuSubTrigger>
|
|
152
156
|
<DropdownMenuSubContent>
|
|
153
|
-
{
|
|
157
|
+
{sortedLanguages?.map(({ code: languageCode, label }) => (
|
|
154
158
|
<DropdownMenuItem
|
|
155
159
|
key={`${channel.code}-${languageCode}`}
|
|
156
160
|
onClick={() => setContentLanguage(languageCode)}
|
|
@@ -161,7 +165,7 @@ export function ChannelSwitcher() {
|
|
|
161
165
|
{languageCode.toUpperCase()}
|
|
162
166
|
</span>
|
|
163
167
|
</div>
|
|
164
|
-
<span>{
|
|
168
|
+
<span>{label}</span>
|
|
165
169
|
{contentLanguage === languageCode && (
|
|
166
170
|
<span className="ml-auto text-xs text-muted-foreground">
|
|
167
171
|
<Trans context="active language">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
|
|
2
|
-
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
3
2
|
import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
|
|
3
|
+
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
4
4
|
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
5
5
|
import { cn } from '@/vdb/lib/utils.js';
|
|
6
6
|
|
|
@@ -12,14 +12,13 @@ interface ContentLanguageSelectorProps {
|
|
|
12
12
|
|
|
13
13
|
export function ContentLanguageSelector({ value, onChange, className }: ContentLanguageSelectorProps) {
|
|
14
14
|
const serverConfig = useServerConfig();
|
|
15
|
-
const { formatLanguageName } = useLocalFormat();
|
|
16
15
|
const {
|
|
17
16
|
settings: { contentLanguage },
|
|
18
17
|
setContentLanguage,
|
|
19
18
|
} = useUserSettings();
|
|
20
19
|
|
|
21
|
-
//
|
|
22
|
-
const
|
|
20
|
+
// Map languages to code and label, then sort by label
|
|
21
|
+
const sortedLanguages = useSortedLanguages(serverConfig?.availableLanguages);
|
|
23
22
|
|
|
24
23
|
// If no value is provided but languages are available, use the first language
|
|
25
24
|
const currentValue = contentLanguage;
|
|
@@ -36,9 +35,9 @@ export function ContentLanguageSelector({ value, onChange, className }: ContentL
|
|
|
36
35
|
<SelectValue placeholder="Select language" />
|
|
37
36
|
</SelectTrigger>
|
|
38
37
|
<SelectContent>
|
|
39
|
-
{
|
|
40
|
-
<SelectItem key={
|
|
41
|
-
{
|
|
38
|
+
{sortedLanguages.map(({ code, label }) => (
|
|
39
|
+
<SelectItem key={code} value={code}>
|
|
40
|
+
{label}
|
|
42
41
|
</SelectItem>
|
|
43
42
|
))}
|
|
44
43
|
</SelectContent>
|
|
@@ -2,14 +2,18 @@ import { Badge } from '@/vdb/components/ui/badge.js';
|
|
|
2
2
|
import { Button } from '@/vdb/components/ui/button.js';
|
|
3
3
|
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
4
4
|
import { Trans } from '@lingui/react/macro';
|
|
5
|
-
import {
|
|
5
|
+
import { SquareDashedMousePointer, XIcon } from 'lucide-react';
|
|
6
6
|
|
|
7
7
|
export function DevModeIndicator() {
|
|
8
8
|
const { setDevMode } = useUserSettings();
|
|
9
9
|
return (
|
|
10
10
|
<Badge className="bg-dev-mode text-background">
|
|
11
|
-
<
|
|
12
|
-
|
|
11
|
+
<div>
|
|
12
|
+
<SquareDashedMousePointer className="w-4 h-4" />
|
|
13
|
+
</div>
|
|
14
|
+
<div>
|
|
15
|
+
<Trans>Dev Mode</Trans>
|
|
16
|
+
</div>
|
|
13
17
|
<Button variant="ghost" size="icon-xs" onClick={() => setDevMode(false)}>
|
|
14
18
|
<XIcon className="w-4 h-4" />
|
|
15
19
|
</Button>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { CurrencyCode } from '@/vdb/constants.js';
|
|
2
2
|
import { useDisplayLocale } from '@/vdb/hooks/use-display-locale.js';
|
|
3
3
|
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
4
|
+
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
4
5
|
import { useUiLanguageLoader } from '@/vdb/hooks/use-ui-language-loader.js';
|
|
5
6
|
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
6
7
|
import { Trans } from '@lingui/react/macro';
|
|
7
|
-
import { useState } from 'react';
|
|
8
|
+
import { useMemo, useState } from 'react';
|
|
8
9
|
import { uiConfig } from 'virtual:vendure-ui-config';
|
|
9
10
|
import { Button } from '../ui/button.js';
|
|
10
11
|
import { DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog.js';
|
|
@@ -18,12 +19,24 @@ export function LanguageDialog() {
|
|
|
18
19
|
const { settings, setDisplayLanguage, setDisplayLocale } = useUserSettings();
|
|
19
20
|
const { humanReadableLanguageAndLocale } = useDisplayLocale();
|
|
20
21
|
const availableCurrencyCodes = Object.values(CurrencyCode);
|
|
21
|
-
const { formatCurrency,
|
|
22
|
-
useLocalFormat();
|
|
22
|
+
const { formatCurrency, formatRegionName, formatCurrencyName, formatDate } = useLocalFormat();
|
|
23
23
|
const [selectedCurrency, setSelectedCurrency] = useState<string>('USD');
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
const
|
|
25
|
+
// Map and sort languages by their formatted names
|
|
26
|
+
const sortedLanguages = useSortedLanguages(availableLanguages);
|
|
27
|
+
|
|
28
|
+
// Map and sort locales by their formatted region names
|
|
29
|
+
const sortedLocales = useMemo(
|
|
30
|
+
() =>
|
|
31
|
+
availableLocales
|
|
32
|
+
.map(code => ({
|
|
33
|
+
code,
|
|
34
|
+
label: formatRegionName(code),
|
|
35
|
+
}))
|
|
36
|
+
.sort((a, b) => a.label.localeCompare(b.label)),
|
|
37
|
+
[availableLocales, formatRegionName],
|
|
38
|
+
);
|
|
39
|
+
|
|
27
40
|
const handleLanguageChange = async (value: string) => {
|
|
28
41
|
setDisplayLanguage(value);
|
|
29
42
|
void loadAndActivateLocale(value);
|
|
@@ -46,10 +59,10 @@ export function LanguageDialog() {
|
|
|
46
59
|
<SelectValue placeholder="Select a language" />
|
|
47
60
|
</SelectTrigger>
|
|
48
61
|
<SelectContent>
|
|
49
|
-
{
|
|
50
|
-
<SelectItem key={
|
|
51
|
-
<span className="uppercase text-muted-foreground">{
|
|
52
|
-
<span>{
|
|
62
|
+
{sortedLanguages.map(({ code, label }) => (
|
|
63
|
+
<SelectItem key={code} value={code} className="flex gap-1">
|
|
64
|
+
<span className="uppercase text-muted-foreground">{code}</span>
|
|
65
|
+
<span>{label}</span>
|
|
53
66
|
</SelectItem>
|
|
54
67
|
))}
|
|
55
68
|
</SelectContent>
|
|
@@ -64,10 +77,10 @@ export function LanguageDialog() {
|
|
|
64
77
|
<SelectValue placeholder="Select a locale" />
|
|
65
78
|
</SelectTrigger>
|
|
66
79
|
<SelectContent>
|
|
67
|
-
{
|
|
68
|
-
<SelectItem key={
|
|
69
|
-
<span className="uppercase text-muted-foreground">{
|
|
70
|
-
<span>{
|
|
80
|
+
{sortedLocales.map(({ code, label }) => (
|
|
81
|
+
<SelectItem key={code} value={code} className="flex gap-1">
|
|
82
|
+
<span className="uppercase text-muted-foreground">{code}</span>
|
|
83
|
+
<span>{label}</span>
|
|
71
84
|
</SelectItem>
|
|
72
85
|
))}
|
|
73
86
|
</SelectContent>
|
|
@@ -17,6 +17,8 @@ import { graphql } from '@/vdb/graphql/graphql.js';
|
|
|
17
17
|
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
18
18
|
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
19
19
|
import { usePermissions } from '@/vdb/hooks/use-permissions.js';
|
|
20
|
+
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
21
|
+
import { globalLanguageCodes } from '@/vdb/utils/global-languages.js';
|
|
20
22
|
import { Trans } from '@lingui/react/macro';
|
|
21
23
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
22
24
|
import { AlertCircle, Lock } from 'lucide-react';
|
|
@@ -67,35 +69,12 @@ const updateChannelDocument = graphql(`
|
|
|
67
69
|
}
|
|
68
70
|
`);
|
|
69
71
|
|
|
70
|
-
// All possible language codes for global settings - includes more than what might be globally enabled
|
|
71
|
-
const ALL_LANGUAGE_CODES = [
|
|
72
|
-
'en',
|
|
73
|
-
'es',
|
|
74
|
-
'fr',
|
|
75
|
-
'de',
|
|
76
|
-
'it',
|
|
77
|
-
'pt',
|
|
78
|
-
'nl',
|
|
79
|
-
'pl',
|
|
80
|
-
'ru',
|
|
81
|
-
'ja',
|
|
82
|
-
'zh',
|
|
83
|
-
'ko',
|
|
84
|
-
'ar',
|
|
85
|
-
'hi',
|
|
86
|
-
'sv',
|
|
87
|
-
'da',
|
|
88
|
-
'no',
|
|
89
|
-
'fi',
|
|
90
|
-
];
|
|
91
|
-
|
|
92
72
|
interface ManageLanguagesDialogProps {
|
|
93
73
|
open: boolean;
|
|
94
74
|
onClose: () => void;
|
|
95
75
|
}
|
|
96
76
|
|
|
97
77
|
export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogProps) {
|
|
98
|
-
const { formatLanguageName } = useLocalFormat();
|
|
99
78
|
const { activeChannel } = useChannel();
|
|
100
79
|
const { hasPermissions } = usePermissions();
|
|
101
80
|
const queryClient = useQueryClient();
|
|
@@ -114,6 +93,9 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
|
|
|
114
93
|
const [channelLanguages, setChannelLanguages] = useState<string[]>([]);
|
|
115
94
|
const [channelDefaultLanguage, setChannelDefaultLanguage] = useState<string>('');
|
|
116
95
|
|
|
96
|
+
// Map and sort channel languages by their formatted names
|
|
97
|
+
const sortedChannelLanguages = useSortedLanguages(channelLanguages || []);
|
|
98
|
+
|
|
117
99
|
// Queries
|
|
118
100
|
const {
|
|
119
101
|
data: globalSettingsData,
|
|
@@ -304,7 +286,7 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
|
|
|
304
286
|
value={globalLanguages}
|
|
305
287
|
onChange={handleGlobalLanguagesChange}
|
|
306
288
|
multiple={true}
|
|
307
|
-
availableLanguageCodes={
|
|
289
|
+
availableLanguageCodes={globalLanguageCodes}
|
|
308
290
|
/>
|
|
309
291
|
</div>
|
|
310
292
|
<p className="text-xs text-muted-foreground">
|
|
@@ -362,7 +344,7 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
|
|
|
362
344
|
)}
|
|
363
345
|
</div>
|
|
364
346
|
|
|
365
|
-
{
|
|
347
|
+
{sortedChannelLanguages.length > 0 && (
|
|
366
348
|
<div>
|
|
367
349
|
<Label className="text-sm font-medium mb-2 block">
|
|
368
350
|
<Trans>Default Language</Trans>
|
|
@@ -376,10 +358,9 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
|
|
|
376
358
|
<SelectValue placeholder="Select default language" />
|
|
377
359
|
</SelectTrigger>
|
|
378
360
|
<SelectContent>
|
|
379
|
-
{
|
|
380
|
-
<SelectItem key={
|
|
381
|
-
{
|
|
382
|
-
{languageCode.toUpperCase()})
|
|
361
|
+
{sortedChannelLanguages.map(({ code, label }) => (
|
|
362
|
+
<SelectItem key={code} value={code}>
|
|
363
|
+
{label} ({code.toUpperCase()})
|
|
383
364
|
</SelectItem>
|
|
384
365
|
))}
|
|
385
366
|
</SelectContent>
|
|
@@ -79,7 +79,7 @@ export function NavItemWrapper({
|
|
|
79
79
|
>
|
|
80
80
|
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
|
81
81
|
<PopoverTrigger asChild>
|
|
82
|
-
<DevModeButton className={`h-
|
|
82
|
+
<DevModeButton className={`h-5 w-5 top-0 end-0`} />
|
|
83
83
|
</PopoverTrigger>
|
|
84
84
|
<PopoverContent className="w-48 p-3">
|
|
85
85
|
<div className="space-y-2">
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
PaginationPrevious,
|
|
14
14
|
} from '@/vdb/components/ui/pagination.js';
|
|
15
15
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
|
|
16
|
+
import { ActionBarItem, PageActionBar } from '@/vdb/framework/layout-engine/page-layout.js';
|
|
16
17
|
import { api } from '@/vdb/graphql/api.js';
|
|
17
18
|
import { assetFragment, AssetFragment } from '@/vdb/graphql/fragments.js';
|
|
18
19
|
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
@@ -357,9 +358,13 @@ export function AssetGallery({
|
|
|
357
358
|
<SelectItem value={AssetType.BINARY}>Binary</SelectItem>
|
|
358
359
|
</SelectContent>
|
|
359
360
|
</Select>
|
|
360
|
-
<
|
|
361
|
-
<
|
|
362
|
-
|
|
361
|
+
<PageActionBar>
|
|
362
|
+
<ActionBarItem itemId="upload-assets-button">
|
|
363
|
+
<Button onClick={openFileDialog} className="whitespace-nowrap">
|
|
364
|
+
<Upload className="h-4 w-4 mr-2" /> <Trans>Upload</Trans>
|
|
365
|
+
</Button>
|
|
366
|
+
</ActionBarItem>
|
|
367
|
+
</PageActionBar>
|
|
363
368
|
</div>
|
|
364
369
|
|
|
365
370
|
{hasTags && (
|
|
@@ -94,17 +94,17 @@ type QueryData = {
|
|
|
94
94
|
* ```
|
|
95
95
|
*/
|
|
96
96
|
export function ConfigurableOperationMultiSelector({
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
97
|
+
value,
|
|
98
|
+
onChange,
|
|
99
|
+
queryDocument,
|
|
100
|
+
queryOptions,
|
|
101
|
+
queryKey,
|
|
102
|
+
dataPath,
|
|
103
|
+
buttonText,
|
|
104
|
+
dropdownTitle,
|
|
105
|
+
emptyText = 'No options found',
|
|
106
|
+
showEnhancedDropdown = true,
|
|
107
|
+
}: Readonly<ConfigurableOperationMultiSelectorProps>) {
|
|
108
108
|
const { data } = useQuery<QueryData>(
|
|
109
109
|
queryOptions || {
|
|
110
110
|
queryKey: [queryKey],
|
|
@@ -134,7 +134,7 @@ export function ConfigurableOperationMultiSelector({
|
|
|
134
134
|
code: operation.code,
|
|
135
135
|
arguments: operationDef.args.map(arg => ({
|
|
136
136
|
name: arg.name,
|
|
137
|
-
value: arg.defaultValue != null ? arg.defaultValue.toString() : '',
|
|
137
|
+
value: arg.defaultValue != null ? arg.defaultValue.toString() : arg.list ? '[]' : '',
|
|
138
138
|
})),
|
|
139
139
|
},
|
|
140
140
|
]);
|
|
@@ -195,10 +195,8 @@ export function ConfigurableOperationMultiSelector({
|
|
|
195
195
|
onCombinationModeChange(index, newValue)
|
|
196
196
|
}
|
|
197
197
|
name={''}
|
|
198
|
-
ref={() => {
|
|
199
|
-
}}
|
|
200
|
-
onBlur={() => {
|
|
201
|
-
}}
|
|
198
|
+
ref={() => {}}
|
|
199
|
+
onBlur={() => {}}
|
|
202
200
|
position={index}
|
|
203
201
|
/>
|
|
204
202
|
</div>
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '@/vdb/components/ui/form.js';
|
|
11
11
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/vdb/components/ui/tabs.js';
|
|
12
12
|
import { CustomFormComponent } from '@/vdb/framework/form-engine/custom-form-component.js';
|
|
13
|
+
import { ConfigurableFieldDef } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
13
14
|
import { useCustomFieldConfig } from '@/vdb/hooks/use-custom-field-config.js';
|
|
14
15
|
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
15
16
|
import { customFieldConfigFragment } from '@/vdb/providers/server-config.js';
|
|
@@ -20,7 +21,7 @@ import { Control } from 'react-hook-form';
|
|
|
20
21
|
import { FormControlAdapter } from '../../framework/form-engine/form-control-adapter.js';
|
|
21
22
|
import { TranslatableFormField } from './translatable-form-field.js';
|
|
22
23
|
|
|
23
|
-
type CustomFieldConfig = ResultOf<typeof customFieldConfigFragment>;
|
|
24
|
+
type CustomFieldConfig = Omit<ResultOf<typeof customFieldConfigFragment>, '__typename'>;
|
|
24
25
|
|
|
25
26
|
interface CustomFieldsFormProps {
|
|
26
27
|
entityType: string;
|
|
@@ -72,7 +73,7 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readon
|
|
|
72
73
|
const shouldShowTabs = useMemo(() => {
|
|
73
74
|
if (!customFields) return false;
|
|
74
75
|
const hasTabbedFields = customFields.some(field => field.ui?.tab);
|
|
75
|
-
return hasTabbedFields
|
|
76
|
+
return hasTabbedFields && groupedFields.length > 1;
|
|
76
77
|
}, [customFields, groupedFields.length]);
|
|
77
78
|
|
|
78
79
|
if (!shouldShowTabs) {
|
|
@@ -120,8 +121,8 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readon
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
interface CustomFieldItemProps {
|
|
123
|
-
fieldDef:
|
|
124
|
-
control: Control<any
|
|
124
|
+
fieldDef: ConfigurableFieldDef;
|
|
125
|
+
control: Control<any>;
|
|
125
126
|
fieldName: string;
|
|
126
127
|
}
|
|
127
128
|
|
|
@@ -130,14 +131,19 @@ function CustomFieldItem({ fieldDef, control, fieldName }: Readonly<CustomFieldI
|
|
|
130
131
|
settings: { displayLanguage },
|
|
131
132
|
} = useUserSettings();
|
|
132
133
|
|
|
133
|
-
const getTranslation = (
|
|
134
|
+
const getTranslation = (
|
|
135
|
+
input: string | Array<{ languageCode: string; value: string }> | null | undefined,
|
|
136
|
+
) => {
|
|
137
|
+
if (typeof input === 'string') {
|
|
138
|
+
return input;
|
|
139
|
+
}
|
|
134
140
|
return input?.find(t => t.languageCode === displayLanguage)?.value;
|
|
135
141
|
};
|
|
136
142
|
const hasCustomFormComponent = fieldDef.ui?.component;
|
|
137
143
|
const isLocaleField = fieldDef.type === 'localeString' || fieldDef.type === 'localeText';
|
|
138
144
|
const shouldBeFullWidth = fieldDef.ui?.fullWidth === true;
|
|
139
145
|
const containerClassName = shouldBeFullWidth ? 'col-span-2' : '';
|
|
140
|
-
const isReadonly = fieldDef.readonly ?? false;
|
|
146
|
+
const isReadonly = (fieldDef as CustomFieldConfig).readonly ?? false;
|
|
141
147
|
|
|
142
148
|
// For locale fields, always use TranslatableFormField regardless of custom components
|
|
143
149
|
if (isLocaleField) {
|
|
@@ -212,7 +218,6 @@ function CustomFieldItem({ fieldDef, control, fieldName }: Readonly<CustomFieldI
|
|
|
212
218
|
<StructFormInput {...inputField} fieldDef={fieldDef} />
|
|
213
219
|
)}
|
|
214
220
|
defaultValue={{}} // Empty struct object as default
|
|
215
|
-
isFullWidth={true} // Structs should always be full-width
|
|
216
221
|
/>
|
|
217
222
|
</FormControl>
|
|
218
223
|
<FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
|
|
@@ -266,9 +271,9 @@ function CustomFieldItem({ fieldDef, control, fieldName }: Readonly<CustomFieldI
|
|
|
266
271
|
}
|
|
267
272
|
|
|
268
273
|
interface CustomFieldFormItemProps {
|
|
269
|
-
fieldDef:
|
|
274
|
+
fieldDef: ConfigurableFieldDef;
|
|
270
275
|
getTranslation: (
|
|
271
|
-
input: Array<{ languageCode: string; value: string }> | null | undefined,
|
|
276
|
+
input: string | Array<{ languageCode: string; value: string }> | null | undefined,
|
|
272
277
|
) => string | undefined;
|
|
273
278
|
fieldName: string;
|
|
274
279
|
children: React.ReactNode;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { api } from '@/vdb/graphql/api.js';
|
|
2
2
|
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
3
|
-
import {
|
|
3
|
+
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
4
4
|
import { useLingui } from '@lingui/react/macro';
|
|
5
5
|
import { useQuery } from '@tanstack/react-query';
|
|
6
|
+
import { useMemo } from 'react';
|
|
6
7
|
import { MultiSelect } from './multi-select.js';
|
|
7
8
|
|
|
8
9
|
const availableGlobalLanguages = graphql(`
|
|
@@ -26,14 +27,21 @@ export function LanguageSelector<T extends boolean>(props: LanguageSelectorProps
|
|
|
26
27
|
queryFn: () => api.query(availableGlobalLanguages),
|
|
27
28
|
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
28
29
|
});
|
|
29
|
-
const { formatLanguageName } = useLocalFormat();
|
|
30
30
|
const { value, onChange, multiple, availableLanguageCodes } = props;
|
|
31
31
|
const { t } = useLingui();
|
|
32
32
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const sortedLanguages = useSortedLanguages(
|
|
34
|
+
availableLanguageCodes ?? data?.globalSettings.availableLanguages ?? undefined,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const items = useMemo(
|
|
38
|
+
() =>
|
|
39
|
+
sortedLanguages.map(language => ({
|
|
40
|
+
value: language.code,
|
|
41
|
+
label: language.label,
|
|
42
|
+
})),
|
|
43
|
+
[sortedLanguages],
|
|
44
|
+
);
|
|
37
45
|
|
|
38
46
|
return (
|
|
39
47
|
<MultiSelect
|
|
@@ -127,7 +127,7 @@ export function MultiSelect<T extends boolean>(props: MultiSelectProps<T>) {
|
|
|
127
127
|
return (
|
|
128
128
|
<Popover>
|
|
129
129
|
<PopoverTrigger asChild>{renderTrigger()}</PopoverTrigger>
|
|
130
|
-
<PopoverContent className="w-[200px] p-0" side="bottom" align="start">
|
|
130
|
+
<PopoverContent className="w-[200px] p-0" side="bottom" align="start" onWheel={(e) => e.stopPropagation()}>
|
|
131
131
|
{(showSearch === true || items.length > 10) && (
|
|
132
132
|
<div className="p-2">
|
|
133
133
|
<Input
|
|
@@ -34,7 +34,7 @@ export function NavigationConfirmation(props: Readonly<NavigationConfirmationPro
|
|
|
34
34
|
return props.form.formState.isDirty;
|
|
35
35
|
},
|
|
36
36
|
withResolver: true,
|
|
37
|
-
enableBeforeUnload:
|
|
37
|
+
enableBeforeUnload: () => props.form.formState.isDirty,
|
|
38
38
|
});
|
|
39
39
|
return (
|
|
40
40
|
<Dialog open={status === 'blocked'} onOpenChange={reset}>
|
|
@@ -47,10 +47,10 @@ export const RichTextDescriptionCell: DataTableCellComponent<{ description: stri
|
|
|
47
47
|
|
|
48
48
|
// Strip HTML tags and decode HTML entities
|
|
49
49
|
const textContent = useMemo(() => {
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
return
|
|
50
|
+
if (!value) return '';
|
|
51
|
+
const div = document.createElement('div');
|
|
52
|
+
div.innerHTML = value;
|
|
53
|
+
return div.textContent ?? '';
|
|
54
54
|
}, [value]);
|
|
55
55
|
|
|
56
56
|
const shortLength = 100;
|
|
@@ -6,8 +6,8 @@ import useEmblaCarousel, {
|
|
|
6
6
|
} from "embla-carousel-react"
|
|
7
7
|
import { ArrowLeft, ArrowRight } from "lucide-react"
|
|
8
8
|
|
|
9
|
-
import { cn } from "@/vdb/lib/utils"
|
|
10
|
-
import { Button } from "@/vdb/components/ui/button"
|
|
9
|
+
import { cn } from "@/vdb/lib/utils.js"
|
|
10
|
+
import { Button } from "@/vdb/components/ui/button.js"
|
|
11
11
|
|
|
12
12
|
type CarouselApi = UseEmblaCarouselType[1]
|
|
13
13
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
|
@@ -4,7 +4,7 @@ import * as React from "react"
|
|
|
4
4
|
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
|
5
5
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
|
6
6
|
|
|
7
|
-
import { cn } from "@/vdb/lib/utils"
|
|
7
|
+
import { cn } from "@/vdb/lib/utils.js"
|
|
8
8
|
|
|
9
9
|
function ContextMenu({
|
|
10
10
|
...props
|
|
@@ -127,6 +127,7 @@ function InputGroupInput({ className, ...props }: React.ComponentProps<'input'>)
|
|
|
127
127
|
'flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent',
|
|
128
128
|
className,
|
|
129
129
|
)}
|
|
130
|
+
value={props.value}
|
|
130
131
|
{...props}
|
|
131
132
|
/>
|
|
132
133
|
);
|
|
@@ -2,7 +2,7 @@ import * as React from "react"
|
|
|
2
2
|
import * as MenubarPrimitive from "@radix-ui/react-menubar"
|
|
3
3
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
|
4
4
|
|
|
5
|
-
import { cn } from "@/vdb/lib/utils"
|
|
5
|
+
import { cn } from "@/vdb/lib/utils.js"
|
|
6
6
|
|
|
7
7
|
function Menubar({
|
|
8
8
|
className,
|
|
@@ -3,7 +3,7 @@ import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
|
|
3
3
|
import { cva } from "class-variance-authority"
|
|
4
4
|
import { ChevronDownIcon } from "lucide-react"
|
|
5
5
|
|
|
6
|
-
import { cn } from "@/vdb/lib/utils"
|
|
6
|
+
import { cn } from "@/vdb/lib/utils.js"
|
|
7
7
|
|
|
8
8
|
function NavigationMenu({
|
|
9
9
|
className,
|