@deenruv/admin-dashboard 1.0.16 → 1.0.17-dev.15
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/DeenruvAdminPanel.js +35 -46
- package/dist/access/access-context.js +10 -0
- package/dist/access/built-in-routes.js +300 -0
- package/dist/access/index.js +6 -0
- package/dist/access/navigation.js +33 -0
- package/dist/access/permission-access.js +9 -0
- package/dist/access/permission-routes.js +3 -0
- package/dist/access/types.js +1 -0
- package/dist/components/GlobalSearch.js +37 -62
- package/dist/components/Menu/ActiveAdmins.js +1 -1
- package/dist/components/Menu/ChannelSwitcher.js +1 -1
- package/dist/components/Menu/LanguagesDropdown.js +1 -1
- package/dist/components/Menu/Navigation.js +68 -229
- package/dist/components/Menu/NavigationFooter.js +7 -4
- package/dist/components/Menu/Notifications.js +1 -1
- package/dist/components/Menu/index.js +50 -16
- package/dist/components/storefrontAction/StorefrontAction.js +9 -0
- package/dist/components/storefrontAction/StorefrontAction.logic.js +27 -0
- package/dist/components/storefrontAction/index.js +2 -0
- package/dist/index.css +54 -44
- package/dist/locales/en/admins.json +62 -0
- package/dist/locales/en/collections.json +21 -0
- package/dist/locales/en/common.json +2 -0
- package/dist/locales/en/orders.json +8 -0
- package/dist/locales/en/products.json +29 -2
- package/dist/locales/en/promotions.json +4 -0
- package/dist/locales/pl/admins.json +62 -0
- package/dist/locales/pl/collections.json +21 -0
- package/dist/locales/pl/common.json +2 -0
- package/dist/locales/pl/orders.json +8 -0
- package/dist/locales/pl/products.json +29 -2
- package/dist/locales/pl/promotions.json +4 -0
- package/dist/pages/Root.js +31 -15
- package/dist/pages/admins/Detail.js +1 -1
- package/dist/pages/admins/List.js +8 -3
- package/dist/pages/admins/Provision.js +173 -0
- package/dist/pages/admins/Provision.logic.js +53 -0
- package/dist/pages/admins/index.js +1 -0
- package/dist/pages/assets/List.js +3 -3
- package/dist/pages/channels/Detail.js +1 -1
- package/dist/pages/collections/Detail.js +24 -6
- package/dist/pages/collections/List.js +87 -20
- package/dist/pages/collections/_components/CollectionDetailView.js +15 -2
- package/dist/pages/collections/_components/FiltersCard.js +51 -11
- package/dist/pages/collections/_components/collectionStorefrontAction/CollectionStorefrontAction.js +41 -0
- package/dist/pages/collections/_components/collectionStorefrontAction/CollectionStorefrontAction.logic.js +44 -0
- package/dist/pages/collections/_components/collectionStorefrontAction/index.js +2 -0
- package/dist/pages/countries/Detail.js +1 -1
- package/dist/pages/customers/_components/PersonalDataCard.js +2 -1
- package/dist/pages/extensions/Extensions.js +3 -7
- package/dist/pages/facets/Detail.js +1 -1
- package/dist/pages/orders/Detail.js +38 -5
- package/dist/pages/orders/List.js +1 -16
- package/dist/pages/orders/_components/CancelAndRefundDialog.js +1 -2
- package/dist/pages/orders/_components/OrderLineCustomFields.js +1 -1
- package/dist/pages/orders/_components/ProductsTable.js +1 -2
- package/dist/pages/orders/_components/RefundPaymentCard.js +1 -2
- package/dist/pages/payment-methods/Detail.js +1 -1
- package/dist/pages/product-variants/List.js +3 -3
- package/dist/pages/products/Detail.js +62 -5
- package/dist/pages/products/_components/AssetsCard.js +15 -2
- package/dist/pages/products/_components/OptionsCard.js +7 -5
- package/dist/pages/products/_components/PriceCard.js +21 -8
- package/dist/pages/products/_components/ProductDetailView.js +7 -3
- package/dist/pages/products/_components/Variant.js +53 -26
- package/dist/pages/products/_components/VariantsTab.js +1 -1
- package/dist/pages/products/_components/productStorefrontAction/ProductStorefrontAction.js +42 -0
- package/dist/pages/products/_components/productStorefrontAction/ProductStorefrontAction.logic.js +39 -0
- package/dist/pages/products/_components/productStorefrontAction/index.js +2 -0
- package/dist/pages/promotions/Detail.js +34 -3
- package/dist/pages/promotions/_components/EnabledCard.js +2 -2
- package/dist/pages/promotions/_components/PromotionDetailSidebar.js +2 -1
- package/dist/pages/roles/Detail.js +1 -1
- package/dist/pages/roles/_components/PermissionsTable.js +0 -1
- package/dist/pages/sellers/Detail.js +1 -1
- package/dist/pages/shipping-methods/Detail.js +10 -6
- package/dist/pages/shipping-methods/_components/CalculatorCard.js +27 -11
- package/dist/pages/shipping-methods/_components/CheckerCard.js +28 -12
- package/dist/pages/stock-locations/Detail.js +1 -1
- package/dist/pages/tax-rates/Detail.js +1 -1
- package/dist/pages/zones/Detail.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +68 -68
|
@@ -7,6 +7,20 @@
|
|
|
7
7
|
"baseInfoUpdated": "Zaktualizowano: {{value}}",
|
|
8
8
|
"buttonDisabledTooltip": "Wypełnij wszystkie wymagane pola",
|
|
9
9
|
"noChangesTooltip": "Nie wprowadzono żadnych zmian",
|
|
10
|
+
"storefrontAction": {
|
|
11
|
+
"label": "Otwórz w sklepie",
|
|
12
|
+
"disabled": {
|
|
13
|
+
"collectionPrivate": "Ustaw kolekcję jako publiczną i zapisz ją, aby otworzyć ją w sklepie.",
|
|
14
|
+
"missingChannel": "Wybierz kanał, aby otworzyć kolekcję w sklepie.",
|
|
15
|
+
"missingSlug": "Zapisana kolekcja nie ma slugu dla wybranego języka.",
|
|
16
|
+
"unsavedPrivate": "Zapisz bieżącą widoczność, aby otworzyć kolekcję w sklepie.",
|
|
17
|
+
"unsavedSlug": "Zapisz bieżący slug, aby otworzyć kolekcję w sklepie.",
|
|
18
|
+
"resolverFailed": "Nie udało się wyznaczyć adresu URL sklepu.",
|
|
19
|
+
"resolverEmpty": "Konfiguracja sklepu nie zwróciła adresu URL.",
|
|
20
|
+
"urlNotAbsolute": "Adres URL sklepu musi być bezwzględny.",
|
|
21
|
+
"unsupportedProtocol": "Adres URL sklepu musi używać protokołu HTTP lub HTTPS."
|
|
22
|
+
}
|
|
23
|
+
},
|
|
10
24
|
"table": {
|
|
11
25
|
"id": "ID",
|
|
12
26
|
"name": "Nazwa",
|
|
@@ -38,6 +52,7 @@
|
|
|
38
52
|
"inherit": "Dziedziczenie filtrów",
|
|
39
53
|
"condition": "Warunek",
|
|
40
54
|
"addCondition": "Dodaj warunek",
|
|
55
|
+
"noAvailableConditions": "Warunki filtrów nadal się ładują",
|
|
41
56
|
"addFacets": "Dodawanie wartości aspektów",
|
|
42
57
|
"labels": {
|
|
43
58
|
"codes": {
|
|
@@ -55,6 +70,12 @@
|
|
|
55
70
|
},
|
|
56
71
|
"contents": {
|
|
57
72
|
"title": "Zawartość"
|
|
73
|
+
},
|
|
74
|
+
"channels": {
|
|
75
|
+
"title": "Dodatkowe kanały",
|
|
76
|
+
"description": "Wybierz dodatkowe kanały, które mają zostać przypisane po utworzeniu kolekcji. Bieżący kanał zostanie przypisany automatycznie.",
|
|
77
|
+
"currentChannel": "Bieżący kanał",
|
|
78
|
+
"noChannels": "Brak dostępnych kanałów"
|
|
58
79
|
}
|
|
59
80
|
},
|
|
60
81
|
"visibility": {
|
|
@@ -581,6 +581,7 @@
|
|
|
581
581
|
"globalSettings": "ustawienia globalne",
|
|
582
582
|
"roles": "role",
|
|
583
583
|
"admins": "administratorzy",
|
|
584
|
+
"adminProvision": "zakładanie administratora",
|
|
584
585
|
"stock": "lokalizacje magazynowe",
|
|
585
586
|
"sellers": "sprzedawcy",
|
|
586
587
|
"assets": "aktywa",
|
|
@@ -961,6 +962,7 @@
|
|
|
961
962
|
"tags": "Filtruj według tagu",
|
|
962
963
|
"perPageValue": "{{value}} na stronę",
|
|
963
964
|
"confirmButton": "Dodaj zasób",
|
|
965
|
+
"uploadButton": "Prześlij z dysku",
|
|
964
966
|
"cancel": "Anuluj",
|
|
965
967
|
"noImages": "Brak zasobów dla określonego wyszukiwania",
|
|
966
968
|
"description": "Przesyłanie i zarządzanie zasobami, które mogą być używane w treści"
|
|
@@ -40,6 +40,14 @@
|
|
|
40
40
|
"confirm": "Usuń wersję roboczą",
|
|
41
41
|
"cancel": "Anuluj"
|
|
42
42
|
},
|
|
43
|
+
"createDraftOrder": {
|
|
44
|
+
"title": "Utwórz nowy projekt zamówienia",
|
|
45
|
+
"description": "Projekt zamówienia zostanie utworzony dopiero po potwierdzeniu. Następnie możesz dodać produkty, klienta i dane dostawy.",
|
|
46
|
+
"startButton": "Rozpocznij projekt zamówienia",
|
|
47
|
+
"cancelButton": "Wróć do zamówień",
|
|
48
|
+
"creating": "Tworzenie projektu...",
|
|
49
|
+
"error": "Nie udało się utworzyć projektu zamówienia"
|
|
50
|
+
},
|
|
43
51
|
"searchProduct": {
|
|
44
52
|
"id": "ID",
|
|
45
53
|
"image": "Obraz",
|
|
@@ -42,6 +42,29 @@
|
|
|
42
42
|
"removeVariant": "Usuń wariant",
|
|
43
43
|
"removeVariantFromChannel": "Usuń wariant z kanału"
|
|
44
44
|
},
|
|
45
|
+
"storefrontAction": {
|
|
46
|
+
"label": "Otwórz w sklepie",
|
|
47
|
+
"disabled": {
|
|
48
|
+
"productDisabled": "Włącz i zapisz produkt, aby otworzyć go w sklepie.",
|
|
49
|
+
"missingChannel": "Wybierz kanał, aby otworzyć produkt w sklepie.",
|
|
50
|
+
"channelNotAssigned": "Zapisany produkt nie jest przypisany do wybranego kanału.",
|
|
51
|
+
"missingSlug": "Zapisany produkt nie ma slugu dla wybranego języka.",
|
|
52
|
+
"unsavedSlug": "Zapisz bieżący slug, aby otworzyć produkt w sklepie.",
|
|
53
|
+
"unsavedEnabled": "Zapisz bieżący status dostępności, aby otworzyć produkt w sklepie.",
|
|
54
|
+
"resolverFailed": "Nie udało się wyznaczyć adresu URL sklepu.",
|
|
55
|
+
"resolverEmpty": "Konfiguracja sklepu nie zwróciła adresu URL.",
|
|
56
|
+
"urlNotAbsolute": "Adres URL sklepu musi być bezwzględny.",
|
|
57
|
+
"unsupportedProtocol": "Adres URL sklepu musi używać protokołu HTTP lub HTTPS."
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"initialVariant": {
|
|
61
|
+
"title": "Początkowy wariant",
|
|
62
|
+
"description": "Utwórz jeden domyślny wariant razem z tym produktem. Jeśli nazwa wariantu jest pusta, zostanie użyta nazwa produktu.",
|
|
63
|
+
"sku": "SKU",
|
|
64
|
+
"price": "Cena",
|
|
65
|
+
"name": "Nazwa wariantu",
|
|
66
|
+
"namePlaceholder": "Użyj nazwy produktu"
|
|
67
|
+
},
|
|
45
68
|
"optionsTab": {
|
|
46
69
|
"placeholder": "Wpisz, aby dodać nową wartość opcji",
|
|
47
70
|
"groupName": "Nazwa grupy",
|
|
@@ -53,6 +76,7 @@
|
|
|
53
76
|
"noOptionValues": "Dodaj opcje powyżej, aby znaleźć je tutaj"
|
|
54
77
|
},
|
|
55
78
|
"variantsTab": {
|
|
79
|
+
"unnamedVariant": "Nienazwany wariant",
|
|
56
80
|
"emptyState": {
|
|
57
81
|
"title": "Nic tutaj nie ma",
|
|
58
82
|
"description": "Nie ma jeszcze żadnych wariantów. Użyj przycisku powyżej, aby je dodać, pod warunkiem, że dodałeś już jakieś opcje. Jeśli nie, utwórz je najpierw w zakładce Opcje."
|
|
@@ -128,6 +152,7 @@
|
|
|
128
152
|
"noAssets": "Nie ma jeszcze żadnych aktywów",
|
|
129
153
|
"setAsFeatured": "Ustaw jako polecane",
|
|
130
154
|
"editAsset": "Edytuj zdjęcie",
|
|
155
|
+
"uploadAsset": "Prześlij obraz",
|
|
131
156
|
"removeAsset": "Usuń zasób",
|
|
132
157
|
"usedForColorsDesc": "Użyj tego atrybutu dla facetów, którzy są kolorami",
|
|
133
158
|
"colorsCollectionDesc": "Użyj tego dla ścieżek produktu w KOLORZE (BLAT/CORPUS)",
|
|
@@ -186,9 +211,11 @@
|
|
|
186
211
|
"new": "Nowy wariant",
|
|
187
212
|
"selectOption": "Wybierz opcję",
|
|
188
213
|
"createBasic": "Dane podstawowe",
|
|
189
|
-
"createOptions": "Opcje"
|
|
214
|
+
"createOptions": "Opcje",
|
|
215
|
+
"createOptionsHint": "Dodaj grupę opcji w tym panelu, aby rozpocząć wybieranie opcji wariantu. Wartości opcji możesz później dopracować w zakładce Opcje."
|
|
190
216
|
},
|
|
191
217
|
"validation": {
|
|
192
|
-
"nameSlugRequired": "Pola nazwy i slug są wymagane"
|
|
218
|
+
"nameSlugRequired": "Pola nazwy i slug są wymagane",
|
|
219
|
+
"initialVariantSkuRequired": "SKU wariantu jest wymagane"
|
|
193
220
|
}
|
|
194
221
|
}
|
package/dist/pages/Root.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { CanLeaveRouteDialog } from "../components";
|
|
3
3
|
import { serverConfigSelector, configurableOperationDefinitionSelector, countrySelector, apiClient, useOrder, useNotifications, DEFAULT_CHANNEL_CODE, useTranslation, capitalizeFirstLetter, } from '@deenruv/react-ui-devkit';
|
|
4
|
-
import { useCallback, useEffect } from 'react';
|
|
4
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
5
5
|
import { Outlet, useLocation, useSearchParams } from 'react-router';
|
|
6
6
|
import { toast } from 'sonner';
|
|
7
7
|
import { activeAdministratorSelector, paymentMethodsSelector, useServer, useSettings, } from '@deenruv/react-ui-devkit';
|
|
@@ -45,8 +45,8 @@ const getAllPaymentMethods = async () => {
|
|
|
45
45
|
export const Root = ({ allPaths }) => {
|
|
46
46
|
const isLocalhost = window.location.hostname === 'localhost';
|
|
47
47
|
const { t } = useTranslation('common');
|
|
48
|
-
const
|
|
49
|
-
const
|
|
48
|
+
const clearAdministratorAccess = useServer((p) => p.clearAdministratorAccess);
|
|
49
|
+
const setAdministratorAccess = useServer((p) => p.setAdministratorAccess);
|
|
50
50
|
const setServerConfig = useServer((p) => p.setServerConfig);
|
|
51
51
|
const setCountries = useServer((p) => p.setCountries);
|
|
52
52
|
const setFulfillmentHandlers = useServer((p) => p.setFulfillmentHandlers);
|
|
@@ -68,6 +68,7 @@ export const Root = ({ allPaths }) => {
|
|
|
68
68
|
const channel = useSettings((p) => p.selectedChannel);
|
|
69
69
|
const location = useLocation();
|
|
70
70
|
const [searchParams] = useSearchParams();
|
|
71
|
+
const hasInitializedAdministratorAccess = useRef(false);
|
|
71
72
|
const managePageTitle = useCallback(() => {
|
|
72
73
|
const pathname = location.pathname;
|
|
73
74
|
const isDeenruvPath = allPaths.some((path) => pathname.startsWith(path));
|
|
@@ -143,17 +144,22 @@ export const Root = ({ allPaths }) => {
|
|
|
143
144
|
};
|
|
144
145
|
}, [notifications.map((n) => n.id).join(','), loaded]);
|
|
145
146
|
useEffect(() => {
|
|
147
|
+
if (hasInitializedAdministratorAccess.current)
|
|
148
|
+
return;
|
|
149
|
+
hasInitializedAdministratorAccess.current = true;
|
|
150
|
+
clearAdministratorAccess();
|
|
146
151
|
const init = async () => {
|
|
147
152
|
const activeAdministratorResponse = await apiClient('query')({
|
|
148
153
|
activeAdministrator: activeAdministratorSelector,
|
|
149
154
|
});
|
|
150
155
|
const roles = activeAdministratorResponse.activeAdministrator?.user.roles || [];
|
|
151
156
|
if (!activeAdministratorResponse.activeAdministrator) {
|
|
157
|
+
clearAdministratorAccess();
|
|
152
158
|
toast.error(t('setup.failedAdmin'));
|
|
159
|
+
return;
|
|
153
160
|
}
|
|
154
161
|
else {
|
|
155
|
-
|
|
156
|
-
setUserPermissions(Array.from(new Set(roles.flatMap((role) => role.permissions))));
|
|
162
|
+
setAdministratorAccess(activeAdministratorResponse.activeAdministrator, Array.from(new Set(roles.flatMap((role) => role.permissions))));
|
|
157
163
|
if ([Permission.ReadChannel].some((p) => roles.some((r) => r.permissions.includes(p)))) {
|
|
158
164
|
const { channels: { items: allChannels = [] }, } = await apiClient('query')({
|
|
159
165
|
channels: [
|
|
@@ -200,12 +206,20 @@ export const Root = ({ allPaths }) => {
|
|
|
200
206
|
if (window?.__DEENRUV_SETTINGS__?.ui?.defaultTranslationLanguageCode) {
|
|
201
207
|
// setTranslationLanguage(window?.__DEENRUV_SETTINGS__?.ui?.defaultTranslationLanguageCode);
|
|
202
208
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
+
try {
|
|
210
|
+
const { globalSettings } = await apiClient('query')({
|
|
211
|
+
globalSettings: { serverConfig: serverConfigSelector, availableLanguages: true },
|
|
212
|
+
});
|
|
213
|
+
fetchPendingJobs();
|
|
214
|
+
initializeOrderCustomFields(globalSettings.serverConfig);
|
|
215
|
+
setLoaded(true);
|
|
216
|
+
setServerConfig(globalSettings.serverConfig);
|
|
217
|
+
setAvailableLanguages(globalSettings.availableLanguages);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
toast.error(t('setup.failedServer'));
|
|
221
|
+
setLoaded(true);
|
|
222
|
+
}
|
|
209
223
|
if ([Permission.ReadCountry, Permission.ReadPaymentMethod, Permission.ReadOrder].some((p) => roles.some((r) => r.permissions.includes(p)))) {
|
|
210
224
|
const [countriesResponse, paymentsResponse, fulfillmentsResponse] = await Promise.allSettled([
|
|
211
225
|
getAllPaginatedCountries(),
|
|
@@ -216,8 +230,6 @@ export const Root = ({ allPaths }) => {
|
|
|
216
230
|
toast.error(t('setup.failedServer'));
|
|
217
231
|
}
|
|
218
232
|
else {
|
|
219
|
-
setServerConfig(globalSettings.serverConfig);
|
|
220
|
-
setAvailableLanguages(globalSettings.availableLanguages);
|
|
221
233
|
// const socket = serverConfigResponse.value.globalSettings.serverConfig.plugins?.find(
|
|
222
234
|
// (plugin) => plugin.name === 'AexolAdminsPlugin',
|
|
223
235
|
// );
|
|
@@ -243,10 +255,14 @@ export const Root = ({ allPaths }) => {
|
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
257
|
};
|
|
246
|
-
fetchGraphQLSchema()
|
|
258
|
+
fetchGraphQLSchema()
|
|
259
|
+
.then(async (schema) => {
|
|
247
260
|
window.__DEENRUV_SCHEMA__ = schema;
|
|
248
261
|
await init();
|
|
262
|
+
})
|
|
263
|
+
.catch(() => {
|
|
264
|
+
clearAdministratorAccess();
|
|
249
265
|
});
|
|
250
266
|
}, []);
|
|
251
|
-
return (_jsx(_Fragment, { children: _jsxs("div", { className: "flex max-h-screen w-full max-w-full overflow-hidden bg-background text-foreground", children: [_jsx(Menu, { children: loaded ? _jsx(Outlet, {}) : _jsx(ContentAreaSkeleton, {}) }), loaded && (_jsxs(_Fragment, { children: [_jsx(GlobalSearch, {}), _jsx(CanLeaveRouteDialog, {}), isLocalhost ? _jsx(DeenruvDeveloperIndicator, {}) : null] }))] }) }));
|
|
267
|
+
return (_jsx(_Fragment, { children: _jsxs("div", { className: "flex max-h-screen w-full max-w-full overflow-hidden bg-background text-foreground antialiased", children: [_jsx(Menu, { children: loaded ? _jsx(Outlet, {}) : _jsx(ContentAreaSkeleton, {}) }), loaded && (_jsxs(_Fragment, { children: [_jsx(GlobalSearch, {}), _jsx(CanLeaveRouteDialog, {}), isLocalhost ? _jsx(DeenruvDeveloperIndicator, {}) : null] }))] }) }));
|
|
252
268
|
};
|
|
@@ -45,7 +45,7 @@ export const AdminsDetailPage = () => {
|
|
|
45
45
|
if (!id) {
|
|
46
46
|
throw new Error('Could not find the id.');
|
|
47
47
|
}
|
|
48
|
-
return remove({
|
|
48
|
+
return remove({ id });
|
|
49
49
|
}, [remove, id]);
|
|
50
50
|
return (_jsx("div", { className: "relative flex flex-col gap-y-4", children: _jsx(DetailView, { id: id, locationId: "admins-detail-view", main: {
|
|
51
51
|
name: 'admin',
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Routes, apiClient, DetailList, deepMerge, ListBadge, ListLocations, useTranslation, TableLabel, } from '@deenruv/react-ui-devkit';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Routes, apiClient, DetailList, deepMerge, ListBadge, ListLocations, useTranslation, TableLabel, useServer, } from '@deenruv/react-ui-devkit';
|
|
3
3
|
import { Permission, SortOrder } from '@deenruv/admin-types';
|
|
4
|
+
import { UserPlus } from 'lucide-react';
|
|
5
|
+
import { useNavigate } from 'react-router';
|
|
4
6
|
const tableId = 'admins-list-view';
|
|
5
7
|
const { selector } = ListLocations[tableId];
|
|
6
8
|
const fetch = async ({ page, perPage, filter, filterOperator, sort }, additionalSelector) => {
|
|
@@ -40,6 +42,9 @@ const onRemove = async (items) => {
|
|
|
40
42
|
};
|
|
41
43
|
export const AdminsListPage = () => {
|
|
42
44
|
const { t } = useTranslation('admins');
|
|
45
|
+
const navigate = useNavigate();
|
|
46
|
+
const userPermissions = useServer(({ userPermissions }) => userPermissions);
|
|
47
|
+
const canCreateAdministrators = userPermissions.includes(Permission.CreateAdministrator);
|
|
43
48
|
return (_jsx(DetailList, { filterFields: [
|
|
44
49
|
{ key: 'firstName', operator: 'StringOperators' },
|
|
45
50
|
{ key: 'emailAddress', operator: 'StringOperators' },
|
|
@@ -52,5 +57,5 @@ export const AdminsListPage = () => {
|
|
|
52
57
|
header: () => _jsx(TableLabel, { children: t('table.role') }),
|
|
53
58
|
cell: ({ row }) => (_jsx("div", { className: "flex gap-1", children: row.original.user.roles.map((r) => (_jsx(ListBadge, { children: r.description }, r.description))) })),
|
|
54
59
|
},
|
|
55
|
-
], entityName: 'Administrator', route: Routes['admins'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateAdministrator], deletePermissions: [Permission.DeleteAdministrator] }));
|
|
60
|
+
], entityName: 'Administrator', route: Routes['admins'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateAdministrator], deletePermissions: [Permission.DeleteAdministrator], additionalButtons: canCreateAdministrators ? (_jsxs(Button, { variant: "outline", className: "flex items-center gap-2", onClick: () => navigate(Routes.admins.provision), children: [_jsx(UserPlus, { size: 16 }), t('provision.action')] })) : null }));
|
|
56
61
|
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useNavigate } from 'react-router';
|
|
4
|
+
import { ArrowLeft, BadgeCheck, ShieldCheck, UserPlus } from 'lucide-react';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { apiClient, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, DEFAULT_CHANNEL_CODE, Input, Label, MultipleSelector, PageBlock, Routes, Separator, useServer, useTranslation, } from '@deenruv/react-ui-devkit';
|
|
7
|
+
import { PermissionsTable } from "../roles/_components/PermissionsTable";
|
|
8
|
+
import { getSelectedAssignablePermissions, normalizeRoleCode, SHOP_MANAGER_ROLE_PERMISSIONS, validateAdminProvisionForm, } from './Provision.logic';
|
|
9
|
+
const roleProvisionSelector = {
|
|
10
|
+
id: true,
|
|
11
|
+
code: true,
|
|
12
|
+
description: true,
|
|
13
|
+
permissions: true,
|
|
14
|
+
channels: {
|
|
15
|
+
id: true,
|
|
16
|
+
code: true,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
const findRoleByCode = async (code) => {
|
|
20
|
+
const response = await apiClient('query')({
|
|
21
|
+
roles: [
|
|
22
|
+
{
|
|
23
|
+
options: {
|
|
24
|
+
take: 1,
|
|
25
|
+
filter: {
|
|
26
|
+
code: {
|
|
27
|
+
eq: code,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{ items: roleProvisionSelector },
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
return response.roles.items[0];
|
|
36
|
+
};
|
|
37
|
+
export const AdminsProvisionPage = () => {
|
|
38
|
+
const { t } = useTranslation('admins');
|
|
39
|
+
const { t: tRoles } = useTranslation('roles');
|
|
40
|
+
const navigate = useNavigate();
|
|
41
|
+
const { channels, serverConfig } = useServer();
|
|
42
|
+
const [firstName, setFirstName] = useState('');
|
|
43
|
+
const [lastName, setLastName] = useState('');
|
|
44
|
+
const [emailAddress, setEmailAddress] = useState('');
|
|
45
|
+
const [password, setPassword] = useState('');
|
|
46
|
+
const [roleCode, setRoleCode] = useState('shop-manager');
|
|
47
|
+
const [roleDescription, setRoleDescription] = useState(t('provision.presets.shopManager.roleDescription'));
|
|
48
|
+
const [channelIds, setChannelIds] = useState([]);
|
|
49
|
+
const [permissions, setPermissions] = useState(SHOP_MANAGER_ROLE_PERMISSIONS);
|
|
50
|
+
const [reuseExistingRole, setReuseExistingRole] = useState(false);
|
|
51
|
+
const [errors, setErrors] = useState({});
|
|
52
|
+
const [submitting, setSubmitting] = useState(false);
|
|
53
|
+
const channelOptions = useMemo(() => channels.map((channel) => ({
|
|
54
|
+
value: channel.id,
|
|
55
|
+
label: channel.code === DEFAULT_CHANNEL_CODE ? tRoles('defaultChannel') : channel.code,
|
|
56
|
+
})), [channels, tRoles]);
|
|
57
|
+
const currentChannelOptions = useMemo(() => channelIds.map((id) => channelOptions.find((option) => option.value === id) || { value: id, label: id }), [channelIds, channelOptions]);
|
|
58
|
+
const assignablePermissions = useMemo(() => {
|
|
59
|
+
if (!serverConfig?.permissions)
|
|
60
|
+
return undefined;
|
|
61
|
+
return new Set(serverConfig.permissions
|
|
62
|
+
.filter((permission) => permission.assignable)
|
|
63
|
+
.map((permission) => permission.name));
|
|
64
|
+
}, [serverConfig]);
|
|
65
|
+
const selectedAssignablePermissions = useMemo(() => getSelectedAssignablePermissions(permissions, assignablePermissions), [assignablePermissions, permissions]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!channelIds.length && channels.length) {
|
|
68
|
+
setChannelIds(channels.map((channel) => channel.id));
|
|
69
|
+
}
|
|
70
|
+
}, [channelIds.length, channels]);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
setRoleDescription(t('provision.presets.shopManager.roleDescription'));
|
|
73
|
+
}, [t]);
|
|
74
|
+
const applyShopManagerPreset = () => {
|
|
75
|
+
setRoleCode('shop-manager');
|
|
76
|
+
setRoleDescription(t('provision.presets.shopManager.roleDescription'));
|
|
77
|
+
setPermissions(SHOP_MANAGER_ROLE_PERMISSIONS);
|
|
78
|
+
};
|
|
79
|
+
const validateForm = () => {
|
|
80
|
+
const result = validateAdminProvisionForm({
|
|
81
|
+
firstName,
|
|
82
|
+
lastName,
|
|
83
|
+
emailAddress,
|
|
84
|
+
password,
|
|
85
|
+
roleCode,
|
|
86
|
+
channelIds,
|
|
87
|
+
permissions,
|
|
88
|
+
reuseExistingRole,
|
|
89
|
+
}, {
|
|
90
|
+
firstNameRequired: t('validation.firstNameRequired'),
|
|
91
|
+
lastNameRequired: t('validation.lastNameRequired'),
|
|
92
|
+
emailRequired: t('provision.validation.emailRequired'),
|
|
93
|
+
emailInvalid: t('provision.validation.emailInvalid'),
|
|
94
|
+
passwordRequired: t('validation.passwordRequired'),
|
|
95
|
+
roleCodeRequired: t('provision.validation.roleCodeRequired'),
|
|
96
|
+
channelsRequired: t('provision.validation.channelsRequired'),
|
|
97
|
+
permissionsRequired: t('provision.validation.permissionsRequired'),
|
|
98
|
+
}, assignablePermissions);
|
|
99
|
+
setErrors(result.errors);
|
|
100
|
+
return result.valid;
|
|
101
|
+
};
|
|
102
|
+
const createRole = async () => {
|
|
103
|
+
const response = await apiClient('mutation')({
|
|
104
|
+
createRole: [
|
|
105
|
+
{
|
|
106
|
+
input: {
|
|
107
|
+
code: normalizeRoleCode(roleCode),
|
|
108
|
+
description: roleDescription.trim() || normalizeRoleCode(roleCode),
|
|
109
|
+
channelIds,
|
|
110
|
+
permissions: selectedAssignablePermissions,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
roleProvisionSelector,
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
return response.createRole;
|
|
117
|
+
};
|
|
118
|
+
const createAdministrator = async (roleId) => {
|
|
119
|
+
const response = await apiClient('mutation')({
|
|
120
|
+
createAdministrator: [
|
|
121
|
+
{
|
|
122
|
+
input: {
|
|
123
|
+
firstName: firstName.trim(),
|
|
124
|
+
lastName: lastName.trim(),
|
|
125
|
+
emailAddress: emailAddress.trim(),
|
|
126
|
+
password,
|
|
127
|
+
roleIds: [roleId],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: true,
|
|
132
|
+
emailAddress: true,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
return response.createAdministrator;
|
|
137
|
+
};
|
|
138
|
+
const handleSubmit = async () => {
|
|
139
|
+
if (!validateForm())
|
|
140
|
+
return;
|
|
141
|
+
setSubmitting(true);
|
|
142
|
+
const normalizedRoleCode = normalizeRoleCode(roleCode);
|
|
143
|
+
try {
|
|
144
|
+
const existingRole = await findRoleByCode(normalizedRoleCode);
|
|
145
|
+
let roleId = existingRole?.id;
|
|
146
|
+
if (reuseExistingRole) {
|
|
147
|
+
if (!roleId) {
|
|
148
|
+
setErrors({ roleCode: t('provision.validation.roleNotFound') });
|
|
149
|
+
toast.error(t('provision.toasts.roleNotFound'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
if (existingRole) {
|
|
155
|
+
setErrors({ roleCode: t('provision.validation.roleCodeExists') });
|
|
156
|
+
toast.error(t('provision.toasts.roleCodeExists'));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
roleId = (await createRole()).id;
|
|
160
|
+
}
|
|
161
|
+
const administrator = await createAdministrator(roleId);
|
|
162
|
+
toast.success(t('provision.toasts.success'));
|
|
163
|
+
navigate(Routes.admins.to(administrator.id));
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
toast.error(error instanceof Error ? error.message : t('provision.toasts.error'));
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
setSubmitting(false);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
return (_jsx(PageBlock, { children: _jsxs("div", { className: "flex flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx(Button, { variant: "secondary", size: "icon", className: "mt-1", onClick: () => navigate(Routes.admins.list), children: _jsx(ArrowLeft, { className: "size-4" }) }), _jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold tracking-tight", children: t('provision.title') }), _jsx("p", { className: "mt-1 max-w-3xl text-sm text-muted-foreground", children: t('provision.description') })] })] }), _jsxs(Button, { className: "gap-2", disabled: submitting, onClick: handleSubmit, children: [_jsx(UserPlus, { className: "size-4" }), submitting ? t('provision.submitting') : t('provision.submit')] })] }), _jsxs("div", { className: "grid gap-4 xl:grid-cols-[minmax(0,1fr)_380px]", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: t('provision.admin.title') }), _jsx(CardDescription, { children: t('provision.admin.description') })] }), _jsx(CardContent, { children: _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(Input, { label: t('details.basic.firstName'), value: firstName, onChange: (event) => setFirstName(event.target.value), errors: errors.firstName ? [errors.firstName] : undefined, required: true }), _jsx(Input, { label: t('details.basic.lastName'), value: lastName, onChange: (event) => setLastName(event.target.value), errors: errors.lastName ? [errors.lastName] : undefined, required: true }), _jsx(Input, { label: t('details.basic.emailAddress'), type: "email", value: emailAddress, onChange: (event) => setEmailAddress(event.target.value), errors: errors.emailAddress ? [errors.emailAddress] : undefined, required: true }), _jsx(Input, { label: t('details.basic.password'), type: "password", value: password, onChange: (event) => setPassword(event.target.value), errors: errors.password ? [errors.password] : undefined, required: true })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: t('provision.role.title') }), _jsx(CardDescription, { children: t('provision.role.description') })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(Input, { label: t('provision.role.code'), value: roleCode, onChange: (event) => setRoleCode(event.target.value), onBlur: () => setRoleCode(normalizeRoleCode(roleCode)), errors: errors.roleCode ? [errors.roleCode] : undefined, required: true }), _jsx(Input, { label: t('provision.role.descriptionLabel'), value: roleDescription, onChange: (event) => setRoleDescription(event.target.value), required: !reuseExistingRole, disabled: reuseExistingRole })] }), _jsxs("label", { className: "flex items-start gap-3 border border-border/70 bg-muted/30 p-3 text-sm", children: [_jsx(Checkbox, { checked: reuseExistingRole, onCheckedChange: (checked) => setReuseExistingRole(checked === true) }), _jsxs("span", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "font-medium", children: t('provision.role.reuseExisting') }), _jsx("span", { className: "text-muted-foreground", children: t('provision.role.reuseExistingHint') })] })] }), !reuseExistingRole && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t('provision.role.channels') }), _jsx(MultipleSelector, { options: channelOptions, value: currentChannelOptions, placeholder: t('provision.role.channelsPlaceholder'), onChange: (options) => setChannelIds(options.map((option) => option.value)), hideClearAllButton: true }), errors.channelIds && _jsx("p", { className: "text-xs text-destructive", children: errors.channelIds })] }))] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: t('provision.permissions.title') }), _jsx(CardDescription, { children: t('provision.permissions.description') })] }), _jsx(CardContent, { children: reuseExistingRole ? (_jsx("div", { className: "border border-dashed border-border p-4 text-sm text-muted-foreground", children: t('provision.permissions.reuseExisting') })) : (_jsxs("div", { className: "flex flex-col gap-3", children: [errors.permissions && _jsx("p", { className: "text-xs text-destructive", children: errors.permissions }), _jsx(PermissionsTable, { currentPermissions: selectedAssignablePermissions, onPermissionsChange: setPermissions })] })) })] })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(ShieldCheck, { className: "size-5 text-primary" }), t('provision.presets.title')] }), _jsx(CardDescription, { children: t('provision.presets.description') })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [_jsx("button", { type: "button", className: "border border-primary/40 bg-primary/10 p-4 text-left transition-colors hover:bg-primary/15", onClick: applyShopManagerPreset, children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: t('provision.presets.shopManager.title') }), _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: t('provision.presets.shopManager.description') })] }), _jsx(Badge, { variant: "secondary", children: t('provision.presets.defaultBadge') })] }) }), _jsx(Separator, {}), _jsxs("div", { className: "flex items-start gap-3 text-sm", children: [_jsx(BadgeCheck, { className: "mt-0.5 size-4 text-green-600" }), _jsx("p", { className: "text-muted-foreground", children: t('provision.presets.backendNote') })] })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: t('provision.summary.title') }), _jsx(CardDescription, { children: t('provision.summary.description') })] }), _jsxs(CardContent, { className: "flex flex-col gap-3 text-sm", children: [_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("span", { className: "text-muted-foreground", children: t('provision.summary.role') }), _jsx(Badge, { variant: "outline", children: normalizeRoleCode(roleCode) || '-' })] }), _jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("span", { className: "text-muted-foreground", children: t('provision.summary.channels') }), _jsx("span", { className: "font-medium", children: reuseExistingRole ? '-' : channelIds.length })] }), _jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("span", { className: "text-muted-foreground", children: t('provision.summary.permissions') }), _jsx("span", { className: "font-medium", children: reuseExistingRole ? '-' : selectedAssignablePermissions.length })] }), _jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("span", { className: "text-muted-foreground", children: t('provision.summary.mode') }), _jsx("span", { className: "font-medium", children: reuseExistingRole ? t('provision.summary.reuse') : t('provision.summary.create') })] })] })] })] })] })] }) }));
|
|
173
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Permission } from '@deenruv/admin-types';
|
|
2
|
+
export const SHOP_MANAGER_ROLE_PERMISSIONS = [
|
|
3
|
+
Permission.ReadOrder,
|
|
4
|
+
Permission.UpdateOrder,
|
|
5
|
+
Permission.ReadCustomer,
|
|
6
|
+
Permission.UpdateCustomer,
|
|
7
|
+
Permission.ReadProduct,
|
|
8
|
+
Permission.ReadCatalog,
|
|
9
|
+
Permission.ReadAsset,
|
|
10
|
+
Permission.ReadCollection,
|
|
11
|
+
Permission.ReadFacet,
|
|
12
|
+
Permission.ReadPaymentMethod,
|
|
13
|
+
Permission.ReadShippingMethod,
|
|
14
|
+
Permission.ReadCountry,
|
|
15
|
+
Permission.ReadStockLocation,
|
|
16
|
+
];
|
|
17
|
+
export const normalizeRoleCode = (value) => value.trim().toLowerCase().replace(/\s+/g, '-');
|
|
18
|
+
export const getSelectedAssignablePermissions = (permissions, assignablePermissions) => {
|
|
19
|
+
if (!assignablePermissions)
|
|
20
|
+
return permissions;
|
|
21
|
+
return permissions.filter((permission) => assignablePermissions.has(permission));
|
|
22
|
+
};
|
|
23
|
+
export const validateAdminProvisionForm = (values, messages, assignablePermissions) => {
|
|
24
|
+
const errors = {};
|
|
25
|
+
const normalizedRoleCode = normalizeRoleCode(values.roleCode);
|
|
26
|
+
const selectedAssignablePermissions = getSelectedAssignablePermissions(values.permissions, assignablePermissions);
|
|
27
|
+
if (!values.firstName.trim())
|
|
28
|
+
errors.firstName = messages.firstNameRequired;
|
|
29
|
+
if (!values.lastName.trim())
|
|
30
|
+
errors.lastName = messages.lastNameRequired;
|
|
31
|
+
if (!values.emailAddress.trim())
|
|
32
|
+
errors.emailAddress = messages.emailRequired;
|
|
33
|
+
if (values.emailAddress.trim() && !values.emailAddress.includes('@')) {
|
|
34
|
+
errors.emailAddress = messages.emailInvalid;
|
|
35
|
+
}
|
|
36
|
+
if (!values.password.trim())
|
|
37
|
+
errors.password = messages.passwordRequired;
|
|
38
|
+
if (!normalizedRoleCode)
|
|
39
|
+
errors.roleCode = messages.roleCodeRequired;
|
|
40
|
+
if (!values.reuseExistingRole) {
|
|
41
|
+
if (!values.channelIds.length)
|
|
42
|
+
errors.channelIds = messages.channelsRequired;
|
|
43
|
+
if (!selectedAssignablePermissions.length) {
|
|
44
|
+
errors.permissions = messages.permissionsRequired;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
errors,
|
|
49
|
+
normalizedRoleCode,
|
|
50
|
+
selectedAssignablePermissions,
|
|
51
|
+
valid: Object.keys(errors).length === 0,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { apiClient, DetailList, deepMerge, ListLocations, createDialogFromComponent, Badge, ImageWithPreview, useTranslation, TableLabel, } from '@deenruv/react-ui-devkit';
|
|
3
|
-
import { Permission, SortOrder } from '@deenruv/admin-types';
|
|
3
|
+
import { DeletionResult, Permission, SortOrder } from '@deenruv/admin-types';
|
|
4
4
|
import { UploadAssetDialog } from "./_components/UploadAssetDialog.js";
|
|
5
5
|
import { EditAssetDialog } from "./_components/EditAssetDialog.js";
|
|
6
6
|
const tableId = 'assets-list-view';
|
|
@@ -28,7 +28,7 @@ const onRemove = async (items) => {
|
|
|
28
28
|
const { deleteAssets } = await apiClient('mutation')({
|
|
29
29
|
deleteAssets: [{ input: { assetIds: ids } }, { message: true, result: true }],
|
|
30
30
|
});
|
|
31
|
-
return
|
|
31
|
+
return deleteAssets.result === DeletionResult.DELETED;
|
|
32
32
|
}
|
|
33
33
|
catch (error) {
|
|
34
34
|
return error;
|
|
@@ -78,5 +78,5 @@ export const AssetsListPage = () => {
|
|
|
78
78
|
return (_jsx("div", { className: "flex flex-wrap gap-1", children: tags.length ? (tags.map((tag, index) => (_jsx(Badge, { variant: "secondary", className: "text-xs", children: tag.value }, index)))) : (_jsx(Badge, { variant: "secondary", className: "text-xs", children: "No Tags" })) }));
|
|
79
79
|
},
|
|
80
80
|
},
|
|
81
|
-
], createPermissions: [Permission.
|
|
81
|
+
], createPermissions: [Permission.CreateAsset], deletePermissions: [Permission.DeleteAsset] }));
|
|
82
82
|
};
|
|
@@ -112,7 +112,7 @@ export const ChannelsDetailPage = () => {
|
|
|
112
112
|
if (!id) {
|
|
113
113
|
throw new Error('Could not find the id.');
|
|
114
114
|
}
|
|
115
|
-
return remove({
|
|
115
|
+
return remove({ id });
|
|
116
116
|
}, [remove, id]);
|
|
117
117
|
return (_jsx("div", { className: "relative flex flex-col gap-y-4", children: _jsx(DetailView, { id: id, locationId: "channels-detail-view", main: {
|
|
118
118
|
name: 'channel',
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback } from 'react';
|
|
3
3
|
import { useParams } from 'react-router';
|
|
4
|
-
import { useValidators, DetailView, createDeenruvForm, getMutation, useMutation } from '@deenruv/react-ui-devkit';
|
|
4
|
+
import { useValidators, DetailView, createDeenruvForm, getMutation, useMutation, useSettings, } from '@deenruv/react-ui-devkit';
|
|
5
5
|
import { CollectionsDetailView } from "./_components/CollectionDetailView.js";
|
|
6
|
+
import { CollectionStorefrontAction } from "./_components/collectionStorefrontAction";
|
|
6
7
|
const CreateCollectionMutation = getMutation('createCollection');
|
|
7
8
|
const EditCollectionMutation = getMutation('updateCollection');
|
|
8
9
|
const DeleteCollectionMutation = getMutation('deleteCollection');
|
|
10
|
+
const AssignCollectionsToChannelMutation = getMutation('assignCollectionsToChannel');
|
|
9
11
|
export const CollectionsDetailPage = () => {
|
|
10
12
|
const { id } = useParams();
|
|
11
13
|
const [update] = useMutation(EditCollectionMutation);
|
|
12
14
|
const [create] = useMutation(CreateCollectionMutation);
|
|
13
15
|
const [remove] = useMutation(DeleteCollectionMutation);
|
|
16
|
+
const [assignCollectionsToChannel] = useMutation(AssignCollectionsToChannelMutation);
|
|
17
|
+
const selectedChannel = useSettings((p) => p.selectedChannel);
|
|
14
18
|
const { configurableOperationArrayValidator, translationsValidator } = useValidators();
|
|
15
|
-
const onSubmitHandler = useCallback((data) => {
|
|
19
|
+
const onSubmitHandler = useCallback(async (data) => {
|
|
16
20
|
const translations = data.translations;
|
|
17
21
|
const inputData = {
|
|
18
22
|
assetIds: data.assetIds,
|
|
@@ -29,14 +33,21 @@ export const CollectionsDetailPage = () => {
|
|
|
29
33
|
};
|
|
30
34
|
if (id)
|
|
31
35
|
return update({ input: { id, ...inputData } });
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
const response = await create({ input: inputData });
|
|
37
|
+
const collectionId = response.createCollection.id;
|
|
38
|
+
const additionalChannelIds = Array.isArray(data.additionalChannelIds)
|
|
39
|
+
? data.additionalChannelIds.filter((channelId) => typeof channelId === 'string' && channelId !== selectedChannel?.id)
|
|
40
|
+
: [];
|
|
41
|
+
for (const channelId of additionalChannelIds) {
|
|
42
|
+
await assignCollectionsToChannel({ input: { collectionIds: [collectionId], channelId } });
|
|
43
|
+
}
|
|
44
|
+
return response;
|
|
45
|
+
}, [id, update, create, assignCollectionsToChannel, selectedChannel?.id]);
|
|
35
46
|
const onDeleteHandler = useCallback(() => {
|
|
36
47
|
if (!id) {
|
|
37
48
|
throw new Error('Could not find the id.');
|
|
38
49
|
}
|
|
39
|
-
return remove({
|
|
50
|
+
return remove({ id });
|
|
40
51
|
}, [remove, id]);
|
|
41
52
|
return (_jsx("div", { className: "relative flex flex-col gap-y-4", children: _jsx(DetailView, { id: id, locationId: "collections-detail-view", main: {
|
|
42
53
|
name: 'collection',
|
|
@@ -52,11 +63,18 @@ export const CollectionsDetailPage = () => {
|
|
|
52
63
|
inheritFilters: {
|
|
53
64
|
initialValue: true,
|
|
54
65
|
},
|
|
66
|
+
...{
|
|
67
|
+
additionalChannelIds: {
|
|
68
|
+
initialValue: [],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
55
71
|
translations: translationsValidator,
|
|
56
72
|
filters: configurableOperationArrayValidator(),
|
|
57
73
|
},
|
|
58
74
|
onSubmitted: onSubmitHandler,
|
|
59
75
|
onDeleted: onDeleteHandler,
|
|
60
76
|
}),
|
|
77
|
+
}, topActions: {
|
|
78
|
+
inline: [_jsx(CollectionStorefrontAction, {}, "collection-storefront-action")],
|
|
61
79
|
} }) }));
|
|
62
80
|
};
|